/*
 * HFS web UI stylesheet.
 *
 * Implements the Figma design "Dashboard V1.1" (file CcLtq79cH2aHv4Ii9aNQTP,
 * nodes 34:2 light / 34:484 dark / 34:222 tenant selector). Colors, type
 * scale, radii, and shadows below are lifted verbatim from that file's
 * inspected values — change them there first, then here.
 *
 * Theming: custom properties on :root (light) overridden by
 * [data-theme="dark"]. assets/theme.js stamps the attribute before first
 * paint; with JS disabled the page stays light, which is the design default.
 */

/* Cascade layers (#543): tokens < base < components < pages. A page-layer
   rule outranks a component rule regardless of specificity, and shared
   component definitions can never be accidentally beaten by an earlier
   page block. Every rule below lives in exactly one layer. */
@layer tokens, base, components, pages;

@layer base {
/* Vendored Figtree variable font (OFL) — no runtime CDN, per crate rules. */
@font-face {
  font-family: "Figtree";
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url("fonts/figtree-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Figtree";
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url("fonts/figtree-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
}

@layer pages {
/* CapabilityStatement-only presentation: all card headers stay edge-to-edge,
   while their bodies own the content rhythm appropriate to fields, tags, or
   tables. The generic table-card keeps its padded treatment elsewhere. */
.cap-operations-card,
.cap-resource-card {
  padding: 0;
}

.cap-interaction-tags {
  margin: 0;
}

.cap-interaction-tags > .tag {
  margin-left: 0;
}

/* Keep the backend note subordinate to its interaction tags. */
.cap-transaction-note {
  margin: 8px 0 0 16px;
  padding-left: 10px;
  border-left: 2px solid var(--surface-border);
  color: var(--muted);
  font-size: 12px;
  line-height: 1.45;
}

.cap-resource-filter {
  flex: 0 1 280px;
  min-width: 0;
}

.cap-resource-filter input {
  width: 100%;
  min-width: 0;
}

.capability-raw-card > .card-head {
  justify-content: flex-start;
  flex-wrap: wrap;
}

.capability-json-actions {
  align-items: center;
  flex-wrap: wrap;
  min-width: 0;
}

.capability-json-actions__status:empty {
  display: none;
}

.capability-json-fallback {
  margin: 0 0 10px;
}

/* One incremental JSON pane inside the HTS workbench raw fold (#898):
   a labelled data-capability-json-root with its own expand/collapse
   controls, stacked twice (request body, response body). */
.capability-json-pane {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.capability-json-pane + .capability-json-pane {
  margin-top: 14px;
}

/* Like `.capability-raw-card > .card-head`: label and controls sit together,
   left-aligned. */
.capability-json-pane__head {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  flex-wrap: wrap;
}

.capability-json-pane__head > span {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

@media (max-width: 520px) {
  .cap-resource-card > .card-head {
    flex-direction: column;
    align-items: stretch;
  }

  .cap-resource-filter {
    flex-basis: auto;
    width: 100%;
  }

  .capability-raw-card > .card-head {
    align-items: stretch;
  }

  .capability-json-actions {
    width: 100%;
  }

  .capability-json-actions__status {
    flex-basis: 100%;
  }
}
}

@layer tokens {
:root {
  color-scheme: light;
  /* Page + panes */
  --bg: #f5f5f5;
  --bg-content: #fafafa;
  /* Raised surfaces (cards, active nav, controls) */
  --surface: linear-gradient(180deg, #fafafa 0%, #f8f8f8 100%);
  --surface-border: #d4d4d4;
  --surface-shadow: 0 1px 2px rgb(0 0 0 / 8%), 0 4px 10px rgb(0 0 0 / 6%);
  /* Floating panels that open over a card: one step above --surface. */
  --popover: #ffffff;
  --popover-border: #d6d6d6;
  --popover-shadow: 0 16px 40px rgb(0 0 0 / 16%), 0 2px 8px rgb(0 0 0 / 8%);
  /* Text */
  --text: #101010;
  --text-strong: #000000;
  /* WCAG 2.2 AA: muted body text must clear 4.5:1 on the light surfaces. */
  --muted: #6a6a6a;
  /* Embossed dividers: dark line + light line underneath */
  --divider: #e4e4e4;
  --divider-glint: #ffffff;
  /* The edge shadow on the sidebar while it has more to scroll (#1676). */
  --sidebar-scroll-shadow: rgb(0 0 0 / 28%);
  /* Outline-only pills (version selector) */
  --outline: #e4e4e4;
  /* Dialogs (#681): one scrim for every modal treatment (Edit Resource's
     .modal__backdrop and the addbox--modal disclosures' summary-as-backdrop)
     so a future change to the darken amount is a single edit. Black at 45%
     is the value Edit Resource shipped with; re-declared (same value, for
     now) under [data-theme="dark"] below as the point to retune it — flat
     black reads differently over a dark page, but that tuning is out of
     scope here. One shared z-index band keeps both parts of every dialog
     above the rest of the page (sidebar, dropdown menus) without the
     per-dialog 20/40/50/100 spread this replaces; .menu__panel dropdowns are
     not dialogs and keep their own stacking. */
  --scrim: rgb(0 0 0 / 45%);
  --z-dialog-backdrop: 100;
  --z-dialog-panel: 110;
  /* Chart */
  --grid-line: #dcdcdc;
  /* Series palette (#555): six distinguishable strokes, legible on both
     themes at 1.5px. Slot 1 is the brand accent. */
  --series-1: #33b8ff;
  --series-2: #7c6cff;
  --series-3: #2fbf71;
  --series-4: #f39c2d;
  --series-5: #e35d8a;
  --series-6: #8a93a6;
  --accent: #33b8ff;
  --accent-soft: rgb(51 184 255 / 15%);
  /* A darker cast of the accent for the one place the light celeste can't work:
     a solid fill behind white text (primary buttons). #33b8ff carries white at
     only 2.2:1; this clears 4.5:1 while staying in the brand's blue. */
  --accent-strong: #0f77bd;
  /* Accent *text* on the soft-accent tint (the selected rail item). Needs the
     opposite move per theme — darker on the light tint, lighter on the dark —
     so it is theme-specific (see the dark block). */
  --accent-text: #0a5f96;
  --json-key: #0a5f96;
  --json-string: #137333;
  --json-number: #b4232d;
  --json-literal: #6f42c1;
  /* FHIRPath token palette (#753 evaluation POC, vd-editor.js): the
     injected grammar's tags mapped to distinct hues, each clearing 4.5:1 on
     --input-bg below. Kept apart from --json-* above so a FHIRPath path
     inside a JSON string never reads as "just another JSON string". */
  --fp-path: #0f6f66;
  --fp-function: #8a5a00;
  --fp-keyword: #9a3aa0;
  --fp-operator: #4b5563;
  --fp-literal: #0b6e99;
  --fp-constant: #b1460a;
  --fp-comment: #5b6472;
  /* SQL token palette (#838, sql-editor.js): declared with its own
     names rather than reusing --json-* / --fp-* directly, so the SQL editor
     never depends on another editor's palette even where the hex values
     happen to match today. Each clears 4.5:1 on --input-bg below; keyword
     (purple) and variable (teal) are deliberately different hues from each
     other and from --text, so a `:ward`-style bind parameter never reads as
     "just another keyword". */
  --sql-keyword: #9a3aa0;
  --sql-type: #0a5f96;
  --sql-string: #137333;
  --sql-number: #b4232d;
  --sql-operator: #4b5563;
  --sql-variable: #0f6f66;
  --sql-comment: #5b6472;
  --sql-punct: #6a6a6a;
  --sql-literal: #8a5a00;
  /* Destructive text clears 7:1 on the light card surface; the soft hover
     fill keeps 5.8:1 contrast with the same foreground. */
  --danger: #a61b12;
  /* Positive / caution pairs, mirroring the danger pair: referenced by the
     stored/member and warn tag variants and the progress track since the
     SearchParameter viewer landed, but never defined - they rendered
     transparent. */
  --ok: #1e7d46;
  --ok-soft: #e4f6ec;
  --warn: #8a5a00;
  --warn-soft: #fdf2dd;
  --danger-soft: #f0dfde;
  /* Neutral pair for the cancelled state (#833): the same shape as the
     ok/warn/danger soft pairs above, but genuinely gray so
     "Cancelled" never reads as an accent-tinted (blue) or danger (red)
     status. */
  --muted-soft: #e9e9e9;
  /* Text on --muted-soft (PR #887 CI a11y fix): --muted itself only clears
     4.455:1 against --muted-soft, just under the 4.5:1 axe requires. Same
     move as --accent-text above — a dedicated, darker-on-light /
     lighter-on-dark shade for text sitting on the soft tint, not a change to
     --muted (which stays as every other muted body-text use already relies
     on it). Clears 6.4:1 here. */
  --muted-text: #525252;
  /* Menu panel input */
  --input-bg: #f5f5f5;
  /* Pill scrollbars: thin bar, fully-rounded track and thumb. */
  --scroll-track: #ececec;
  --scroll-thumb: #bdbdbd;
  --scroll-thumb-hover: #a0a0a0;
  /* The semantic status pairs `.tag--*`, `.lint__item--*`, `.notice--warn` and
     `.batch-badge--*` reference are declared once, above. This branch used to
     redeclare all six here; every one of them now has a canonical definition
     earlier in the same rule, so a second declaration only shadowed it — which
     is what `design-system.spec.ts` and the contrast guard in
     `bulk-import.spec.ts` exist to catch. */

  font-family: Figtree, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
}

[data-theme="dark"] {
  color-scheme: dark;
  --bg: #121212;
  --bg-content: #1d1d1d;
  --surface: linear-gradient(180deg, #222222 0%, #242424 100%);
  --surface-border: rgb(255 255 255 / 8%);
  --surface-shadow: 0 4px 4px rgb(0 0 0 / 30%);
  /* Floating panels that open over a card: one step above --surface. */
  --popover: #2e2e2e;
  --popover-border: rgb(255 255 255 / 16%);
  --popover-shadow: 0 16px 40px rgb(0 0 0 / 60%), 0 2px 8px rgb(0 0 0 / 40%);
  --text: #ffffff;
  --text-strong: #ffffff;
  /* On the dark surfaces the lighter grey already clears 4.5:1; keep it (the
     light-theme override above would be too dark to read here). */
  --muted: #a0a0a0;
  /* Selected-item accent text on the dark tint: lighter, to clear 4.5:1. */
  --accent-text: #7cc9ff;
  --json-key: #7cc9ff;
  --json-string: #7ee787;
  --json-number: #ff7b72;
  --json-literal: #d2a8ff;
  /* FHIRPath token palette, dark cast — see the light block for why these
     are distinct from --json-*. */
  --fp-path: #5eead4;
  --fp-function: #e3b341;
  --fp-keyword: #e2a8ff;
  --fp-operator: #9aa7b5;
  --fp-literal: #79c0ff;
  --fp-constant: #ffa657;
  --fp-comment: #9198a1;
  /* SQL token palette, dark cast — see the light block for why these are
     distinct from --json-* / --fp-*. */
  --sql-keyword: #e2a8ff;
  --sql-type: #7cc9ff;
  --sql-string: #7ee787;
  --sql-number: #ff7b72;
  --sql-operator: #9aa7b5;
  --sql-variable: #5eead4;
  --sql-comment: #9198a1;
  --sql-punct: #a0a0a0;
  --sql-literal: #e3b341;
  /* Theme-specific destructive pair: 7.6:1 on the card and 5.6:1 on soft. */
  --danger: #ff9a90;
  --ok: #7fd8a5;
  --ok-soft: #14301f;
  --warn: #e8c07a;
  --warn-soft: #2f2410;
  --danger-soft: #453534;
  --muted-soft: #333333;
  /* Opposite move from the light theme (lighter, not darker) — see the
     light block. Clears 5.8:1 against --muted-soft here. */
  --muted-text: #b0b0b0;
  /* Same value as light for now (#681) — the point to adjust from if flat
     black at 45% is later found to read too heavy/light over the dark bg. */
  --scrim: rgb(0 0 0 / 45%);
  --divider: #000000;
  --divider-glint: rgb(255 255 255 / 12%);
  --sidebar-scroll-shadow: rgb(255 255 255 / 32%);
  --outline: #353535;
  --grid-line: rgb(255 255 255 / 12%);
  --input-bg: #1a1a1a;
  --scroll-track: rgb(255 255 255 / 7%);
  --scroll-thumb: rgb(255 255 255 / 25%);
  --scroll-thumb-hover: rgb(255 255 255 / 38%);
  /* Dark-theme status pairs are declared once, above — see the light block. */
}
}

@layer base {
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  grid-template-columns: 76px 1fr;
  font-size: 15px;
  line-height: 1.2;
}
}

@layer base {
svg {
  display: block;
}

/* Class display values (flex, inline-flex) must not defeat the hidden
   attribute on script-toggled regions. */
[hidden] {
  display: none !important;
}

/* Minimal pill scrollbars everywhere: an 8px bar whose track and thumb are
   fully-rounded pills. The standard properties are Firefox-only on purpose:
   Chromium also understands scrollbar-width, and when it is set it IGNORES
   the ::-webkit-scrollbar styling below — so they must never apply there. */
@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--scroll-thumb) var(--scroll-track);
  }
}

::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: var(--scroll-track);
  border-radius: 999px;
}

::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border-radius: 999px;
}

::-webkit-scrollbar-thumb.is-hover {
  background: var(--scroll-thumb-hover);
}
}

@layer components {
/* ---------- Sidebar ---------- */

.sidebar {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: 40;
  box-sizing: border-box;
  width: 295px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 20px 24px;
  border-right: 1px solid var(--divider);
  box-shadow: 1px 0 0 var(--divider-glint);
  /* Whenever the rail overflows (a shorter screen, or the expanded rail with
     its group labels), a shadow at the clipped edge shows there is more to
     scroll (#1676). The two covers scroll with the content and hide each
     shadow once its edge is reached. */
  background:
    linear-gradient(var(--bg) 30%, transparent) center top / 100% 32px no-repeat local,
    linear-gradient(transparent, var(--bg) 70%) center bottom / 100% 32px no-repeat local,
    radial-gradient(farthest-side at 50% 0, var(--sidebar-scroll-shadow), transparent) center top / 100% 16px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, var(--sidebar-scroll-shadow), transparent) center bottom / 100% 16px no-repeat scroll,
    var(--bg);
  overflow-y: auto;
  overflow-x: hidden;
  transition: width 0.15s ease;
}

/* Expanded (hover / keyboard focus): overlay the content with a lift shadow. */
.sidebar.is-hover,
.sidebar:focus-within {
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.14);
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 4px 0;
  color: inherit;
  text-decoration: none;
}

.brand.is-hover .brand__name,
.brand:focus-visible .brand__name {
  color: var(--accent-text);
}

.brand:focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 2px;
}

.brand__logo {
  width: 40px;
  height: 39px;
}

.brand__name {
  display: block;
  font-weight: 500;
  color: var(--text-strong);
}

.brand__version {
  display: block;
  margin-top: 2px;
  font-family: "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 10px;
  color: var(--muted);
}

.divider {
  height: 0;
  margin: 0;
  border: 0;
  border-top: 1px solid var(--divider);
  box-shadow: 0 1px 0 var(--divider-glint);
}

/* Sidebar nav */

.nav {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 4px;
}

.nav__section {
  margin: 16px 4px 8px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 44px;
  padding: 0 16px;
  border: 1px solid transparent;
  border-radius: 16px;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
}

.nav-item .icon {
  flex: none;
  color: inherit;
}

a.nav-item.is-hover {
  color: var(--text-strong);
}

.nav-item[aria-current="page"] {
  background: var(--surface);
  border-color: var(--surface-border);
  box-shadow: var(--surface-shadow);
  color: var(--text-strong);
}

/* Placeholder entries for pages that don't exist yet. */
.nav-item--soon {
  cursor: default;
}

.nav-item__label {
  white-space: nowrap;
}

/* ---------- Collapsed (icon-only) rail ---------- */

.brand__text {
  flex: 1;
  min-width: 0;
}

/*
 * Collapsed rail: the resting state, whenever the pointer and the keyboard
 * focus are both elsewhere (#438 dropped the explicit toggle). Labels are
 * visually hidden but kept in the accessibility tree so screen readers still
 * announce them; a `title` on each item gives a hover tooltip, and hovering
 * or focusing the rail expands it in place.
 */
.sidebar:not(.is-hover):not(:focus-within) {
  width: 76px;
  padding: 20px 14px;
  align-items: center;
}

.sidebar:not(.is-hover):not(:focus-within) .brand {
  flex-direction: column;
  gap: 10px;
}

.sidebar:not(.is-hover):not(:focus-within) .brand__text {
  display: none;
}

.sidebar:not(.is-hover):not(:focus-within) .nav__section {
  /* Group headers are decorative; drop them entirely when collapsed. */
  height: 1px;
  margin: 8px 0;
  overflow: hidden;
  text-indent: -999px;
  border-top: 1px solid var(--divider);
}

.sidebar:not(.is-hover):not(:focus-within) .nav-item {
  width: 48px;
  padding: 0;
  justify-content: center;
}

.sidebar:not(.is-hover):not(:focus-within) .selector {
  width: 48px;
  padding: 8px;
  justify-content: center;
}

/* Hide the text of labels/selectors without removing them from the a11y tree. */
.sidebar:not(.is-hover):not(:focus-within) .nav-item__label,
.sidebar:not(.is-hover):not(:focus-within) .selector__label,
.sidebar:not(.is-hover):not(:focus-within) .selector__chevrons,
.sidebar:not(.is-hover):not(:focus-within) .brand__version {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Short screens (#1676): a tighter rhythm keeps every entry of the resting
   rail on screen down to a 1366x768 laptop. */
@media (max-height: 1040px) {
  .sidebar,
  .sidebar:not(.is-hover):not(:focus-within) {
    gap: 10px;
    padding-top: 12px;
    padding-bottom: 12px;
  }

  .nav {
    gap: 2px;
  }

  .nav-item {
    height: 34px;
  }

  .nav__section {
    margin: 8px 4px 2px;
  }

  .sidebar:not(.is-hover):not(:focus-within) .nav__section {
    margin: 4px 0;
  }

  .brand__logo {
    width: 34px;
    height: 33px;
  }
}

/* ---------- Menus (tenant selector, shared popover panel) ---------- */

.menu {
  position: relative;
}

.menu > summary {
  list-style: none;
  cursor: pointer;
}

.menu > summary::-webkit-details-marker {
  display: none;
}

/* Footer variant: the version selector opens upward, clear of the viewport
   bottom edge. */
.menu--up .menu__panel {
  top: auto;
  bottom: calc(100% + 8px);
}

.menu__panel {
  position: absolute;
  z-index: 10;
  top: calc(100% + 8px);
  left: 0;
  width: 246px;
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 16px;
  box-shadow: var(--surface-shadow);
}

.selector {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 48px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 16px;
  box-shadow: var(--surface-shadow);
  font-weight: 500;
  color: var(--muted);
}

.selector__label {
  flex: 1;
}

.selector__chevrons {
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: var(--muted);
}

.selector__chevrons .icon--flip {
  transform: rotate(180deg);
}

/* Outline-only variant: the FHIR version selector (outline-only so it pops). */
.selector--outline {
  background: none;
  border-color: var(--outline);
  box-shadow: none;
  padding: 8px 16px;
}

.avatar {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 10px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12px;
  font-weight: 500;
}

/* Tenant dropdown panel */

.menu__search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 33px;
  padding: 0 10px;
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  color: var(--muted);
}

.menu__search input {
  width: 100%;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12px;
  color: var(--text);
  outline: none;
}

.menu__heading {
  margin: 12px 4px 8px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.menu__option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px;
  border-radius: 10px;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
}

/* The version options are form buttons; keep them visually identical to the
   anchor options. */
button.menu__option {
  width: 100%;
  font: inherit;
  font-weight: 500;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
}

.menu__option.is-hover {
  color: var(--text-strong);
}

.menu__option--danger {
  color: var(--danger);
}

.menu__option[aria-current="true"] {
  color: var(--text-strong);
}

.menu__option .check {
  margin-left: auto;
  color: var(--accent);
}

.sidebar__foot {
  margin-top: auto;
}

/* ---------- Top bar + content pane ---------- */

.pane {
  grid-column: 2;
  display: flex;
  flex-direction: column;
  background: var(--bg-content);
  min-width: 0;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: 53px;
  padding: 0 32px;
  /* Same surface as the sidebar (the body background), not the content
     pane's — the header and the left menu read as one frame. */
  background: var(--bg);
  border-bottom: 1px solid var(--divider);
  box-shadow: 0 1px 0 var(--divider-glint);
}

.topbar__tools {
  display: flex;
  align-items: center;
  gap: 16px;
}

.theme-toggle {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 16px;
  box-shadow: var(--surface-shadow);
}

.theme-toggle button {
  display: grid;
  place-items: center;
  /* WCAG 2.2 AA, SC 2.5.8 (Target Size Minimum): each toggle is a >=24x24
     target. The icon inside stays small; only the hit area grows. */
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

/* The active side of the toggle is raised, like the design's inner circle. */
:root:not([data-theme="dark"]) .theme-toggle button[data-set-theme="light"],
[data-theme="dark"] .theme-toggle button[data-set-theme="dark"] {
  border-color: var(--surface-border);
  box-shadow: var(--surface-shadow);
  color: var(--text-strong);
}

.topbar__avatar {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 50%;
  box-shadow: var(--surface-shadow);
  font-weight: 500;
  color: var(--muted);
  overflow: hidden;
}

/* The user menu (#725): the avatar doubles as the disclosure summary; the
   panel hangs off the topbar's right edge at a width for a name + email. */
.menu--user .menu__panel {
  width: 264px;
}

.topbar__avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Account-card layout (#725, proposal B): centered identity block, the
   language selector as a segmented row, Sign out full width. */
.user-menu__id {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 14px 4px 12px;
  text-align: center;
}

.user-menu__avatar {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--accent-strong);
  color: #ffffff;
  font-weight: 600;
  font-size: 20px;
}

.user-menu__name {
  max-width: 100%;
  font-weight: 600;
  color: var(--text-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-menu__hint {
  max-width: 100%;
  font-size: 12px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-menu__seg {
  display: flex;
  gap: 2px;
  padding: 3px;
  margin: 2px 2px 4px;
  background: var(--accent-soft);
  border-radius: 999px;
}

.user-menu__seg a {
  flex: 1;
  padding: 6px 0;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
  text-align: center;
  text-decoration: none;
}

.user-menu__seg a[aria-current="true"] {
  background: var(--surface);
  color: var(--text-strong);
  box-shadow: var(--surface-shadow);
}

/* Sign out is a POST form (#1449, #738): a `<button>` styled as the row the
   anchor used to draw, so the resets below keep it looking identical. */
.user-menu__out-form {
  display: block;
  margin: 0;
}

.user-menu__out {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: calc(100% - 4px);
  margin: 4px 2px 2px;
  padding: 8px 0;
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  background: none;
  font: inherit;
  font-weight: 500;
  color: var(--text);
  text-decoration: none;
  cursor: pointer;
}

.user-menu__out.is-hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.content {
  flex: 1;
  padding: 24px 32px 32px;
}

/* App-shell rules live at the end of the file in @layer pages: they override
   per-page layout rules (.queries-layout, .filter-rail) that live in that
   later layer, where a rule here in components would lose regardless of
   specificity. */

/* ---------- Cards ---------- */

/* Padded body for card content that is not a table or a card-head — form
   panes, field lists. `.card` itself stays padding-free (tables run edge to
   edge), which left bare content flush against the border (#649). */
.card__body {
  padding: 14px;
}

/* An empty-state pane: breathing room between the lede and its call to
   action (the lede's own margin is 0 for the page head, where the section
   gap already separates it). */
.card__body > .page-head__lede {
  margin-bottom: 14px;
}

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

.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  margin-bottom: 24px;
}

.stat {
  padding: 16px 20px;
  min-height: 121px;
}

.stat__label {
  display: block;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.stat__value {
  display: block;
  margin-top: 10px;
  font-size: 34px;
  font-weight: 500;
  letter-spacing: -0.7px;
  color: var(--text);
}

/* The single accent moment on a stat tile — used sparingly (currently only
   the "up" state of the HTS dashboard Status tile). Reuses the accent token
   already tuned for AA contrast per theme, so no new palette values enter. */
.stat__value--ok {
  color: var(--accent-text);
}

.stat__unit {
  font-size: 17px;
  letter-spacing: -0.3px;
  vertical-align: 12px;
}

.stat__sub {
  display: block;
  margin-top: 6px;
  font-size: 12px;
  color: var(--muted);
}
}

@layer components {
.stat--link {
  display: block;
  color: inherit;
  text-decoration: none;
}

.stat--link.is-hover {
  border-color: var(--accent);
}

.stat--link:focus-visible {
  outline: 2px solid var(--outline);
  outline-offset: 2px;
}

.stat__value--unavailable {
  color: var(--muted);
}
}

@layer pages {
/* ---------- Chart card ---------- */

.chart-card {
  padding: 16px 20px 20px;
}

.chart-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}

.chart-card__tools {
  display: flex;
  gap: 8px;
}
}

@layer components {
.pill {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 42px;
  padding: 0 14px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  box-shadow: var(--surface-shadow);
  font-weight: 500;
  color: var(--muted);
}

/* A pill rendered as a real <button> (e.g. the chart-expand affordance) keeps
   the pill look but sheds the native button chrome and inherits type. */
button.pill {
  appearance: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
}

@layer pages {
.chart {
  width: 100%;
  height: auto;
  margin-top: 20px;
}

.chart .grid-line {
  stroke: var(--grid-line);
  stroke-width: 1;
  stroke-dasharray: 2 4;
}

.chart .series {
  stroke: var(--accent);
  stroke-width: 1.5;
  fill: none;
}

.chart .axis-label {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.6px;
  fill: var(--muted);
}

/* ---------- Chart time-window selector ---------- */

.window-picker {
  display: inline-flex;
  align-items: center;
  height: 42px;
  padding: 4px;
  gap: 4px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  box-shadow: var(--surface-shadow);
}

.window-picker__option {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding: 0 12px;
  border-radius: 8px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}

.window-picker__option.is-hover {
  color: var(--text);
}

.window-picker__option--active {
  background: var(--accent-soft);
  color: var(--text);
}

/* ---------- Chart legend / series selector ---------- */

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 16px 0 0;
  padding: 0;
  list-style: none;
}

.chart-legend__item {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 12px;
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  background: var(--surface);
  box-shadow: var(--surface-shadow);
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  text-decoration: none;
}

.chart-legend__item.is-hover {
  color: var(--text);
  border-color: var(--accent);
}

.chart-legend__item--active {
  color: var(--text);
  border-color: var(--accent);
  background: var(--accent-soft);
}

.chart-legend__total {
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ---------- Functional chart (#555): multi-series, picker, tooltip -------- */

/* Legend focus (#602): the focused line carries the weight, the rest recede
   but stay on the chart at their true values. */
.chart .series--focused { stroke-width: 3; }
.chart .series--receded { opacity: 0.25; }

/* The invisible twin polyline inside each line's focus anchor: a 14px hit
   corridor, since a 1.5px stroke is no click target. */
.chart .series-hit {
  stroke: transparent;
  stroke-width: 14;
  fill: none;
  pointer-events: stroke;
  cursor: pointer;
}

.chart-legend__item--focused {
  font-weight: 600;
  color: var(--text-strong);
}
.chart-legend__item--focused .chart-legend__dot {
  outline: 2px solid var(--text-strong);
  outline-offset: 1px;
}

/* A series whose history is still loading (#1603): only the buckets its
   counters recorded are drawn, dashed, and its current point is marked so a
   lone bucket is still visible. Nothing is drawn where nothing was measured. */
.chart .series--partial { stroke-dasharray: 6 4; }
.chart .series-end { stroke: none; fill: currentColor; }

/* Series palette: slot N styles the polyline, its end marker (via color) and
   its legend dot. */
.chart .series--1 { stroke: var(--series-1); color: var(--series-1); }
.chart .series--2 { stroke: var(--series-2); color: var(--series-2); }
.chart .series--3 { stroke: var(--series-3); color: var(--series-3); }
.chart .series--4 { stroke: var(--series-4); color: var(--series-4); }
.chart .series--5 { stroke: var(--series-5); color: var(--series-5); }
.chart .series--6 { stroke: var(--series-6); color: var(--series-6); }

.chart-legend__loading {
  font-size: 0.8em;
  color: var(--muted);
  font-style: italic;
}

.chart-legend__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
}

.chart-legend__dot--1 { background: var(--series-1); }
.chart-legend__dot--2 { background: var(--series-2); }
.chart-legend__dot--3 { background: var(--series-3); }
.chart-legend__dot--4 { background: var(--series-4); }
.chart-legend__dot--5 { background: var(--series-5); }
.chart-legend__dot--6 { background: var(--series-6); }

/* Type picker: the mockup's checkbox menu, as plain toggle links. */
.chart-pick summary.pill {
  cursor: pointer;
  max-width: 320px;
}

.chart-pick .menu__panel {
  min-width: 260px;
}

/* The option list scrolls on its own so the heading and the filter stay put
   however many types the tenant stores. */
.chart-pick__list {
  max-height: min(340px, 50vh);
  overflow-y: auto;
}

.chart-pick__option {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
}

.chart-pick__box {
  width: 16px;
  height: 16px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  color: #ffffff;
  border: 1.5px solid var(--surface-border);
  border-radius: 4px;
}

.chart-pick__option--on .chart-pick__box {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
}

/* "View all resources" (#599) sits above the filter input, set off from the
   scrolling option list below it by a divider. */
.chart-pick__option--all {
  padding-bottom: 8px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--divider);
}

.chart-pick__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chart-pick__count {
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.chart-pick__none {
  margin: 4px 8px 8px;
  font-size: 12px;
  color: var(--muted);
}

/* Tooltip host: the wrapper is the positioning context for the readout and
   the vertical guide the script moves along the buckets. */
.chart-wrap {
  position: relative;
  /* The svg scales its viewBox (text included) with rendered width; cap it so
     axis labels stay near their designed size on wide panes. */
  max-width: 1440px;
  margin: 0 auto;
}

.chart-tip {
  position: absolute;
  pointer-events: none;
  min-width: 150px;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  box-shadow: 0 8px 28px rgb(0 0 0 / 16%);
  z-index: 5;
  font-size: 12px;
}

.chart-tip__date {
  margin-bottom: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10.5px;
  color: var(--muted);
}

.chart-tip__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 1px 0;
}

.chart-tip__name {
  display: flex;
  align-items: center;
  gap: 7px;
}

.chart-tip__value {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.chart-guide {
  position: absolute;
  top: 0;
  width: 1px;
  background: var(--accent);
  opacity: 0.5;
  pointer-events: none;
}

.chart-empty {
  padding: 56px 16px;
  text-align: center;
  color: var(--muted);
  font-size: 13px;
}

/* The tabular alternative to the plot — same numbers, as text. */
.chart-table {
  margin-top: 12px;
}

.chart-table > summary {
  cursor: pointer;
  font-size: 12px;
  color: var(--muted);
  width: fit-content;
}

.chart-table > summary.is-hover {
  color: var(--text);
}

}
@layer components {
/* ---------- Tenant maintenance (/ui/tenants) ---------- */

.stat-grid--2 {
  grid-template-columns: repeat(2, 1fr);
  max-width: 620px;
}

/* Accessible sr-only helper — hides the element visually while keeping it
   in the accessibility tree. Referenced by row headings in the HTS
   dashboard partial and by outcome/import announcements. Without this rule
   those `<h2>`s would render at browser default size and break the
   visual hierarchy. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Progressive-enhancement utility (#843): hides an element that only works
   once JavaScript has actually run. `theme.js` marks `<html class="js">`
   from `<head>`, without `defer`, before first paint — so "js" here means
   "the earliest script ran", not "JavaScript is enabled" the way `[hidden]`
   above means "the server or a script says so".

   Generic on purpose — no page or container it needs to sit inside: the
   reveal rule below is `html.js .needs-js`, nothing narrower, so any page
   that renders a `.needs-js` element can rely on it regardless of what
   wraps that element. `display: flex` is this crate's one real use so far
   (View Definitions' guided-form card, a flex column) — a future consumer
   needing a different shown-state display overrides it with a selector at
   least as specific as this one (or later in the same layer), the same way
   `.editor__grid--stretch`'s own layout rules add flex-item properties
   (`flex`, `min-height`, ...) on top of this pair without touching
   `display` themselves. Not `!important` like `[hidden]` above: two
   declarations of ordinary weight, `display: none` and `display: flex`,
   let a more specific override win normally instead of requiring every
   future consumer to out-`!important` this utility too. */
.needs-js {
  display: none;
}

html.js .needs-js {
  display: flex;
}


.stat-grid--5 {
  grid-template-columns: repeat(5, 1fr);
}

@media (max-width: 1100px) {
  .stat-grid--5 {
    grid-template-columns: repeat(3, 1fr);
  }
}

.table-card {
  padding: 20px;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
}

/* Same scale as .card-head h3 — a toolbar is the in-card section header. */
.toolbar__title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-strong);
}

.toolbar__count {
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
}

/* A .toolbar__count inside a .btn (e.g. "Active Exports N") stays plain and
   matches the label color; only the gap separates it from the text (#608). */
.btn .toolbar__count {
  margin-left: 8px;
  color: inherit;
  font-variant-numeric: tabular-nums;
}

.toolbar__search {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  height: 42px;
  padding: 0 14px;
  background: var(--input-bg, var(--surface));
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  color: var(--muted);
}

.toolbar__search input {
  flex: 1;
  border: 0;
  background: none;
  outline: none;
  font-size: 15px;
  color: var(--text);
}

/* Add-tenant disclosure */
.addbox {
  position: relative;
}

.addbox > summary {
  list-style: none;
}

.addbox > summary::-webkit-details-marker {
  display: none;
}

.addbox__panel {
  position: absolute;
  right: 0;
  top: 50px;
  z-index: var(--z-dialog-panel);
  width: 340px;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 16px;
  box-shadow: 0 12px 40px rgb(0 0 0 / 12%);
}

.addbox__title {
  font-weight: 600;
  margin-bottom: 14px;
}

.field {
  display: block;
  margin-bottom: 14px;
}

.field__label {
  display: block;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  margin-bottom: 6px;
}

/* Required-field marker (#680): the `required` attribute on the input is the
   single source of truth, and this is the single point of flip. It reaches
   every dialog field (present and future) without touching a template —
   `.field` wraps `label > span.field__label + input.field__input`, and the
   search builder's URL input has no visible label of its own, so its marker
   lands on the query-builder tag chip instead. Empty alt text (the `/ ""`
   in `content`) keeps this purely visual: assistive tech already announces
   "required" from the input's `required` attribute, which stays in the
   markup. To change the convention later (e.g. an `(optional)` suffix
   instead of an accent asterisk on required fields), edit only this rule —
   swap `:required` for `:optional` and change `content` — never the
   templates. Child combinator (`> :required`) keeps bulk-import's
   `fieldset.field` (which nests its own `label.field` fields) from matching
   on a nested required input and marking the fieldset's legend instead. */
.field:has(> :required) > .field__label::after,
.query-builder__row:has(> .query-builder__url:required) > .query-builder__tag::after {
  content: " *" / "";
  color: var(--accent);
  font-style: normal;
}

.field__input {
  width: 100%;
  height: 40px;
  padding: 0 12px;
  box-sizing: border-box;
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  background: var(--input-bg, var(--surface));
  color: var(--text);
  /* Form controls do not inherit the page font by default — a bare textarea
     falls back to the UA's monospace (#685). */
  font: inherit;
  font-size: 15px;
}

/* Multi-line variant (#685): sized by its rows= within the panel, never able
   to widen the dialog. */
textarea.field__input {
  height: auto;
  min-height: 64px;
  padding: 8px 12px;
  resize: vertical;
}

.field__input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* SQL numeric parameters retain their native value, step and keyboard
   semantics. Only the JS-enhanced presentation removes OS spinner arrows. */
.js .field__input--number[type="number"] {
  appearance: textfield;
}
.js .field__input--number::-webkit-inner-spin-button,
.js .field__input--number::-webkit-outer-spin-button {
  appearance: none;
  margin: 0;
}

.field__input[aria-invalid="true"] {
  border-color: var(--danger);
}

.field__input[aria-invalid="true"]:focus {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-soft);
}

/* The native dropdown arrow hugs the border regardless of padding, so draw
   our own chevron with the same inset the text gets. The muted gray reads on
   both themes. */
select.field__input {
  appearance: none;
  padding-right: 36px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%23888888' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px;
}

.field__hint {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-top: 6px;
}

/* A radio option inside a `fieldset.field` (#1108): the control and its
   label text share the first row, and the hint sits on the second row
   indented under the text — not under the radio. The 16px control plus the
   10px gap match `.choice-card`, so the two radio treatments read alike. */
.field--choice {
  display: grid;
  grid-template-columns: 16px 1fr;
  column-gap: 10px;
  align-items: center;
  margin-bottom: 10px;
}

.field--choice > input[type="radio"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
}

.field__choice-label {
  color: var(--text);
}

.field--choice > .field__hint {
  grid-column: 2;
  margin-top: 2px;
}

.addbox__actions {
  margin-top: 4px;
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

/* Add Tenant dialog's own submit-error slot (#681 adenda): sits below the
   fields, above the action row (see #tenant-add-error in tenants.html and
   partials/tenant_add_error.html). `.alert`'s own margin is bottom-only
   (sized for a banner that precedes content, tenant_rows.html); scoped here
   by id, not on `.alert` itself, so this does not add a gap under every
   other alert use. The `.alert` selector, not the bare id, keeps the empty
   (no-error) container from reserving this space. */
#tenant-add-error.alert {
  margin-top: 16px;
}

.spinner,
.btn[aria-busy="true"]::after {
  /* Explicit border-box (#786): the `*` reset does not match pseudo-elements,
     and content-box would render the ::after ring at 18px instead of the
     14px glyph the standalone .spinner draws. */
  box-sizing: border-box;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid var(--outline);
  border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .spinner,
  .btn[aria-busy="true"]::after { animation: none; }
}

/* The busy button (#679), driven by busy.js: the label stays and the ring
   joins it as a trailing flex item, spaced by .btn's own gap (#1253). A
   blanked label left an empty pill that read as "the action vanished", not
   "working". Under reduced motion the ring remains as a static glyph (the
   animation stops above). */
.btn[aria-busy="true"] {
  cursor: progress;
}

.btn[aria-busy="true"]::after {
  content: "";
  flex: none;
}

/* A button busy.js can mark busy carries .btn--busy-slot, so the ring's
   room (14px ring + 6px gap) is there before the click and the button never
   changes width (#1253 review). At rest the room is split into two empty
   4px boxes, one each side (4 + 6 gap on both sides = 20), which keeps the
   label centered; busy drops the leading box and the trailing one becomes
   the ring. Without the class a busy button still works, it just grows. */
.btn--busy-slot::before,
.btn--busy-slot::after {
  content: "";
  flex: none;
  width: 4px;
}

.btn--busy-slot[aria-busy="true"]::before {
  display: none;
}

/* The ring must not inherit accent-on-accent on the filled button — the
   track all but vanishes in dark theme. Explicit white, not currentColor,
   so the ring does not follow the label's color. */
.btn--primary[aria-busy="true"]::after {
  border-color: rgb(255 255 255 / 45%);
  border-top-color: #ffffff;
}

/* Inline working state (#679): spinner plus a short label, role="status" in
   the markup so it announces. Also the tenants in-table provisioning row
   (#581), which polls in via the rows fragment until the job finishes. */
.busy-status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

/* The SQL live preview's "Running query…" line (#1750): while it shows, the
   previous run's labels ("last successful run", "Waiting for a value…") are
   hidden; the server's response repaints both when it lands. `!important`
   because `.card-head__meta`'s own `display` lives in the later `pages`
   layer, which outranks this one (the same reason `[hidden]` has it). */
.card-head__tools:has(> .run-busy:not([hidden])) > #run-results-meta,
body:has(.run-busy:not([hidden])) #run-notice > * {
  display: none !important;
}

/* The Raw CapabilityStatement — and each HTS workbench pane body (#898) —
   has one keyboard-focusable scroll owner. Every nested outline and shared
   JSON fragment remains overflow-visible inside it. */
#capability-json-body,
.capability-json-pane__body {
  max-height: 70vh;
  overflow: auto;
  scrollbar-gutter: stable;
  outline: none;
}

#capability-json-body:focus-visible,
.capability-json-pane__body:focus-visible {
  border-radius: 8px;
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.capability-json-outline {
  padding: 10px 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.6;
}

#capability-json-body .capability-json-outline,
.capability-json-pane__body .capability-json-outline {
  max-height: none;
  overflow: visible;
}

.capability-json-bracket,
.capability-json-row {
  min-height: 24px;
  padding-right: 10px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.capability-json-bracket { padding-left: 12px; }
.capability-json-rows { padding-left: 18px; }
.capability-json-row { display: flex; align-items: baseline; gap: 6px; }
.capability-json-row.is-hover { background: var(--input-bg); }
.capability-json-row code { font: inherit; min-width: 0; }

.capability-json-node > summary {
  cursor: pointer;
  list-style: none;
}

.capability-json-node > summary::-webkit-details-marker { display: none; }
.capability-json-row__fold { flex: 0 0 18px; color: var(--muted); }
.capability-json-node[open] > summary .capability-json-row__fold { transform: rotate(90deg); }
.capability-json-node__body {
  padding-left: 18px;
  overflow: visible;
}

.capability-json-truncated {
  color: var(--muted);
  font-family: Figtree, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 11px;
}

.capability-json-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 10px;
  font-family: Figtree, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}

/* Centered-popup variant of the addbox (the Bulk Import dialogs, #527): the
   same no-JS <details> mechanics, presented as a modal. While open, the
   summary itself stretches over the viewport as the backdrop, so clicking
   outside the panel closes the dialog without any script. */
.addbox--modal[open] > summary.btn {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog-backdrop);
  /* The summary doubles as the button (.btn/.btn--primary), whose fixed
     height would beat the inset stretch and shrink the backdrop to a bar
     across the top of the viewport. */
  height: auto;
  padding: 0;
  border: 0;
  background: var(--scrim);
  border-radius: 0;
  cursor: default;
  /* The summary's label is a text node, which `> *` cannot hide. */
  color: transparent;
}

.addbox--modal[open] > summary > * {
  visibility: hidden;
}

.addbox--modal[open] > .addbox__panel {
  position: fixed;
  top: 50%;
  left: 50%;
  right: auto;
  z-index: var(--z-dialog-panel);
  width: min(460px, calc(100vw - 40px));
  max-height: calc(100vh - 80px);
  overflow-y: auto;
  transform: translate(-50%, -50%);
}

.field__hint--error {
  color: var(--danger);
}

/* The one error treatment (#676): danger tokens, so it holds in both themes.
   Block by default; the inline variant sits next to the button that raised
   the error. */
.alert {
  display: block;
  margin: 0 0 12px;
  padding: 10px 14px;
  border: 1px solid var(--danger);
  border-radius: 10px;
  background: var(--danger-soft);
  color: var(--danger);
  font-size: 13px;
}

.alert--inline {
  display: inline-block;
  margin: 0;
  padding: 5px 10px;
}

.col-num {
  font-variant-numeric: tabular-nums;
}

.data-table .col-actions {
  text-align: right;
  width: 64px;
}

/* A leading checkbox column (SQL Export's subjects table, #834): just wide
   enough for the control, never stretching to carry column weight. */
.data-table .col-check {
  width: 32px;
}

.col-tenant {
  display: flex;
  align-items: center;
  gap: 12px;
}
}

@layer pages {
.tenant-id {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.tenant-id__name {
  font-weight: 500;
}

.tenant-id__slug {
  font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace;
  font-size: 12px;
  color: var(--muted);
}

/* The Tenants resources column (#1851) reserves room for its widest cell:
   a compact figure ("≈842.1K") or the presence label on storage that only
   reports which tenants hold data (de "enthält Daten", 13 characters). So a
   cell turning from "…" into its value never shifts the columns beside it. */
#tenant-rows .data-table .col-num {
  width: 14ch;
  white-space: nowrap;
}

/* A resources cell with something to say beyond its figure: pending,
   unknown, unsupported, unavailable, presence only, approximate or stale.
   The dotted underline marks the title that says which; screen readers hear
   it in place of the glyph. */
.count-note {
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  cursor: help;
}

.count-note--muted {
  color: var(--muted);
}

.count-note--error {
  color: var(--danger);
}

/* The count status line between the toolbar and the table (#1851): the
   counts' state, as-of time and provenance. Deliberately not .busy-status,
   which belongs to the provisioning rows. A reserved line height keeps the
   table still when its text changes. */
.counts-status {
  min-height: 20px;
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 20px;
  color: var(--muted);
}

.counts-status__refresh {
  margin-left: 8px;
}

}
@layer components {
.tag--muted {
  background: var(--accent-soft);
  color: var(--muted);
}

/* Failed provisioning job row (#581), matching .btn--danger's tokens. */
.tag--danger {
  background: var(--danger-soft);
  color: var(--danger);
}

.icon-button {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.icon-button.is-hover {
  background: var(--accent-soft);
  color: var(--text);
}

.icon-button--danger.is-hover {
  background: rgb(220 38 38 / 12%);
  color: #dc2626;
}

/* ---------- Narrow screens: stack the shell ---------- */

@media (max-width: 900px) {
  .stat-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
}

@layer pages {
/* ---------------------------------------------------------------------------
   History & Versions (#236, Figma "History & Versions").

   The version rail + two-layer diff. The textual layer cribs diff2html's
   visual language — paired lines, +/- gutters, per-side line numbers, and a
   word-level highlight of what changed within a line — without shipping any of
   its JavaScript. All server-rendered.
   --------------------------------------------------------------------------- */

.history__locate {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}

/* One combined "Type / id" reference field, like Brett's editor — the slash
   lives inside the field, not between two separate boxes. */
.history__ref {
  display: flex;
  align-items: center;
  height: 42px;
  padding: 0 4px;
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
}

.history__ref:focus-within {
  border-color: var(--accent);
}

.history__type,
.history__id {
  height: 38px;
  padding: 0 8px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: transparent;
  border: 0;
  outline: none;
}

.history__type {
  width: 120px;
  font-weight: 500;
}
.history__id { width: 200px; }
.history__slash { color: var(--muted); }

/* Tabs — one definition for both tab designs (history's underline feed
   switcher and the compartment viewer's pill links); the blocks used to be
   defined twice and render their cascade-merge, which this preserves. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--surface-border);
  padding: 8px;
}

.tab {
  padding: 8px 16px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
  border-radius: 9px;
  text-decoration: none;
}

.tab.is-hover { color: var(--text); }

.tab--on {
  color: var(--text-strong);
  border-bottom-color: var(--accent);
}

/* The Type Feed / System Feed panel (#1674). */
.history__feed {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-bottom: 12px;
}

.history__feed-more {
  display: flex;
  justify-content: center;
}

.history__split {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

/* ---- version rail ---- */

.history__rail {
  padding: 12px;
}

.history__rail-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-strong);
}

.history__rail-path {
  margin: 4px 0 10px;
  font-size: 11px;
  color: var(--muted);
}

.history__versions {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.history-version {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 34px;
  padding: 7px 9px;
  font: inherit;
  color: var(--text);
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 9px;
  cursor: pointer;
}

.history-version.is-hover { background: var(--input-bg); }
.history-version:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.history-version--current .history-version__id { color: var(--accent); }

/* The two versions being compared get a visible from/to marker. */
.history-version--from { border-color: var(--surface-border); }
.history-version--to {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.history-version__id {
  min-width: 2.5em;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.history-version__when {
  font-size: 11px;
  color: var(--muted);
}

.history-version__kind {
  margin-left: auto;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
}

/* ---- comparison controls ---- */

.history__controls {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}

.history__select {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
  --combobox-width: 88px;
  --combobox-height: 30px;
  --combobox-font-size: 13px;
}

.history__select select {
  height: 30px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
}

}
@layer components {
/* ---- Saved queries (#234) ---------------------------------------------- */

.page-head {
  margin-bottom: 20px;
}

.page-head--back-link {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "back-link ."
    "copy action";
  column-gap: 16px;
  align-items: start;
}

.page-head--back-link > .back-link {
  grid-area: back-link;
  justify-self: start;
}

.page-head--back-link > .page-head__copy {
  grid-area: copy;
  min-width: 0;
}

/* Vertical-rhythm variant for detail-page headers with eyebrow + title +
   optional lede + chips + kv-grid (HTS CodeSystem / ValueSet / ConceptMap).
   HFS pages with simpler title+lede keep the base `.page-head__copy`. */
.page-head__copy--spaced {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.page-head__copy--spaced > .page-head__title {
  margin-bottom: 0; /* gap handles spacing */
}
.page-head__copy--spaced > .facets--bare {
  padding-bottom: 6px; /* reduce from 12px; gap adds rest */
}
.page-head__copy--spaced > .kv-grid {
  margin-bottom: 0; /* final child; no trailing margin needed */
}

.page-head--back-link > .page-head__action {
  grid-area: action;
  min-width: 0;
  align-self: start;
}

/* #835: the job detail header's status row (status chip, contextual
   action, overflow) — a peer of .job-card__actions, right-aligned like it. */
.page-head__action {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 10px;
}

/* #835: the job detail's own vertical rhythm for the notice/progress/cards
   region. That content lives inside #job-detail, a descendant of .content
   rather than a direct child of it, so the site-wide
   .content > .card ~ .card spacing rule ("Stacked section cards keep the
   page's vertical rhythm", further down this file) never reaches it. */
.job-detail-stack {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.page-head__title {
  margin: 0 0 4px;
  font-size: 24px;
  font-weight: 600;
  color: var(--text-strong);
}

.page-head__lede {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

/* #1184: give the rebuild notice a small gap above it so it reads as its own
   element, not a continuation of the lede. Scoped to `.page-head` so the shared
   `.notice` rule (used flush inside cards) is untouched, and so the dashboard's
   in-chart notice keeps its own spacing. */
.page-head [data-rebuild-notice] {
  margin-top: 12px;
}

.back-link,
.back-link:visited {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 24px;
  font-size: 13px;
  color: var(--accent-text);
  text-decoration: none;
}

.back-link.is-hover {
  text-decoration: underline;
}

.back-link:focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 3px;
  border-radius: 4px;
}
}

@layer pages {
.query-form-card {
  padding: 20px;
  margin-bottom: 24px;
}
}

@layer components {
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 12px;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 9px;
  box-shadow: var(--surface-shadow);
  cursor: pointer;
  text-decoration: none;
}

.btn.is-hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

/* The app's own focus indicator (#682): keyboard focus gets the accent ring,
   and a mouse click never leaves the UA's default ring behind — buttons and
   the <summary class="btn"> dialog toggles alike. */
.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.btn:focus:not(:focus-visible) {
  outline: none;
}

.btn--primary {
  color: #ffffff;
  background: var(--accent-strong);
  border-color: var(--accent-strong);
}

.btn--primary.is-hover {
  background: var(--accent-strong);
  filter: brightness(1.08);
}

.btn--icon {
  width: 30px;
  padding: 0;
  justify-content: center;
}

.btn:disabled {
  opacity: 0.6;
  cursor: progress;
}
}

@layer pages {
.query-group__type {
  margin: 0 0 4px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
}

/* Shared empty-state text used by the HTS UI, independent of legacy Queries. */
.query-empty {
  margin: 8px 4px;
  font-size: 13px;
  color: var(--muted);
}

/* Search Builder (hi-fi console design): featured editable GET URL + Run,
   with the Recent dropdown on the same row. */

.query-builder {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 12px;
}

.query-builder__row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.query-builder__url {
  flex: 1;
  min-width: 0;
  height: 38px;
  padding: 0 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
}

.history__arrow { color: var(--muted); }

.history__meta-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  font-size: 12px;
  color: var(--muted);
}

.history__empty {
  padding: 24px;
  font-size: 13px;
  border-radius: 10px;
  outline: none;
}

.query-builder__url:focus {
  border-color: var(--accent);
}

.query-recent__toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.query-recent__toggle .icon {
  color: inherit;
}

}
@layer components {
.menu__panel--right {
  left: auto;
  right: 0;
  width: 380px;
  max-width: 90vw;
}

/* Short action and sort menus keep the shared popover behavior without
   inheriting the account/recent-search panel width. */
.menu__panel--compact {
  width: max-content;
  min-width: 140px;
  padding: 8px;
}

.menu__panel--compact .menu__option {
  white-space: nowrap;
}
}

@layer pages {
.recent-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  min-height: 34px;
  border-radius: 8px;
}

.recent-item.is-hover {
  background: var(--accent-soft);
}

.recent-item__query {
  flex: 1;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  color: var(--text);
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

.recent-item__when {
  flex-shrink: 0;
  font-size: 10.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.recent-item__del {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.recent-item__del.is-hover {
  background: var(--accent-soft);
  color: var(--text-strong);
}

.recent-empty {
  margin: 0;
  padding: 10px 8px;
  font-size: 12px;
  color: var(--muted);
  text-align: center;
}

/* ---- banners (deleted / identical / error) ---- */

.history__banner {
  padding: 14px 16px;
  font-size: 13px;
  border-radius: 12px;
  border: 1px solid var(--surface-border);
}

.history__banner--deleted {
  color: var(--text);
  background: var(--input-bg);
  border-color: var(--muted);
}

.history__banner--same { color: var(--muted); }

.history__banner--error {
  background: var(--danger-soft);
  border-color: var(--danger);
  color: var(--danger);
}

/* ---- the diff ---- */

.diff__head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}

.diff__head h3 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 14px;
  color: var(--text-strong);
}

@media (max-width: 900px) {
  .query-builder__row {
    flex-wrap: wrap;
  }

  .query-builder__url {
    flex-basis: 100%;
  }

  .menu__panel--right {
    right: auto;
    left: 0;
  }

  .toolbar .menu__panel--right.menu__panel--compact {
    right: 0;
    left: auto;
  }
}

}
@layer components {
/* ---- Registry viewers: shared filter layout (#237/#238) ----------------- */

.notice {
  margin: 0 0 16px;
  padding: 10px 14px;
  font-size: 13px;
  color: var(--muted);
  border-radius: 10px;
}

.notice--warn {
  color: var(--text);
  background: var(--warn-soft);
  border: 1px solid var(--warn);
}

/* The shell's own notice (#1560), between the topbar and the page: aligned
   with the content gutter, the page's top padding is the gap below it. Styled
   like .notice--warn but on its own class, so page-level warning counts and
   locators never see the shell. */
.shell-notice {
  margin: 24px 32px 0;
  padding: 10px 14px;
  font-size: 13px;
  color: var(--text);
  background: var(--warn-soft);
  border: 1px solid var(--warn);
  border-radius: 10px;
}

/* The HTS OperationOutcome box (`crates/hts-ui/templates/partials/
   hts-outcome.html`, #805). It is a `.notice` whose body is a stack of
   `<p>` rows, which the primitive alone cannot spell: HFS's own notices
   put bare text in the box, so `.notice` carries no paragraph reset and
   the UA's `margin: 1em 0` pushed the first line ~13px below a box padded
   for 10px. Scope the rhythm here rather than touching `.notice`, which is
   shared with every HFS warning surface. */
.hts-outcome > * {
  margin: 0;
}

.hts-outcome > * + * {
  margin-top: 6px;
}

/* The box follows result content — the `$validate-code` field grid is
   pinned flush on the assumption it is the panel's last child. The margin
   collapses against a preceding notice's 16px, so stacked warnings stay
   16px apart rather than 30. */
.hts-outcome:not(:first-child) {
  margin-top: 14px;
}

/* Last child of a `.panel` / `.card`: the container's 18px padding is the
   gap, and the primitive's 16px on top of it reads as a doubled band. */
.hts-outcome:last-child {
  margin-bottom: 0;
}

/* FHIRPath locations. Previously borrowed `.filter-rail__list`, the
   scrolling type-rail container, which indented them 40px (its rules never
   touch the UA list padding) inside a needless scroll region. */
.hts-outcome__locations {
  display: grid;
  gap: 2px;
  padding-left: 0;
  list-style: none;
}

/* 280px matches .nav-panel and .queries-layout's rail column (#603 follow-up):
   one rail width across Resources, Search, and Search
   Parameters. */
.filter-layout {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr) 360px;
  gap: 20px;
  align-items: start;
}

/* Stacked section cards keep the page's vertical rhythm. Pages that lay
   cards out in a flex/grid column get their spacing from the gap; this
   covers the block-flow stacks (SQL Export, Files, Capability Statement,
   the Bulk Import detail), where .card alone sat flush against the previous
   one. The general sibling, not the adjacent one: htmx wrapper divs sit
   between cards on the Bulk Import detail. */
.content > .card ~ .card,
.export-jobs > .card ~ .card,
.content > .card ~ .export-jobs > .card:first-child {
  margin-top: 20px;
}

.filter-layout--two {
  grid-template-columns: 280px minmax(0, 1fr);
}

.version-switch {
  display: flex;
  gap: 4px;
  margin-top: 10px;
}

.version-switch a {
  padding: 4px 10px;
  border: 1px solid var(--outline);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
}

.version-switch a[aria-current="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text-strong);
}

/* Resource Filter rail (shared primitive; the Resources screen reuses it). */

.filter-rail {
  position: sticky;
  top: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: calc(100vh - 110px);
  padding: 14px;
}

.filter-rail__search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px;
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  color: var(--muted);
}

.filter-rail__search input {
  flex: 1;
  height: 34px;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  outline: none;
}

.filter-rail__heading {
  margin: 6px 4px 0;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

/* Sub-group labels inside the list ("Recently used", "All types") read as
   section labels, not a fainter echo of the panel's own heading (#603
   follow-up: they camouflaged against the items below them). */
.filter-rail__heading--group {
  color: var(--text);
  font-weight: 700;
}

/* The Recently used heading sits inside its group, so the group's 2px item
   gap is all that separates it from the first entry; the All Types heading
   is a rail child and gets the rail's 10px. Top up to the same 10px (#785). */
.filter-rail__group > .filter-rail__heading--group {
  margin-bottom: 8px;
}

/* Between the Recently used group and the general list. Hidden by default —
   shown only when it directly follows a *populated* (non-`hidden`) recent
   group. The group is server-rendered and `hidden` when it has no entries,
   including when the rail filter leaves none, so the divider follows it. */
.filter-rail__divider {
  display: none;
  height: 1px;
  margin: 8px 4px;
  background: var(--divider);
}

.filter-rail__group:not([hidden]) + .filter-rail__divider {
  display: block;
}

.filter-rail__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow-y: auto;
  /* Breathing room between the counts and the scrollbar pill. */
  padding-right: 6px;
}

/* The list scrolls; its children keep their natural size. Without this the
   flex algorithm crushes shrinkable rows before overflowing — the empty
   1px divider collapses to 0 height the moment the list becomes a
   height-bound scroll region. */
.filter-rail__list > * {
  flex: none;
}

.filter-rail__group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.filter-rail__item {
  /* WCAG 2.2 target-size floor - font fallback must not shrink rows below it. */
  min-height: 24px;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  padding: 7px 10px;
  border-radius: 9px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  text-decoration: none;
}

/* Long resource type names (e.g. "MedicinalProductContraindication", 32
   chars) truncate with an ellipsis instead of colliding with the count or
   forcing horizontal scroll on the rail. `title` is the no-JS fallback;
   resource-filter.js replaces it with a visible hover/keyboard tooltip when
   the label is actually truncated (#604, #634). */
.filter-rail__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* The right-hand meta of a rail item — the resource type on the SQL rails
   (#649, Figma 420-2): quiet right-aligned text, not a count pill. */
.filter-rail__meta {
  margin-left: auto;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 400;
  color: var(--muted);
}

/* The JSON / SQL editor pane (#649): the design's inner inset box — mono at
   an explicit size, generous height, vertical resize only. */
.json-editor {
  display: block;
  width: 100%;
  min-height: 320px;
  padding: 14px;
  box-sizing: border-box;
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  background: var(--input-bg, var(--surface));
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.5;
  resize: vertical;
}

/* Right-aligned actions row at the foot of a form pane. */
.form-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}

/* Paired editors keep Cancel, Save and the dirty cue within their card. */
#vd-editor-form .form-actions,
#lib-editor-form .form-actions {
  flex-wrap: wrap;
}

/* The Library pages' preview notice sits inside the SQL card, between the
   editor and the Save row. Its empty states (nothing has run, or the load
   shell) take no space; the notice itself drops its own bottom margin. */
#lib-editor-form #run-notice {
  margin-top: 12px;
}

#lib-editor-form #run-notice:empty {
  display: none;
}

#lib-editor-form #run-notice > .notice {
  margin: 0;
}

/* The "nothing to export yet" card's actions (SQL Export, #834): left-aligned
   like the rest of that card's body copy, not trailing off to the right. */
.form-actions--start {
  justify-content: flex-start;
}

/* Shared labelled-value compositions. The flush modifier sets the value that
   the existing key/value grid consumes in the later page layer, so component
   ownership does not depend on !important or layer-breaking specificity. */
:where(.detail__field) {
  min-width: 0;
}

:where(.detail__field) > div {
  overflow-wrap: anywhere;
}

.detail-stack {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.detail-stack > .form-actions {
  margin-top: 0;
}

.kv-grid--flush {
  --kv-grid-margin-bottom: 0;
}

.json-editor:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* The collapsible JSON fold's summary rides .card-head; the chevron flips
   when the fold opens, like the app's other disclosures. #839 retired
   `sql-library.html`'s own `.json-fold` (the SQL pane's JSON pane), but the
   class is still load-bearing for the CapabilityStatement raw-JSON card
   (`crates/ui-chrome/templates/partials/capability-raw-card.html`), so the
   rule stays. */
details.json-fold > summary {
  cursor: pointer;
  list-style: none;
}

details.json-fold > summary::-webkit-details-marker {
  display: none;
}

details.json-fold[open] > summary .icon {
  transform: rotate(180deg);
}

.filter-rail__item.is-hover {
  background: var(--accent-soft);
}

.filter-rail__item[aria-current="true"] {
  background: var(--accent-soft);
  color: var(--text-strong);
  font-weight: 600;
}

.filter-rail__item .count {
  flex: none;
  margin-left: auto;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* An estimated rail count ("≈1234"): its title explains the estimate, so
   the cursor advertises the tooltip. */
.count--approximate {
  cursor: help;
}

/* Appended to <body> and positioned from the focused/hovered truncated-name
   item so the full label is not clipped by rail or grid overflow. One shared
   tooltip serves rails, cloneNode()-based Recent items, and Bulk Export. */
.filter-rail__tooltip {
  position: fixed;
  z-index: 1000;
  max-width: min(320px, calc(100vw - 16px));
  padding: 6px 9px;
  border: 1px solid var(--popover-border);
  border-radius: 8px;
  background: var(--popover);
  box-shadow: var(--popover-shadow);
  color: var(--text-strong);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  overflow-wrap: anywhere;
  pointer-events: none;
}

.filter-rail__tooltip[hidden] {
  display: none;
}

/* The rail's own empty message ("No matches for …", "No … yet") reads as
   body text, not as one of the rail's group headings (#1780). */
.filter-rail__empty {
  margin: 6px 4px 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}

.filter-rail__note {
  margin: 8px 4px 0;
  padding-top: 10px;
  border-top: 1px solid var(--surface-border);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--muted);
}

.filter-center {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

/* HTS Phase-5 filter-rail field group: label above a row of
   mode-select + text-input. Input skin comes from
   `.builder-row__value` / `.builder-row__modifier`; rules below
   only handle the stacked layout and the sticky action row. */
.filter-rail__field { display: flex; flex-direction: column; gap: 4px; padding: 0 4px; }
.filter-rail__field-label { margin: 0 2px; font-size: 11px; font-weight: 600; color: var(--muted); }
.filter-rail__field-row { display: flex; gap: 6px; align-items: center; }
.filter-rail__field-row .builder-row__value { flex: 1; min-width: 0; }
.filter-rail__actions {
  display: flex; gap: 8px; padding: 10px 4px 0; margin-top: 4px;
  border-top: 1px solid var(--surface-border);
}
/* HTS browsers: bound Name column so long computer-friendly names
   (`hl7.terminology.r4.CodeSystem-*`) don't crowd Title. */
.data-table .col-name { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* HTS ConceptMap stacked source/target cell (Phase-5 §7.5 Gate 1):
   two vertically-stacked lines with a monospace `S:` / `T:` prefix
   so the mapping direction stays scannable in-row. */
.cm-mapping { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cm-mapping__line { display: flex; gap: 6px; align-items: baseline; min-width: 0; }
.cm-mapping__prefix { font-size: 11px; font-weight: 600; color: var(--muted); flex: none; }
.cm-mapping__line .url { min-width: 0; }

/* Facet chip rows */

.facets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  padding: 12px 14px;
}

.facets--bare {
  padding: 0 0 12px;
}

.facet-label {
  margin-right: 4px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 10px;
  border: 1px solid var(--surface-border);
  border-radius: 999px;
  font-size: 12px;
  color: var(--text);
  text-decoration: none;
}

.chip.is-hover {
  border-color: var(--accent);
}

.chip[aria-current="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text-strong);
  font-weight: 600;
}

.chip .count {
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* A native radio/checkbox carries a UA margin that .chip's
   align-items: center centers by margin box, not visible box — the
   control lands a couple of px below the chip's vertical center (#804).
   Same reset .choice-card already applies to its radios. */
.chip input[type="radio"],
.chip input[type="checkbox"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
}

/* Data table */

.table-wrap {
  overflow-x: auto;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.data-table th {
  padding: 12px 14px 8px;
  border-bottom: 1px solid var(--surface-border);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  text-align: left;
  color: var(--muted);
}

.data-table td {
  padding: 9px 14px;
  border-bottom: 1px solid var(--surface-border);
  color: var(--text);
  text-align: left;
  vertical-align: top;
}

.data-table tbody tr:last-child td {
  border-bottom: 0;
}

.data-table .data-table__empty td,
.empty-state {
  margin: 0;
  color: var(--muted);
  text-align: center;
  padding: 32px 12px;
}


/* Merge of two independent intents, 2026-08-28:
   - main excluded the empty-state row from hover highlighting, so "no
     results" no longer lights up under the cursor;
   - this branch added `aria-current`, the convention every other selected
     element in this file already uses (.chip, .tab, .nav-item,
     .filter-rail__item), so a selected row paints like everything else.
   They touch different selectors in the same list; both are kept. */
.data-table tbody tr:not(.data-table__empty):not(.row--params).is-hover td,
.data-table tbody tr[aria-selected="true"] td,
.data-table tbody tr[aria-current="true"] td {
  background: var(--accent-soft);
}

.data-table[data-row-navigation] tbody tr {
  cursor: pointer;
}

.row-link {
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: inherit;
  text-decoration: none;
}

.row-link .code,
.data-table .code {
  font-weight: 600;
  color: var(--text-strong);
}

.url {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* Result id chip (#1106): long ids never wrap past one line — the label
   shows an 8-character prefix and the full id stays the link's accessible
   name (see .visually-hidden usage in search-builder.js). */
.query-results__table .col-id {
  white-space: nowrap;
  vertical-align: middle;
}

.query-results__table .result-id {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  height: 20px;
  padding: 0 7px;
  border-radius: 6px;
  background: var(--muted-soft);
  color: var(--muted-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  white-space: nowrap;
  text-decoration: none;
}

.query-results__table .result-id.is-hover {
  color: var(--text-strong);
}

.query-results__table .result-id:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Visible id text inside the chip (#1106): the rest of an abbreviated id
   lives in a sibling .visually-hidden node, so this span carries the id's
   own no-wrap contract rather than relying on the chip around it. */
.query-results__table .result-id__text {
  white-space: nowrap;
  overflow-wrap: normal;
}

/* Copy-id button and its "Copied" confirmation pill (#1106), clipboard-gated
   so a browser without the Clipboard API never renders either. */
.result-id-group {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.result-id__copy {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

tr.is-hover .result-id__copy,
.result-id__copy.is-hover,
.result-id__copy:focus-visible {
  border-color: var(--surface-border);
  background: var(--surface);
  color: var(--text);
}

.result-id__copy:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.result-id__copied {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 20px;
  padding: 0 7px;
  border-radius: 999px;
  background: var(--ok-soft);
  color: var(--ok);
  font-size: 11px;
  font-weight: 600;
}

/* Single-line result cells (#1106): every data cell clips at the same width
   as `.data-table .col-name` so rows stay a uniform height regardless of
   content length; the full value surfaces in the shared tooltip
   (resource-filter.js) on hover and, for the id, on keyboard focus. */
.query-results__table td {
  vertical-align: middle;
}

.query-results__table .result-cell {
  display: block;
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.query-results__table .result-cell.url {
  overflow-wrap: normal;
}

.table-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  font-size: 12px;
  color: var(--muted);
}

.pagination {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.btn--current {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text-strong);
}

/* Status tags (small pills). Distinct from .pill, the large control chip. */

.tag {
  display: inline-flex;
  margin-left: 8px;
  vertical-align: middle;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
}

.tag--type {
  background: var(--accent-soft);
  color: var(--text);
}

.tag--param {
  background: var(--accent-soft);
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-weight: 500;
}

.tag--embedded {
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  color: var(--muted);
}

.tag--stored,
.tag--member,
.tag--matched {
  background: var(--ok-soft);
  color: var(--ok);
}

.tag--config,
.tag--overrides,
.tag--shadowed {
  background: var(--warn-soft);
  color: var(--warn);
}

.tag--conflict,
.tag--excluded {
  background: var(--danger-soft);
  color: var(--danger);
}

/* FHIR publication-status tags (HTS browsers + resource detail headers).
   Distinct from the .tag--stored/config/conflict family: those describe
   configuration/lint outcomes, these describe a resource's own status. */
.tag--active {
  background: var(--ok-soft);
  color: var(--ok);
}

.tag--draft {
  background: var(--warn-soft);
  color: var(--warn);
}

/* Unsaved-changes pill (#1240), same tokens as .tag--draft. */
.tag--unsaved {
  background: var(--warn-soft);
  color: var(--warn);
}

.tag--retired {
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  color: var(--muted);
}

.tag--unknown {
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  color: var(--muted);
}

/* Subscription states (#580, reshaped to the frame in #767): the cards and
   the STATUS column share a dot vocabulary — green delivers, amber has no
   listeners, red needs attention. */
.status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 7px;
  border-radius: 50%;
  vertical-align: baseline;
}

.status-dot--ok {
  background: var(--ok);
}

.status-dot--danger {
  background: var(--danger);
}

.status-dot--warn {
  background: var(--warn);
}

.status-dot--muted {
  background: var(--muted);
}

/* A failing row carries the frame's red left edge. */
.data-table tr.row--alert > td:first-child {
  box-shadow: inset 3px 0 0 var(--danger);
}

/* #837: the SQL Export builder's parameter values row, right under a
   parameterized SQL Query subject's own `<tr>` — the "client's notebook" of
   values the checked row needs before it can run. Shaded off the plain row
   background so the boundary between "picking a subject" and "filling in
   its values" reads at a glance, without the weight of a border. */
.row--params {
  background: var(--input-bg);
}

.row--params td {
  padding: 10px 14px 12px;
}

/* Field grid inside a values row (#837): plain `.field` voice, three per
   row, no icon. `.field` already carries its own bottom margin for stacked
   forms; inside this grid every field sits in its own column instead, so
   that margin only adds uneven whitespace. */
.param-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  max-width: 720px;
}

.row--params .field {
  margin: 0;
}

/* The values row's expand/collapse chevron (#837), in the Subject cell of a
   parameterized SQL Query — sql-export-form.js reveals it (server-rendered
   `hidden`) only once its row is checked, then toggles `aria-expanded` on
   click. The icon itself rotates purely off that attribute, matching
   `.json-line__arrow`'s own collapsed-state rotation. */
.row-toggle {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  vertical-align: middle;
}

.row-toggle.is-hover {
  color: var(--text);
}

.row-toggle .icon {
  display: inline-flex;
  transition: transform 0.15s ease;
}

.row-toggle[aria-expanded="false"] .icon {
  transform: rotate(-90deg);
}

/* The folded values row's own chip summary (#837), next to the chevron:
   `sql-export-form.js` fills it with one `.tag--param`/`.tag--danger` chip
   per parameter while the row is collapsed, and empties/hides it again on
   expand. */
.param-summary {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  vertical-align: middle;
}

/* One undeclared-placeholder hint (#841's Parameters card, `.lib-params-
   hint`): the ":name is used but not declared" text and its own one-click
   *Declare* button, side by side — an informational row, not a `.notice`
   (this is expected while a query is still being written, never an error),
   so it gets its own quieter surface instead of `.notice--warn`'s. */
.lib-params-hint {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  margin: 0 0 8px;
  font-size: 13px;
  color: var(--text);
  background: var(--bg-content);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
}

.lib-params-hint:last-of-type {
  margin-bottom: 0;
}

/* The `Add parameter` panel's own name/type row (#841) — plain `.field`s
   side by side, unlike `.param-grid`'s fixed three-up layout (built for a
   *values* row that can hold many, not this always-exactly-two form). */
.lib-params-add__fields {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

.lib-params-add__fields .field {
  flex: 1 1 160px;
  margin-bottom: 0;
}

/* The Tables card (#842), redesigned as a flat list (#1238): a `<table>`
   with three fixed columns never fit the left-hand track of `.editor__
   grid`'s two `minmax(0,1fr)` tracks, forcing `.table-wrap`'s own
   horizontal scrollbar at ordinary widths. `ul.lib-tables__list` wraps
   instead, so a row's own alias/chip/link/remove reflow onto more than
   one line rather than clipping or scrolling. The always-visible add row
   (`.lib-tables__add`) reuses `combobox.html`'s shared picker unmodified —
   its own `legend` (`.field__label`) is hidden here, from the card's own
   CSS, the same way `.visually-hidden` hides one (#1238's decision 3: no
   other caller of that macro wants this treatment, so the macro itself
   stays untouched); the macro's own `hint`/`fallback-hint` spans are
   always passed empty by this card and `:empty` hides them, since the
   card supplies its own single help line instead. */
.lib-tables__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.lib-tables__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--surface-border);
  font-size: 13px;
  min-width: 0;
}

.lib-tables__row:last-child {
  border-bottom: 0;
}

/* Same failing-row frame as `.data-table tr.row--alert > td:first-child`
   (above), adapted to a row with no cells. */
.lib-tables__row.row--alert {
  box-shadow: inset 3px 0 0 var(--danger);
  padding-left: 10px;
}

.lib-tables__alias {
  min-width: 64px;
}

.lib-tables__target {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 140px;
  min-width: 0;
}

.lib-tables__target .tag {
  margin-left: 0;
}

.lib-tables__target a {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lib-tables__remove {
  margin-left: auto;
}

.lib-tables__declare {
  margin-left: auto;
}

.lib-tables__detail {
  flex-basis: 100%;
  margin: 0;
}

.lib-tables__add {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding-top: 10px;
}

.lib-tables__add .combobox {
  flex: 1 1 150px;
  margin-bottom: 0;
}

/* The picker's own `legend` (`combobox.html`'s `.field__label`) carries no
   visible label in this card — the search field's own placeholder already
   says what it is, and there is no room for a second line of label text
   next to the alias/Add controls. Hidden here, from this card's own CSS,
   rather than in the shared macro every other caller still needs it in. */
.lib-tables__add .combobox > .field__label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.lib-tables__add .combobox .field__hint:empty {
  display: none;
}

.lib-tables__add .field__input {
  height: 36px;
  font-size: 14px;
}

.lib-tables__alias-input {
  flex: 0 1 96px;
}

/* The *Add* button keeps its own canonical 30px `.btn--primary` height
   (`design-system.spec.ts`'s own "emphasis variants cannot declare
   geometry" guard, #543) rather than matching the two 36px fields beside
   it — `align-items: center` on `.lib-tables__add` still keeps it centered
   in the row. */

.lib-tables__add > .field__hint {
  flex-basis: 100%;
  margin-top: 0;
}

.lib-tables__add > #lib-tables-add-error {
  flex-basis: 100%;
}

/* The Last 24 hrs sparkline (#782): the engine's half-hour delivery series,
   scaled server-side; color follows the row's state — the frame's dotted
   amber marks a listener-less websocket. */
.spark {
  display: block;
  width: 120px;
  height: 24px;
}

.spark__line {
  fill: none;
  stroke: var(--muted);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.spark--active .spark__line {
  stroke: var(--ok);
}

.spark--error .spark__line {
  stroke: var(--danger);
}

.spark--idle .spark__line {
  stroke: var(--warn);
  stroke-dasharray: 2 3;
}

.spark--requested .spark__line,
.spark--off .spark__line {
  stroke: var(--muted);
}

/* Bulk Export job states (#537) — the same status-pill vocabulary. */
.tag--in-progress {
  background: var(--warn-soft);
  color: var(--warn);
}

.tag--complete {
  background: var(--ok-soft);
  color: var(--ok);
}

.tag--failed {
  background: var(--danger-soft);
  color: var(--danger);
}

.tag--cancelled {
  background: var(--muted-soft);
  color: var(--muted-text);
}

.subs-sort summary.btn {
  list-style: none;
}

.subs-sort summary.btn::-webkit-details-marker {
  display: none;
}

.subs-sort__label {
  color: var(--muted);
  font-size: 9px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.subs-sort__chevron {
  transition: transform 120ms ease;
}

.subs-sort[open] .subs-sort__chevron {
  transform: rotate(180deg);
}
}

@layer pages {
/* SQL Queries / SQL Views title row (#839): the selected Library's type
   icon and its two chips (type, status) sit inline with its name inside
   `.page-head__title` (View Definitions reuses it for its icon alone) —
   no other page nests an icon or a chip in this heading class, so the flex/gap treatment `.card-head h3` already gives
   its own inline icon is scoped here as a modifier instead of changing the
   shared rule every other page's title also uses. */
.page-head__title--kind {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

/* Bulk Import detail: consistent rhythm between its non-empty cards. */
.bulk-import-section {
  margin-bottom: 16px;
}

/* The processing card's Abort row: .detail__actions carries a 10px bottom
   margin that doubled up with the card padding (#789). */
#bulk-status .detail__actions {
  margin-bottom: 0;
}

/* The processing card's progress bar. Determinate when the recipient
   reports a percentage; indeterminate (sweeping) while only the entry
   counter moves. */
.progress-track {
  overflow: hidden;
  position: relative;
  height: 6px;
  margin: 8px 0 6px;
  border-radius: 999px;
  background: var(--accent-soft);
}

.progress-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  transition: width 400ms ease;
}

.progress-track--indeterminate .progress-fill {
  position: absolute;
  width: 30%;
  animation: progress-sweep 1.6s ease-in-out infinite;
}

@keyframes progress-sweep {
  0% {
    left: -30%;
  }

  100% {
    left: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .progress-track--indeterminate .progress-fill {
    animation: none;
    left: 0;
    width: 100%;
    opacity: 0.5;
  }
}

.bulk-import-summary-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 18px 24px;
  align-items: start;
}

.bulk-import-summary__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  margin: 0;
}

.bulk-import-summary__actions > form,
.bulk-import-summary__actions > details {
  margin: 0;
}

/* Advanced-options fold in the create dialog: a native disclosure whose
   chevron flips while open, matching the sort menus' affordance. */
.disclosure {
  margin: 14px 0;
}

/* #806: the HTS folds adopting this pattern sit directly in a `.panel`,
   usually as the card's only child. `.panel`'s own 18px padding already
   spaces them, and a panel is a plain block — so the margin above cannot
   collapse into anything and would simply make every collapsed card 28px
   taller. The dialog folds this pattern was written for are not in a
   `.panel`, so they keep the default. */
.panel > .disclosure {
  margin: 0;
}

.disclosure__summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
  list-style: none;
}

.disclosure__summary::-webkit-details-marker {
  display: none;
}

.disclosure__summary.is-hover {
  color: var(--text);
}

.disclosure__chevron {
  transition: transform 120ms ease;
}

.disclosure[open] .disclosure__chevron {
  transform: rotate(180deg);
}

.disclosure__body {
  padding-top: 8px;
}

.bulk-import-section button:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

@media (max-width: 900px) {
  .bulk-import-summary-card {
    grid-template-columns: minmax(0, 1fr);
  }

  .bulk-import-summary__actions {
    justify-content: flex-start;
  }
}

@media (max-width: 520px) {
  .bulk-import-manifests-card {
    padding-top: 14px;
  }

  .bulk-import-manifest-row {
    gap: 7px;
    padding: 8px;
  }
}

/* Detail panel (right rail on the SearchParameter viewer) */

.detail {
  position: sticky;
  top: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-height: calc(100vh - 110px);
  padding: 16px;
  overflow-y: auto;
}

/* Full-width form sections (bulk export): the flex/gap/padding of .detail
   without its sticky sidebar behavior (#608). */
.form-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
}

/* Vertical gap between the stacked .form-panel/table-card sections of the
   bulk export form (#608). */
.bulk-export-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* The Group ID field only applies to the Group scope; hide it otherwise.
   Browsers without :has() keep it always visible — today's behavior. */
.bulk-export-form .field--group-id {
  display: none;
}

.bulk-export-form:has(input[name="scope"][value="group"]:checked) .field--group-id {
  display: block;
}

/* HTS CodeSystem $validate-code: show only the fields for the selected
   input mode (#804), same CSS-only reveal idiom as the Bulk Export scope
   field above. Browsers without :has() keep both halves visible — today's
   (buggy) fallback behavior. */
#hts-workbench-input .validate-mode-panel--coding {
  display: none;
}

#hts-workbench-input:has(input[name="mode"][value="coding"]:checked) .validate-mode-panel--code {
  display: none;
}

#hts-workbench-input:has(input[name="mode"][value="coding"]:checked) .validate-mode-panel--coding {
  display: block;
}

.detail__title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 14px;
  color: var(--text-strong);
}

.diff__meta-note {
  font-size: 11px;
  color: var(--muted);
}

/* Semantic layer: the field-level change list. */
.diff__semantic {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 14px;
}

.diff-change {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 5px 10px;
  font-size: 12px;
  border-radius: 8px;
  background: var(--input-bg);
}

.diff-change__op {
  min-width: 58px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
}

.diff-change--add .diff-change__op { color: #1a7f37; }
.diff-change--remove .diff-change__op { color: #cf222e; }
.diff-change--replace .diff-change__op { color: var(--accent); }

.diff-change__path {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--text);
}

.diff-change__was {
  color: var(--muted);
  text-decoration: line-through;
}

.diff-change__arrow { color: var(--muted); }

.diff-change__value { color: var(--text-strong); }

.diff__none {
  padding: 8px 10px;
  font-size: 12px;
  color: var(--muted);
}

/* Textual layer: paired lines with +/- gutters, diff2html's language. */
.diff__textual summary {
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  margin-bottom: 8px;
}

.diff-table {
  overflow-x: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.55;
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 10px;
}

.diff-line {
  display: grid;
  grid-template-columns: 40px 40px 16px 1fr;
  white-space: pre;
}

.diff-line__ln {
  padding: 0 6px;
  color: var(--muted);
  text-align: right;
  user-select: none;
}

.diff-line__gutter {
  text-align: center;
  user-select: none;
}

.diff-line__text { padding-right: 12px; }

.diff-line--add {
  background: rgb(26 127 55 / 12%);
}
.diff-line--add .diff-line__gutter { color: #1a7f37; }

.diff-line--del {
  background: rgb(207 34 46 / 12%);
}
.diff-line--del .diff-line__gutter { color: #cf222e; }

/* The word-level highlight: only the changed run inside a changed line. */
.diff-line__text mark {
  padding: 0 1px;
  border-radius: 3px;
  color: inherit;
}

.diff-line--add mark { background: rgb(26 127 55 / 32%); }
.diff-line--del mark { background: rgb(207 34 46 / 32%); }

@media (max-width: 1000px) {
  .history__split { grid-template-columns: 1fr; }
  font-size: 15px;
  color: var(--text-strong);
}

.detail__field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 13px;
}

.detail__field > span {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.detail__field code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  overflow-wrap: anywhere;
}

.detail__field input {
  height: 34px;
  padding: 0 10px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 9px;
  outline: none;
}

.detail__field input:focus {
  border-color: var(--accent);
}

.detail__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

/* #1719: `.tag`'s own margin-left is for a chip following a heading. Inside a
   field it would offset the value's leading chip from plain-text values and
   add to `.detail__tags`' (or `.job-detail__subjects`') gap; chips after the
   first in a plain inline run keep it as their only separation. */
.detail__tags > .tag,
.job-detail__subjects > .tag,
.detail__field > div > .tag:first-child {
  margin-left: 0;
}

/* #1719: in the Search Parameters detail panel every value — text, code,
   code block or chip row — sits one indent in from its label. Scoped to
   `.detail` so kv-grid fields and the tester's inputs stay flush. */
.detail > .detail__field > :not(:first-child) {
  margin-left: 8px;
}

/* #835: the job detail's Job card Subjects field — a `.tag--type` pill per
   subject holding only the kind, its display name running on as plain text
   right after (not inside the pill, unlike .detail__tags' own single-node
   pills), so the two flow and wrap together like ordinary inline content. */
.job-detail__subjects {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.detail__code {
  margin: 0;
  padding: 10px 12px;
  background: var(--input-bg);
  border-radius: 9px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.55;
  overflow-x: auto;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.detail__hint {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--muted);
}

.detail__hint-inline {
  font-size: 11.5px;
  color: var(--muted);
}

/* Lint findings (the validation surface the editor grows into) */

.lint {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.lint__item {
  display: flex;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 9px;
  font-size: 12px;
  line-height: 1.45;
}

.lint__item--error {
  background: var(--danger-soft);
  border: 1px solid var(--danger);
}

.lint__item--warn {
  background: var(--warn-soft);
  border: 1px solid var(--warn);
}

.lint__item--info {
  background: var(--accent-soft);
  border: 1px solid var(--accent);
}

.lint__sev {
  flex-shrink: 0;
  font-weight: 700;
}

.lint__item--error .lint__sev { color: var(--danger); }
.lint__item--warn .lint__sev { color: var(--warn); }
.lint__item--info .lint__sev { color: var(--accent); }

/* Compartment viewer: tabs, members, tester. The .tabs / .tab primitives are
   defined once, with the history feed switcher. */

.tab[aria-current="true"],
.tab[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--text-strong);
}

.panel {
  padding: 18px;
}

.kv-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px 18px;
  margin-bottom: var(--kv-grid-margin-bottom, 14px);
}

.detail__field--wide {
  grid-column: 1 / -1;
}

/* Job detail facts: pack the label/value pairs from the left at their
   natural width. In a wide card the two-column `1fr 1fr` grid left the
   second column halfway across the page, far from the values it belongs
   with, and ids rendered in `code` were smaller (11.5px) than the other
   13px values. Wide fields (manifest URL, subjects) take a row of their own. */
.kv-grid--facts {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 40px;
}

.kv-grid--facts > .detail__field {
  min-width: 0;
  max-width: 100%;
}

.kv-grid--facts > .detail__field--wide {
  flex-basis: 100%;
}

.kv-grid--facts .detail__field code {
  font-size: inherit;
}

.members {
  display: flex;
  flex-direction: column;
  max-height: 560px;
  overflow-y: auto;
}

.member {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 6px;
  border-bottom: 1px solid var(--surface-border);
  font-size: 13px;
}

.member__name {
  flex-shrink: 0;
  width: 220px;
  font-weight: 600;
  color: var(--text-strong);
}

.member__params {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  gap: 5px;
  align-items: center;
}

.tester {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: end;
}

.tester .detail__field {
  min-width: 170px;
}

.tester-result {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 16px;
  background: var(--bg-content);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
}

.tester-result__title {
  margin: 0;
  font-size: 13px;
  color: var(--text-strong);
}

.tester-result__title--ok { color: var(--ok); }
.tester-result__title--danger { color: var(--danger); }

@media (max-width: 1250px) {
  .filter-layout {
    grid-template-columns: 1fr;
  }

  .filter-rail,
  .detail {
    position: static;
    max-height: none;
  }

  .member__name {
    width: 150px;
  }

  .kv-grid {
    grid-template-columns: 1fr;
  }
}

/* Visual builder rows (conditions / includes / result controls) */

.builder-sections {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 4px;
}

.builder-section__title {
  margin: 0 0 8px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.builder-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px 18px;
}

.builder-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 8px;
}

.builder-row {
  display: flex;
  gap: 6px;
  align-items: center;
}

.builder-row__key,
.builder-row__modifier,
.builder-row__comparator,
.builder-row__value {
  height: 32px;
  padding: 0 10px;
  font: inherit;
  font-size: 12.5px;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 9px;
  outline: none;
}

.builder-row__key {
  width: 190px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}

.builder-row__modifier {
  width: 110px;
  color: var(--muted);
}

.builder-row__comparator {
  flex-shrink: 0;
  width: 72px;
  color: var(--muted);
}

/* The projection keeps the original control's place in the builder's flex
   rows; its shared component owns the button and popup styling. */
.combobox--inline:has(> .builder-row__key) { --combobox-width: 190px; }
.combobox--inline:has(> .builder-row__modifier) { --combobox-width: 110px; }
.combobox--inline:has(> .builder-row__comparator) { --combobox-width: 72px; }
.combobox--inline:has(> .builder-row__ctype) { --combobox-width: 130px; }
.combobox--inline:has(> .builder-row__itarget) { --combobox-width: 120px; }

.builder-row__value {
  flex: 1;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}

.builder-row__key:focus,
.builder-row__modifier:focus,
.builder-row__comparator:focus,
.builder-row__value:focus {
  border-color: var(--accent);
}

.builder-row--invalid {
  flex-wrap: wrap;
}

.builder-row__key[aria-invalid="true"] {
  border-color: var(--danger);
}

.builder-row__key[aria-invalid="true"]:focus {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-soft);
}

.builder-row__error {
  flex-basis: 100%;
  margin: 0;
  font-size: 12px;
}

.builder-row__remove {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.builder-row__remove.is-hover {
  background: var(--accent-soft);
  color: var(--text-strong);
}

.builder-add {
  align-self: flex-start;
}

/* Condition rows can grow a full-width modifier panel. */
#builder-conditions > .builder-row {
  flex-wrap: wrap;
}

.builder-row__ctype,
.builder-row__cparam,
.builder-row__chainref,
.builder-row__htype,
.builder-row__href {
  height: 32px;
  padding: 0 10px;
  font: inherit;
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 9px;
  outline: none;
  width: 150px;
}

.builder-row__ctype {
  width: 130px;
  font-family: inherit;
  color: var(--muted);
}

.builder-row__ctype:focus,
.builder-row__cparam:focus,
.builder-row__chainref:focus,
.builder-row__htype:focus,
.builder-row__href:focus {
  border-color: var(--accent);
}

.builder-row__chainlabel {
  flex-shrink: 0;
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.builder-pill {
  flex-shrink: 0;
  padding: 3px 8px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  color: var(--accent);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
}

.builder-row__drill {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--surface-border);
  border-radius: 7px;
  background: none;
  color: var(--accent);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.builder-row__drill.is-hover {
  background: var(--accent-soft);
}

/* In-page results card */

.query-results {
  margin-bottom: 24px;
}

.query-results__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 20px 4px;
}

/* The heading reuses .query-group__type, whose bottom margin spaces the
   stacked query groups it was written for. Here the row is centred with
   align-items, which centres each child's margin box — so that margin lifted
   the heading ~2px above the count beside it (#994). */
.query-results__head .query-group__type {
  margin-bottom: 0;
}

.query-results__meta {
  flex: 1;
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

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

  .builder-row {
    flex-wrap: wrap;
  }
}

/* Shared Search/Resources layout: resource picker rail and main column. */

.queries-layout {
  display: grid;
  /* Same rail width as .nav-panel and .filter-layout (#603 follow-up): one
     rail width across Resources, Search, and Search
     Parameters. */
  grid-template-columns: 280px minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}

.queries-main {
  min-width: 0;
}

/* The picker rail reuses .filter-rail with real `<a>` items (#541). */

@media (max-width: 1100px) {
  .queries-layout {
    grid-template-columns: 1fr;
  }
}

/* ---------------------------------------------------------------------------
   Search page (#255, Figma "Search V1.0"): a segmented mode switch over one
   query — describe it in natural language, or build it by hand. Both write the
   same editable QUERY strip, so the mode is a way of writing, not a separate
   search path.
   --------------------------------------------------------------------------- */

.seg {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
}

.seg__btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 14px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 9px;
  cursor: pointer;
}

.seg__btn.is-hover {
  color: var(--text);
}

.seg__btn[aria-pressed="true"] {
  color: var(--text-strong);
  background: var(--bg-content);
  border-color: var(--surface-border);
  box-shadow: var(--surface-shadow);
}

/* The natural-language pane and the builder rows are the two modes; the QUERY
   strip below them belongs to neither, and is always visible. Driven by CSS
   rather than the `hidden` attribute so the builder's own re-render cannot
   fight the mode switch. */
.search-page[data-mode="nl"] .builder-sections,
.search-page[data-mode="builder"] .nl-pane {
  display: none;
}

.nl-pane {
  margin-top: 14px;
}

.nl-input {
  display: flex;
  gap: 8px;
  align-items: center;
  position: relative;
}

.nl-input__spark {
  position: absolute;
  left: 14px;
  color: var(--accent);
  pointer-events: none;
}

.nl-input input {
  flex: 1;
  min-width: 0;
  height: 44px;
  padding: 0 14px 0 40px;
  font: inherit;
  font-size: 14px;
  color: var(--text);
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: 12px;
  outline: none;
}

.nl-input input::placeholder {
  color: var(--muted);
}

/* The Ask button keeps the shared action-button scale. */
.nl-input .btn {
  white-space: nowrap;
}

.nl-pane__hint {
  margin: 10px 2px;
  font-size: 12px;
  color: var(--muted);
}

.nl-examples {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.nl-examples .chip {
  height: 30px;
  color: var(--muted);
  background: transparent;
  cursor: pointer;
}

.nl-examples .chip.is-hover {
  color: var(--text);
}

.nl-answer {
  margin-top: 14px;
  padding: 12px 14px;
  font-size: 13px;
  border: 1px solid var(--surface-border);
  border-radius: 10px;
}

.nl-answer__text {
  margin: 0;
  color: var(--text);
}

.nl-answer--ok {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.nl-answer--working {
  color: var(--muted);
}

/* A refused request is an answer, not a failure: the model was asked for
   something this server does not do. Muted, not alarming. */
.nl-answer--unsupported {
  background: var(--input-bg);
}

.nl-answer--error {
  background: var(--danger-soft);
  border-color: var(--danger);
  color: var(--danger);
}

.nl-answer__caveats-heading {
  margin: 10px 0 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

.nl-answer__caveats {
  margin: 0;
  padding-left: 18px;
  color: var(--text);
}

/* Enabled but unconfigured: advertise the feature, name the env vars, link the
   how-to. An operator who has never heard of it can turn it on from here. */
.nl-setup {
  margin-top: 14px;
  padding: 18px;
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
}

.nl-setup__head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
  color: var(--accent);
}

.nl-setup__head h2 {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-strong);
}

.nl-setup p {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--text);
}

.nl-setup__env {
  margin: 0 0 12px;
  padding: 12px 14px;
  overflow-x: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--text);
  background: var(--bg-content);
  border: 1px solid var(--surface-border);
  border-radius: 10px;
}

.nl-setup__note {
  font-size: 12px;
  color: var(--muted);
}

/* The QUERY strip: the generated query is never hidden and never read-only —
   the user sees it, can fix it, and only then runs it. */
.query-builder__tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 38px;
  padding: 0 12px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  white-space: nowrap;
}

.query-builder__copy {
  color: var(--muted);
}

.query-builder__copy.is-hover {
  color: var(--text);
}

@media (max-width: 900px) {
  .query-builder__tag {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
   Resource editor (#264, Figma "Resources - Edit").

   Brett's shell — the Guided form / JSON toggle, Save & Delete — with a tree
   that is generated from the resource's schema rather than from a fixed list of
   friendly fields. Extensions are tinted, not exiled: they are ordinary
   elements and they render inline with everything else.
   --------------------------------------------------------------------------- */

.editor__bar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 18px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 14px;
  box-shadow: var(--surface-shadow);
}

.editor__title h2 {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-strong);
}

.editor__subject {
  font-size: 12px;
  color: var(--muted);
}

/* "Will be saved as Type/id" notice while creating (#1751): the editor and
 * modal subject lines wear it when the document carries a valid id. */
.subject--target {
  background: var(--accent-soft);
  color: var(--accent-text);
  border-radius: 4px;
  padding: 2px 6px;
}

.editor__actions {
  display: flex;
  /* Centre the row's children on one line: the 20px "Unsaved changes" pill
     `unsaved.js` prepends here is shorter than the buttons and otherwise sits
     at the top of the row (#1648). `.modal__actions` already does this. */
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* #1723: let editing actions wrap without a long logical id pushing Cancel
   or Save outside a narrow viewport. Keep the rule local to these hosts. */
#editor .editor__bar,
#editor .editor__actions {
  flex-wrap: wrap;
}
#editor .editor__title {
  min-width: 0;
  flex: 1 1 180px;
}
#editor .editor__subject {
  overflow-wrap: anywhere;
}
@media (max-width: 640px) {
  #editor .editor__actions {
    margin-left: 0;
    width: 100%;
  }
}

.editor__status {
  min-height: 20px;
  margin: 10px 4px;
  font-size: 13px;
  color: var(--muted);
}

.editor__status--error {
  color: var(--text);
}

/* Three panels on one screen (Brett's Resource Editor): JSON + guided form side
   by side, with a version rail to the right. */
.editor__layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 240px;
  gap: 16px;
  align-items: start;
}

.editor__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
  /* Both cards end on one line, whichever is taller (#1648). */
  align-items: stretch;
}

.editor-json,
.editor-form {
  min-width: 0;
  padding: 0;
  overflow: hidden;
}

/* The JSON card hands the height it was stretched to (above) to its content,
   so the editor fills the card instead of leaving a blank band under the
   hint when the guided form is the taller of the two (#1648). */
.editor-json {
  display: flex;
  flex-direction: column;
}

/* View Definitions' editor + guided-form pairing (#843): the two cards grow
   together, stretching to whichever is taller, capped at 70vh — beyond that,
   each scrolls inside its own content area instead of growing the row.
   `.editor__grid` alone (the Resource Editor's layout, above) does not do
   this: each of its panes caps itself independently at 70vh (`.editor-tree`,
   `.json-view`, below) with nothing requiring the two to match. A flex row
   rather than a second grid template: hiding the guided-form card without
   JavaScript (`needs-js`) then leaves the editor card alone to fill the
   whole row, with no empty second track to collapse by hand. */
.editor__grid--stretch {
  display: flex;
  align-items: stretch;
  gap: 16px;
  max-height: 70vh;
}

/* `display` lives in its own rule, scoped `:not(.needs-js)`: layer order
   beats specificity in the cascade, so a `display` declared anywhere in
   this `pages` layer would otherwise always win over `.needs-js`'s
   `display: none` in the earlier `components` layer, `html.js` or not, if
   this selector also matched the guided-form card — undoing the whole
   point of that utility. `html.js .needs-js` (also `components`, specific
   enough to win there) already resolves the guided-form card's own
   `display: flex` generically; every other box-model property below is
   identical for both cards and safe to share, since they do nothing to an
   element `display: none` has already taken out of layout. */
.editor__grid--stretch > .card:not(.needs-js) {
  display: flex;
}

.editor__grid--stretch > .card,
.editor__grid--stretch > .needs-js {
  flex: 1 1 0;
  flex-direction: column;
  min-width: 0;
  min-height: 320px;
  overflow: hidden;
}

/* `.editor__doc` (#840, generalized off View Definitions' own `#vd-editor-
   form`): the flex-column container of a JSON editor pane inside a stretch
   pairing — on View Definitions, the `<form id="vd-editor-form">` around
   the editor itself carries this class; any future `.editor__grid--stretch`
   consumer's own editor container does the same to get identical sizing.
   The editor card's own scrolling area is whichever of the two the browser
   actually mounted: CodeMirror's wrapper (`.code-editor`) normally, or the
   plain `<textarea>` it replaces when the bundle never mounted.
   Only `flex` lives here for the CodeMirror wrapper: the matching
   `min-height`/`max-height` override that actually lets it grow past its
   own 320px/70vh clamp to fill the stretch row lives beside the unlayered
   rule it overrides, further down this file — a layered override of an
   unlayered declaration always loses regardless of specificity (see that
   section's own header comment), so `max-height: none` written here would
   silently do nothing. The textarea fallback's own `flex: 1` moved down to
   sit there too (`design-system.spec.ts`'s "no selector twice" guard does
   not treat `@layer` as licensing a second definition of the same selector,
   deliberately — a repeat is a repeat regardless of which cascade layer it
   lives in) — with nothing here it would otherwise need to beat, `flex`
   loses nothing by living unlayered alongside `max-height: none` instead of
   splitting the one selector across two blocks. */
.editor__grid--stretch .editor__doc {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
}

.editor__grid--stretch .code-editor {
  flex: 1;
  min-height: 0;
}

/* The guided-form card: header, legend, and validity chip stay put; the row
   tree (or, mid-edit, the invalid-JSON notice) becomes the scrolling area. */
.editor__grid--stretch .editor-form .editor-tree,
.editor__grid--stretch .editor-form .alert {
  flex: 1;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
}

/* `#editor-form` (`editor-hidden-form.html`) carries only hidden `<input>`s
   — nothing to show — but as a bare `<form>` it is still a block box; inside
   a stretch row it would otherwise get `align-items: stretch`'s full row
   height as empty, gap-widening space between the two cards. */
#editor-form {
  display: none;
}

.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--surface-border);
}

.card-head h3 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-strong);
}

/* A `<details class="card">` whose `<summary>` is itself a `.card-head`
   (SQL Export's "Advanced" disclosure, #836): a card that opens and closes
   like every other native disclosure in this file, without a second markup
   language for "collapsible card" versus "collapsible section". */
details.card > summary.card-head {
  list-style: none;
  cursor: pointer;
}

details.card > summary.card-head::-webkit-details-marker {
  display: none;
}

.card-head__tools {
  display: flex;
  gap: 6px;
}

.editor-json__act {
  padding: 3px 8px;
  font: inherit;
  font-size: 11px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--surface-border);
  border-radius: 7px;
  cursor: pointer;
}

.editor-json__act.is-hover { color: var(--text); }
.editor-json__act--on { color: var(--accent); border-color: var(--accent); }

/* The Format button of the two JSON editor cards (#1757): it reuses
   `.editor-json__act`; the hint and the invalid-JSON status sit before it
   in the same `.card-head__tools--wrap` row, which wraps instead of
   overflowing a narrow card. */
.card-head__tools--wrap {
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
}

.editor-format__hint {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}

/* No button, no shortcut hint: the button stays `hidden` until the editor
   mounts. */
.editor-format__hint:has(+ [data-editor-format][hidden]) { display: none; }

.editor-format__status {
  font-size: 12px;
  color: var(--warn);
}

.editor-format__status:empty { display: none; }

/* Both panes are viewport-bound scroll containers, so a large document
   scrolls inside its own column and the cross-highlight's reveal always has
   a scrollbar to move — with page-level scrolling, the highlighted
   counterpart could sit far off-screen (#573 follow-up). */
.editor-tree {
  padding: 12px 14px;
  max-height: 70vh;
  overflow-y: auto;
}

/* ---- the foldable JSON view ---- */

.json-view {
  padding: 10px 0;
  overflow-x: auto;
  max-height: 70vh;
  overflow-y: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.6;
}

/* The CapabilityStatement — and each HTS workbench pane (#898) — owns the
   only scroll container for its incremental tree. Keep bounded scalar
   fragments from creating nested 70vh scrollers. */
#capability-json-body .json-view,
.capability-json-pane__body .json-view {
  max-height: none;
  overflow: visible;
}

/* Form ↔ JSON cross-highlight (editor-pair.js): hovering or focusing a form
   row lights the JSON lines that hold that node, and vice versa. */
.editor-row--hit {
  background: var(--accent-soft);
  border-radius: 8px;
}

.json-line {
  display: flex;
  align-items: baseline;
  white-space: pre;
}

.json-line.is-hover { background: var(--input-bg); }

.json-line__num {
  flex: 0 0 auto;
  width: 40px;
  padding-right: 10px;
  text-align: right;
  color: var(--muted);
  opacity: 0.6;
  user-select: none;
}

.json-line__fold {
  flex: 0 0 auto;
  width: 24px;
  text-align: center;
  color: var(--muted);
  user-select: none;
}

.json-line__arrow {
  cursor: pointer;
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  transition: transform 0.1s;
}

.json-line--collapsed .json-line__arrow { transform: rotate(-90deg); }

.json-line__code { min-width: 0; }

/* The summary shows only on a collapsed opener. */
.json-line__summary { display: none; color: var(--muted); }
.json-line--collapsed .json-line__summary { display: inline; padding-left: 2px; }

/* Syntax highlighting. */
.jt--key { color: var(--json-key); }
.jt--string { color: var(--json-string); }
.jt--number { color: var(--json-number); }
.jt--bool,
.jt--null { color: var(--json-literal); font-weight: 600; }
.jt--punct { color: var(--muted); }

/* ---- version rail ---- */

.editor-versions { padding: 0; }

.editor-versions__list {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px;
}

.editor-version {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 7px 9px;
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 9px;
  cursor: pointer;
}

.editor-version.is-hover { background: var(--input-bg); }
.editor-version--current { border-color: var(--surface-border); }
.editor-version--current .editor-version__id { color: var(--accent); }

.editor-version__id { font-size: 13px; font-weight: 600; color: var(--text); }
.editor-version__meta { font-size: 11px; color: var(--muted); }
.editor-versions__none { padding: 10px; font-size: 12px; color: var(--muted); }

.editor-validity {
  font-size: 12px;
  font-weight: 600;
  color: #cf222e;
}
.editor-validity--ok { color: var(--accent); }


.alert code {
  display: block;
  margin-top: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--muted);
}

/* ---- the tree ---- */

.editor-row {
  padding: 3px 0;
  border-left: 1px solid transparent;
}

.editor-row__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  min-height: 28px;
}

.editor-row__label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
}

.editor-row__type {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--muted);
}

.editor-row__input {
  flex: 1;
  /* Never squeezed below a usable width — the head wraps it to its own line
     instead of eating the content. */
  min-width: 180px;
  max-width: 420px;
  height: 28px;
  padding: 0 10px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  outline: none;
}

.editor-row__input:focus {
  border-color: var(--accent);
}

.editor-row__del {
  width: 22px;
  height: 22px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  color: var(--muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
}

.editor-row__del.is-hover {
  color: var(--text);
  border-color: var(--surface-border);
}

/* Extensions are normal FHIR. Tinted, not exiled. */
.editor-row--extension > .editor-row__head .editor-row__label {
  color: var(--accent);
}

/* A modifier extension changes the meaning of the resource: an unrecognised
   one must stop processing. It gets an outline, not a tint. */
.editor-row--modifier {
  border-left: 2px solid var(--muted);
  padding-left: 6px;
}

.editor-row__warn {
  padding: 1px 6px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-strong);
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: 999px;
  cursor: help;
}

.editor-row__warn--muted {
  color: var(--muted);
  background: transparent;
  border-color: var(--surface-border);
}

.editor-row--error .editor-row__input {
  border-color: #cf222e;
}

.editor-row__error {
  margin: 2px 0 4px 0;
  font-size: 12px;
  color: var(--danger);
}

/* ---- "what can I add here?" ---- */

.editor-add {
  margin: 2px 0 6px;
}

.editor-add__toggle {
  display: inline-block;
  padding: 2px 8px;
  font-size: 12px;
  color: var(--muted);
  border: 1px dashed var(--surface-border);
  border-radius: 8px;
  cursor: pointer;
  list-style: none;
}

.editor-add__toggle::-webkit-details-marker {
  display: none;
}

.editor-add__toggle.is-hover {
  color: var(--accent);
  border-color: var(--accent);
}

.editor-add__panel {
  max-width: 520px;
  margin-top: 6px;
  padding: 10px;
  background: var(--bg-content);
  border: 1px solid var(--surface-border);
  border-radius: 10px;
}

.editor-add__filter {
  width: 100%;
  height: 30px;
  padding: 0 10px;
  margin-bottom: 8px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  outline: none;
}

.editor-add__list {
  display: flex;
  flex-direction: column;
  max-height: 260px;
  overflow-y: auto;
}

.editor-add__item,
.editor-add__choice {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 8px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  text-align: left;
  background: transparent;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
}

.editor-add__item.is-hover {
  background: var(--accent-soft);
}

.editor-add__name em {
  color: var(--accent);
  font-style: normal;
}

.editor-add__type,
.editor-add__again {
  margin-left: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--muted);
}

.editor-add__choice select {
  margin-left: auto;
  height: 26px;
  font: inherit;
  font-size: 12px;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 6px;
}

.editor-add__choice .combobox--inline {
  --combobox-height: 26px;
  --combobox-font-size: 12px;
  --combobox-width: min(65%, 320px);
  margin-left: auto;
}

/* The ad-hoc extension: any URL, at any node. The thing the surveyed editors
   cannot do without a profile. */
.editor-add__ext {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--surface-border);
}

.editor-add__ext label {
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}

.editor-add__ext-url {
  flex: 1;
  min-width: 0;
  height: 28px;
  padding: 0 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  outline: none;
}

/* ---- picker panel (#1239): header with a close control and an
   Elements/Extensions accordion so the
   form around the panel stays in view. Everything here hangs off
   .editor-add--picker; the SQL library cards keep sharing the bare
   .editor-add markup and rules above, untouched. ---- */

.editor-add--picker .editor-add__panel {
  max-width: 560px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.editor-add--picker .editor-add__filter {
  margin-bottom: 0;
}

.editor-add__head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.editor-add__title {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

/* Not `.icon-button` (#1239): that class is a hard 30x30/9px contract
   (`design-system.spec.ts`'s "canonical button scale" check) shared by
   every other icon-only control in the crate, and this close control needs
   to sit smaller, inline with the picker's title. Same recipe as the
   CodeMirror lint panel's own 24x24 close button below (`#821`) instead of
   a one-off. */
.editor-add__close {
  display: inline-grid;
  place-items: center;
  margin-left: auto;
  width: 24px;
  height: 24px;
  padding: 0;
  font-size: 18px;
  line-height: 1;
  color: var(--muted);
  background: transparent;
  border: 0;
  border-radius: 9px;
  cursor: pointer;
}

.editor-add__close.is-hover {
  color: var(--text);
  background: var(--accent-soft);
}

.editor-row__undo {
  margin: 2px 0 4px;
}

.editor-add__undo {
  padding: 2px 0;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 12px;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}

.editor-add__undo.is-hover { color: var(--text); }
.editor-add__undo:disabled {
  opacity: 0.5;
  cursor: progress;
}

.editor-add__undo:focus-visible,
.editor-row[tabindex="-1"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.editor-row--collection > .editor-row__head > [data-collection-add],
.editor-row--collection > .editor-row__head > .editor-add {
  margin-left: auto;
}

.editor-row--collection > .editor-row__head > .editor-add {
  min-width: 0;
  width: auto;
}

.editor-add__group {
  border: 1px solid var(--surface-border);
  border-radius: 8px;
}

.editor-add__group-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  background: var(--input-bg);
  border-radius: 8px;
  cursor: pointer;
  list-style: none;
}

.editor-add__group-head::-webkit-details-marker {
  display: none;
}

.editor-add__group-head::before {
  content: "▸";
  display: inline-block;
  color: var(--muted);
  transition: transform 0.1s ease;
}

.editor-add__group[open] > .editor-add__group-head::before {
  transform: rotate(90deg);
}

.editor-add__count {
  margin-left: auto;
  font-weight: 400;
  font-size: 11px;
  color: var(--muted);
}

.editor-add--picker .editor-add__list {
  max-height: 200px;
  padding: 4px;
}

.editor-add--picker .editor-add__ext {
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  margin-top: 0;
  padding: 4px;
  border-top: 0;
}

.editor-add__ext-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--surface-border);
}

.editor-add__ext-label {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}

.editor-add__ext-label .editor-add__ext-url {
  flex: 1;
}

/* ---- source view & validity rail ---- */

/* The JSON pane's plain textarea (#1756): visible only when the code editor
   did not mount (no bundle, or JavaScript off), where it is the pane. Same
   footprint as the mounted editor (`.code-editor--resource`). */
.editor-json__source {
  flex: 1 1 auto;
  min-height: 320px;
  max-height: 70vh;
  margin: 0 14px;
  padding: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  outline: none;
  resize: vertical;
}

.editor-json__source:focus-visible {
  border-color: var(--accent);
}

.editor__source-hint {
  margin: 8px 2px 0;
  font-size: 12px;
  color: var(--muted);
}

/* In the Resource Editor's JSON pane the hint is a direct child of a card
   with no padding of its own, so it needs the card's inset (`.card-head`'s
   14px) to clear the border and the rounded corner (#1648). The SQL pages'
   hints sit inside a padded `.card__body` and keep the rule above. */
.editor-json .editor__source-hint {
  margin: 8px 14px 12px;
}

.editor__side {
  padding: 14px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 14px;
  box-shadow: var(--surface-shadow);
}

.editor__validity {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-strong);
}

.editor__validity--ok {
  color: var(--accent);
}

.editor__orphan {
  margin: 8px 0 0;
  font-size: 12px;
  color: #cf222e;
}

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

}
@layer components {
/* ---------------------------------------------------------------------------
   Resources workspace (#282): the nav submenu, the Create-button page head, and
   the Edit Resource modal (shadowbox).
   --------------------------------------------------------------------------- */

/* Page head with an action on the right (Create new). */
.page-head--row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

/* In a row head the actions sit beside the title, not under a lede: flex
   line-up at the title's level, no conformance-page top offset. */
.page-head--row .page-head__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 0;
}

.filter-rail__item--on {
  background: var(--accent-soft);
  color: var(--text-strong);
}
}

@layer pages {
/* ---- the Edit Resource modal (shadowbox) ---- */

.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog-backdrop);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.modal__backdrop {
  position: absolute;
  inset: 0;
  background: var(--scrim);
}

.modal__box {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(1200px, 100%);
  /* A fixed height, not a ceiling (#607): the dialog occupies the same
     rectangle whichever pane is showing, so switching Edit/History or a
     status message appearing never moves the header. The taller pane
     scrolls inside .modal__body. */
  height: 90vh;
  background: var(--bg-content);
  border: 1px solid var(--surface-border);
  border-radius: 18px;
  box-shadow: 0 24px 64px rgb(0 0 0 / 30%);
  overflow: hidden;
}

.modal__head {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--surface-border);
}

.modal__title h2 {
  margin: 0;
  font-size: 16px;
  color: var(--text-strong);
}

.modal__subject {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--muted);
}

.modal__tabs {
  display: flex;
  gap: 4px;
  margin-left: 8px;
}

.modal__tab {
  padding: 6px 12px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 9px;
  cursor: pointer;
}
.modal__tab--on {
  color: var(--text-strong);
  background: var(--input-bg);
  border-color: var(--surface-border);
}

.modal__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.modal__x {
  width: 34px;
  height: 34px;
  font-size: 20px;
  line-height: 1;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--surface-border);
  border-radius: 9px;
  cursor: pointer;
}
.modal__x.is-hover { color: var(--text); }

.modal__status {
  padding: 0 18px;
  font-size: 13px;
}
.modal__status:not(:empty) { padding: 10px 18px; }
.modal__status--error {
  color: var(--danger);
  font-weight: 600;
  background: var(--danger-soft);
  border-top: 1px solid var(--danger);
  border-bottom: 1px solid var(--danger);
}

.modal__body {
  flex: 1;
  padding: 16px 18px;
  overflow-y: auto;
}

/* The embedded editor's own bar (subject + view toggle) is redundant inside the
   modal, which already has a title and actions — hide it. */
.modal__body .editor__bar,
.modal__body .editor__layout > .editor-versions {
  display: none;
}
.modal__body .editor__layout {
  grid-template-columns: minmax(0, 1fr);
}

@media (max-width: 900px) {
  .modal__box { height: 96vh; }
  .modal__head { flex-wrap: wrap; }
}
}

@layer components {
/* ---------------------------------------------------------------------------
   Resources: the resource-type panel as part of the menu (#282, Brett's
   "Resources" frame). A secondary column flush against the sidebar — same
   surface, no card — with the search + results in the content column.
   --------------------------------------------------------------------------- */

/* The Create-new primary button (blue + plus), top-right of the page head.
   The label names the selected type ("Create new Patient") and can grow long
   (#605) — the button caps its width and truncates the label rather than
   pushing the header row wider than the page. */
.resources-create-wrap {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 320px;
}

.resources-create {
  width: 100%;
  min-width: 0;
  max-width: 100%;
}

.resources-create__label {
  overflow: hidden;
  min-width: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}

}

@layer pages {
/* Saved / Recent group headings inside the Recent dropdown (#282). */
.recent-group {
  margin: 8px 4px 4px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.recent-group:first-child { margin-top: 0; }
.recent-item__saved {
  text-align: left;
  font-weight: 500;
}

/* OR value stacks (#414): one input per comma alternative. */
.builder-row__values {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
  min-width: 230px;
}

.builder-row__orvalue {
  display: flex;
  gap: 4px;
  align-items: center;
}

.builder-row__orvalue .builder-row__value {
  flex: 1;
}

.builder-row__orvalue:only-child .builder-row__remove--or {
  visibility: hidden;
}

.builder-row__or {
  flex-shrink: 0;
  align-self: flex-start;
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--surface-border);
  border-radius: 9px;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.builder-row__or.is-hover {
  background: var(--accent-soft);
}

/* MODIFY panel (#415): explained modifier chips under the row. */
.builder-row__adv {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--surface-border);
  border-radius: 7px;
  background: none;
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}

.builder-row__adv.is-hover,
.builder-row__adv[aria-expanded="true"] {
  background: var(--accent-soft);
  color: var(--accent);
}

.builder-row__modpanel {
  flex-basis: 100%;
  min-width: 0;
  max-width: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  padding: 6px 0 2px;
}

.builder-row__modheading {
  color: var(--muted);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.builder-row__modchip {
  display: inline-flex;
  gap: 6px;
  align-items: baseline;
  padding: 3px 10px;
  border: 1px solid var(--surface-border);
  border-radius: 999px;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 11.5px;
  cursor: pointer;
}

.builder-row__modchip strong {
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
}

.builder-row__modchip.is-hover {
  border-color: var(--accent);
}

.builder-row__modchip[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}

.builder-row__modchip[aria-pressed="true"] strong {
  color: var(--accent);
}

/* The search lifecycle stays next to QUERY even when results are below it. */
.query-search-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-size: 12.5px;
}

.query-search-state:has(> :not([hidden])) {
  margin-top: 8px;
}

.query-search-slow {
  margin-top: 8px;
}

.query-results.is-busy .data-table {
  opacity: 0.55;
}

/* Results sort (#416). */
.query-results__sort {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  margin-left: auto;
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  --combobox-width: 200px;
  --combobox-height: 30px;
}

.query-results__sort select {
  height: 30px;
  padding: 0 8px;
  font: inherit;
  font-size: 12.5px;
  text-transform: none;
  letter-spacing: normal;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  outline: none;
}

.query-results__sort .combobox--inline {
  text-transform: none;
  letter-spacing: normal;
}

/* Related-data rows (#396). */
.builder-row__itype,
.builder-row__iparam {
  height: 32px;
  padding: 0 10px;
  font: inherit;
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 9px;
  outline: none;
  width: 150px;
}

.builder-row__itarget {
  height: 32px;
  padding: 0 10px;
  font: inherit;
  font-size: 12px;
  color: var(--muted);
  background: var(--input-bg);
  border: 1px solid var(--surface-border);
  border-radius: 9px;
  outline: none;
  width: 120px;
}

.builder-row__itype:focus,
.builder-row__iparam:focus,
.builder-row__itarget:focus {
  border-color: var(--accent);
}

.builder-row__iterate {
  flex-shrink: 0;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--surface-border);
  border-radius: 999px;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 11.5px;
  cursor: pointer;
}

.builder-row__iterate[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}

.builder-row--include {
  flex-wrap: wrap;
}

.builder-section__sub {
  margin: -4px 0 4px;
  color: var(--muted);
  font-size: 12px;
}

/* In plain English (#395). */
.query-plain {
  display: flex;
  gap: 10px;
  align-items: baseline;
  margin: 2px 0 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}

.query-plain__unknown {
  color: var(--danger);
  font-weight: 500;
}

.query-plain__pill {
  flex-shrink: 0;
  padding: 2px 8px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  color: var(--accent);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
}

}
@layer components {
/* Conformance CRUD (#237, #238): New in the page head, Edit/Delete in the
   detail panels. */
.page-head__actions {
  margin-top: 10px;
}

.detail__actions {
  display: flex;
  gap: 8px;
  margin: 10px 0;
}

.btn--danger {
  border-color: var(--danger);
  color: var(--danger);
}

.btn--danger.is-hover {
  background: var(--danger-soft);
}

/* The shared in-page confirmation (#1667, confirm.js): a modal <dialog> in
   the top layer, so it needs no z-index band of its own. */
.confirm-dialog {
  width: min(420px, calc(100vw - 32px));
  padding: 18px 20px;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 16px;
  box-shadow: 0 24px 64px rgb(0 0 0 / 30%);
}

.confirm-dialog::backdrop {
  background: var(--scrim);
}

.confirm-dialog__message {
  margin: 0 0 16px;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.confirm-dialog__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}

}
@layer pages {
/* mustSupport emphasis + human labels (#364). */
.editor-row--must-support > .editor-row__head .editor-row__label {
  font-weight: 600;
}

/* The element's short description sits on its own line under the head, so it
   can never squeeze the input out of the row. */
.editor-row__desc {
  margin: 2px 0 0;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.4;
}

.editor-row__ms,
.editor-add__ms {
  padding: 1px 5px;
  border: 1px solid var(--accent);
  border-radius: 5px;
  color: var(--accent);
  font-size: 9.5px;
  letter-spacing: 0.04em;
}

.editor-add__short {
  display: block;
  flex-basis: 100%;
  color: var(--muted);
  font-size: 11px;
}

/* Batch / Transaction workspace (#476). */
.batch-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 44px 16px;
  border: 1.5px dashed var(--divider);
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
}

.batch-drop strong {
  color: var(--text);
}

.batch-drop--over,
.batch-drop.is-hover {
  border-color: var(--accent-strong);
  color: var(--text);
}

/* Spacing between the dropzone and its error banner (pre-existing gap,
   fixed per user QA on #681): `.alert`'s own margin is bottom-only, sized
   for a banner that precedes content elsewhere, so it leaves no gap above
   `.batch-drop`. Scoped by id, not on `.alert` itself, to avoid affecting
   any other `.alert` use on the page. */
#batch-upload-error.alert {
  margin-top: 16px;
}

.request-strip {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
  padding: 10px 14px;
  border: 1px solid var(--divider);
  border-radius: 10px;
  background: var(--surface);
}

.request-strip__label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The request line uses the app's mono stack at an explicit size — with no
   rule it fell back to the UA's default monospace, visibly larger than the
   surrounding text (#674). */
.request-strip code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
}

.batch-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}

/* Card content sits inset from the card border (#674): the tab row, entry
   list, and JSON pane pick up the card's horizontal padding instead of
   running edge-to-edge. */
.card > .batch-tabs {
  margin: 12px 14px 8px;
}

.card > .batch-rows,
.card > .batch-json {
  margin: 0 14px 12px;
}

.batch-tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border: 1px solid var(--divider);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
}

.batch-tab[aria-selected="true"] {
  background: var(--accent-soft);
  color: var(--accent-text);
  border-color: transparent;
}

.batch-rows {
  list-style: none;
  margin: 0;
  padding: 0;
}

.batch-row {
  border-top: 1px solid var(--divider);
}

.batch-row__head {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 4px;
  border: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  cursor: pointer;
  text-align: left;
}

.batch-row__head--static {
  cursor: default;
}

.batch-row__num {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-text);
  font-size: 12px;
  flex: none;
}

.batch-chip {
  padding: 2px 8px;
  border: 1px solid var(--divider);
  border-radius: 6px;
  font-family: ui-monospace, monospace;
  font-size: 12px;
  flex: none;
}

.batch-chip--post { color: var(--accent-text); border-color: var(--accent-strong); }
.batch-chip--delete { color: var(--danger); border-color: var(--danger); }

.batch-row__url {
  font-family: ui-monospace, monospace;
  font-size: 13px;
  overflow-wrap: anywhere;
}

.batch-row__arrow {
  margin-left: auto;
  color: var(--muted);
}

.batch-row__body {
  margin: 0 0 12px 34px;
  padding: 12px;
  border: 1px solid var(--divider);
  border-radius: 8px;
  background: var(--surface);
  font-size: 12.5px;
  overflow-x: auto;
}

.batch-row__body .json-view,
.batch-json .json-view {
  padding: 0;
  max-height: 60vh;
}

.json-view__fallback {
  margin: 0;
  white-space: pre;
  font: inherit;
}

.batch-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
}

/* #679: one busy region serves both the parse and execute phases; sticky
   keeps it in view when a large plan pushes the bottom footer past the
   viewport (#675's scroll problem, again). */
.batch-busy {
  position: sticky;
  bottom: 12px;
  width: fit-content;
  margin-top: 12px;
  padding: 6px 12px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 9px;
  box-shadow: var(--surface-shadow);
}

.batch-footer--top {
  margin: 0 0 12px;
}

.batch-footer--end {
  justify-content: flex-end;
}

.batch-badge {
  margin-left: auto;
  padding: 2px 10px;
  border-radius: 6px;
  font-family: ui-monospace, monospace;
  font-size: 12px;
}

.batch-badge--ok { background: var(--accent-soft); color: var(--accent-text); }
.batch-badge--error { background: var(--danger-soft); color: var(--danger); }

.batch-summary {
  margin: 4px 0 10px;
  color: var(--muted);
}

/* The card head's right-hand cluster (#729): created count beside the
   status badge. */
.card-head__meta {
  display: flex;
  align-items: center;
  gap: 10px;
}

.batch-created {
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* The stage takes programmatic focus when revealed (#679); it is not
   keyboard-reachable, so the browser's default ring around the whole
   section is noise (#726). */
#batch-preflight:focus {
  outline: none;
}


.batch-json {
  margin: 0;
  padding: 12px;
  border: 1px solid var(--divider);
  border-radius: 8px;
  background: var(--surface);
  font-size: 12.5px;
  overflow-x: auto;
}

/* Slice + binding chips (#365). */
.editor-row__slice {
  padding: 1px 6px;
  border-radius: 5px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.editor-row__bind {
  padding: 1px 5px;
  border: 1px dashed var(--surface-border);
  border-radius: 5px;
  color: var(--muted);
  font-size: 9.5px;
  letter-spacing: 0.03em;
}

.editor-add__slicename {
  color: var(--accent);
  font-weight: 500;
}

/* Live-vs-save legend (#366): stacked, inset from the card edge. */
.editor-legend {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 8px 14px 10px;
  color: var(--muted);
  font-size: 11px;
}

.editor-legend__live::before,
.editor-legend__save::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 5px;
}

.editor-legend__live::before {
  background: var(--accent);
}

.editor-legend__save::before {
  border: 1px dashed var(--muted);
}

.editor-row__deferred {
  padding: 1px 5px;
  border: 1px dashed var(--muted);
  border-radius: 5px;
  color: var(--muted);
  font-size: 9.5px;
}

/* The JSON-pane header (#548): fold controls hug the heading on the left,
   Edit raw stays alone at the end. */
.card-head__tools--start {
  margin-right: auto;
}

/* The subjects table's toolbar (SQL Export builder, #834): the type switch
   and filter box beside "Subjects". Rendered `hidden` until the filter/
   switch/select-all enhancement reveals it — a no-JavaScript client never
   sees this row at all. `flex-wrap` keeps the switch and the filter box from
   forcing horizontal overflow once revealed on a narrow viewport. */
.card-head__tools--subjects {
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.card-head__tools--subjects .field__input {
  width: 220px;
}

}
@layer components {
/* Shared HTMX-backed multi-select combobox (#655). Domain-specific search and
   result rendering stay in each caller; this block owns the reusable control. */
.combobox {
  position: relative;
  min-inline-size: 0;
  padding: 0;
  border: 0;
}

.combobox__enhancement {
  display: block;
  position: relative;
}

.combobox__selections {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.combobox__control {
  display: block;
  position: relative;
}

.combobox__search-icon {
  position: absolute;
  z-index: 1;
  top: 50%;
  left: 12px;
  width: 16px;
  height: 16px;
  color: var(--muted);
  pointer-events: none;
  transform: translateY(-50%);
}

.combobox__search-icon svg {
  width: 100%;
  height: 100%;
}

.combobox__input {
  padding-left: 38px;
}

.combobox__listbox {
  position: absolute;
  z-index: 10;
  top: calc(100% + 4px);
  right: 0;
  left: 0;
  max-height: 280px;
  padding: 5px;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--popover-border);
  border-radius: 10px;
  background: var(--popover);
  box-shadow: var(--popover-shadow);
}

.combobox__message:not([hidden]) {
  display: block;
}

.combobox__option {
  display: block;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.combobox__option.is-hover,
.combobox__option--active {
  background: var(--accent-soft);
  color: var(--text-strong);
}

.combobox__option[aria-selected="true"] {
  color: var(--muted);
}

/* Fixed choices share the lookup popup, with an enduring selection marker
   independent of the keyboard's active option. */
.combobox__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  cursor: pointer;
  text-align: left;
}
.combobox__trigger [data-combobox-value] {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.combobox__trigger:disabled {
  color: var(--muted);
  cursor: default;
}
/* Adopted selects keep their native business classes. Callers can size this
   shared composition with variables instead of copying those classes to buttons. */
.combobox--inline {
  display: inline-block;
  width: var(--combobox-width, auto);
  max-width: 100%;
  flex-shrink: 0;
  vertical-align: middle;
}
.combobox--inline .combobox__trigger {
  height: var(--combobox-height, 32px);
  padding: 0 8px;
  gap: 6px;
  border-radius: 8px;
  font-size: var(--combobox-font-size, 12.5px);
}
.combobox--fixed .combobox__listbox {
  position: fixed;
  z-index: calc(var(--z-dialog-panel) + 1);
  top: auto;
  right: auto;
  left: auto;
  max-width: calc(100vw - 16px);
}
.combobox__chevron {
  display: flex;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  color: var(--muted);
}
.combobox__chevron svg { width: 100%; height: 100%; }
.combobox--fixed .combobox__option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  overflow-wrap: anywhere;
}
.combobox--fixed .combobox__option[aria-disabled="true"] {
  color: var(--muted);
  background: transparent;
  cursor: default;
}
.combobox--fixed .combobox__option[aria-selected="true"] {
  color: var(--text-strong);
  font-weight: 600;
}
.combobox--fixed .combobox__option[aria-selected="true"]::after {
  content: "✓";
  color: var(--accent-text);
}

/* The words of a Patient name the query matched. --accent-text is the
   AA-contrast accent on both themes (the plain --accent is not on light). */
.combobox__match {
  background: none;
  color: var(--accent-text);
  font-weight: 600;
}

/* Pinned under the scrolled options; the negative offsets cancel the
   listbox's own 5px padding so it sits flush with the edges. */
.combobox__footer {
  position: sticky;
  bottom: -5px;
  margin: 0 -5px -5px;
  padding: 7px 12px;
  border-top: 1px solid var(--popover-border);
  background: var(--popover);
  color: var(--muted);
  font-size: 12px;
}

/* Page load in flight: spinner + label (#1755). */
.combobox__footer--loading {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Infinite-scroll sentinel — the page script observes it. */
.combobox__more {
  height: 1px;
}

.combobox__chip {
  gap: 4px;
  max-width: 100%;
  padding-right: 2px;
}

.combobox__chip-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.combobox__remove {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

.combobox__remove.is-hover,
.combobox__remove:focus-visible {
  background: var(--accent-soft);
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Shared single-value typeahead (#1643). */
.typeahead__listbox {
  position: fixed;
  z-index: 30;
  max-height: 280px;
  padding: 5px;
  overflow-y: auto;
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  background: var(--surface);
  box-shadow: 0 12px 30px rgb(0 0 0 / 12%);
}

.typeahead__option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 10px;
  border-radius: 7px;
  color: var(--text);
  font-family: "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 12px;
  cursor: pointer;
}

.typeahead__option--active {
  background: var(--accent-soft);
  color: var(--text-strong);
}

.typeahead__hint {
  display: inline-flex;
  flex: none;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--text);
  font-family: Figtree, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 11px;
  font-weight: 600;
}

.typeahead__empty {
  padding: 9px 10px;
  color: var(--muted);
  font-size: 12.5px;
}

/* Addbox close affordances (#545). */
.addbox__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.menu__heading--row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.addbox__x {
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 6px;
}

.addbox__x.is-hover,
.addbox__x:focus-visible {
  color: var(--text-strong);
  background: var(--accent-soft);
}

}

@layer pages {
/* The Recent/Saved dropdown scrolls instead of growing unbounded (#542):
   the recents are capped at ten, but every saved query renders in the same
   panel, and a long collection was pushing it off-screen. */
.query-recent #recent-searches {
  max-height: min(420px, 60vh);
  overflow-y: auto;
}

/* Issues that have no field yet (#546): a bounded, styled panel instead of a
   wall of bare red lines under the tree. */
.editor__orphans {
  margin-top: 14px;
  padding: 12px 16px;
  border-left: 3px solid #C0362C;
  border-radius: 0 10px 10px 0;
  background: rgb(192 54 44 / 8%);
  max-height: 220px;
  overflow-y: auto;
}

.editor__orphans-title {
  font-weight: 600;
  font-size: 13px;
  margin-bottom: 6px;
}

.editor__orphans .editor__orphan {
  margin: 2px 0;
  font-size: 13px;
}

/* Temporal-primitive assistance (#549): the pattern attribute drives live
   native validity — a malformed date shows before any server round trip. */
.editor-row__input:invalid {
  border-color: #C0362C;
  box-shadow: 0 0 0 3px rgb(192 54 44 / 12%);
}
}

@layer pages {
/* Bulk Export (#537): the resource-type checkbox grid. Its job-status chips
   are .tag variants — one status-pill vocabulary, not a second chip family. */
.bulk-export-form .combobox--scope-patient {
  display: none;
}

.bulk-export-form:has(input[name="scope"][value="patient"]:checked) .combobox--scope-patient {
  display: block;
}

/* Resource types read alphabetically down each column, like an index (#1758).
   Multi-column flow keeps DOM and tab order unchanged. */
.typegrid {
  columns: 220px;
  column-gap: 18px;
  margin-top: 8px;
  padding: 3px 0;
}

.typegrid > .typegrid__item {
  break-inside: avoid;
  padding: 3px 0;
}

.typegrid__item {
  font-size: 14px;
  display: flex;
  gap: 8px;
  align-items: center;
  min-width: 0;
}

.typegrid__item > input {
  flex: none;
}

/* Resource names share the rail's clipped-name tooltip. Keep their text
   inside its grid track instead of allowing a long flex item to overlap the
   next column (#793). */
.typegrid__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- Bulk Export against the Figma frame (#735) ---------------------- */

.bulk-export-form__name {
  max-width: 420px;
}

/* A small standalone section heading between cards. */
.form-legend {
  margin: 4px 2px -8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-strong);
}

/* The app's radio-group treatment: one selectable card per choice. */
.choice-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
}

.choice-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  cursor: pointer;
}

.choice-card:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.choice-card:has(input:focus-visible) {
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.choice-card input[type="radio"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
}

.choice-card__title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  color: var(--text-strong);
}

.choice-card__hint {
  margin-left: 26px;
  font-size: 13px;
  color: var(--muted);
}

/* SQL Export's CSV header switch (#1716): a CSV-only sub-option right under
   the format cards, its hint indented to line up with the checkbox label. */
.sql-export-header-option {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.sql-export-header-option input[type="checkbox"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
}

.sql-export-header-option .field__hint {
  margin-left: 24px; /* 16px checkbox + the label's 8px gap */
}

/* Uppercase-labelled filter fields side by side. */
.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
}

.field-row .field__label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- Exports builder and job cards (#735, #795, #832) ---------------- */

[data-bulk-export-name-heading] {
  overflow-wrap: anywhere;
}

.job-card {
  padding: 14px;
}

.job-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.job-card__name {
  min-width: 0;
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-strong);
  overflow-wrap: anywhere;
}

/* #835: the card title links to the job's own detail page. */
.job-card__name a {
  color: inherit;
  text-decoration: none;
}

.job-card__name a.is-hover {
  text-decoration: underline;
}

.job-card__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 10px;
}

.job-card__delete > summary {
  list-style: none;
  cursor: pointer;
}

.job-card__delete > summary::-webkit-details-marker {
  display: none;
}

.job-card__delete-confirm {
  width: min(280px, calc(100vw - 64px));
  padding: 8px 4px 4px;
  color: var(--text);
  white-space: normal;
}

.job-card__delete-confirm p {
  margin: 0 0 10px;
}

.job-card__delete-confirm form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.progress {
  height: 8px;
  margin: 12px 0 10px;
  border-radius: 999px;
  background: var(--accent-soft);
  overflow: hidden;
}

.progress__bar {
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
}

.progress--in-progress .progress__bar {
  background: var(--accent);
}

.progress--complete .progress__bar {
  background: var(--ok);
}

.progress--failed .progress__bar {
  background: var(--danger);
}

.progress--cancelled .progress__bar {
  background: var(--muted);
}

.job-card__meta {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

.job-card__window {
  color: var(--muted);
}

.job-card__files {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.job-card__files a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border: 1px solid var(--surface-border);
  border-radius: 999px;
  font-size: 13px;
  color: var(--text);
  text-decoration: none;
}

.job-card__files a.is-hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* #835: the Output files table's Files column reuses .job-card__files for
   its download pills, but as a table cell's sole content — the top margin
   above assumes trailing a .job-card__meta paragraph, which isn't there. */
.data-table .job-card__files {
  margin-top: 0;
}
}

@layer pages {
/* ---------------------------------------------------------------------------
   App-shell pages (#541): the screen itself never scrolls - the page head
   stays put while each region below it (type rail, main panel, detail)
   scrolls on its own. Declared last, in the pages layer, so it wins over the
   per-page layout rules above. Reverted under 1100px, where the columns stack
   and the page scrolls normally.
   --------------------------------------------------------------------------- */

body.app-shell {
  height: 100dvh;
  overflow: hidden;
}

body.app-shell .pane {
  min-height: 0;
  overflow: hidden;
}

.content--app {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  max-width: none;
}

.content--app .filter-layout,
.content--app .queries-layout {
  flex: 1;
  min-height: 0;
  align-items: stretch;
}

.content--app .filter-rail {
  position: static;
  max-height: none;
  min-height: 0;
  overflow: hidden;
}

.content--app .filter-rail__list {
  /* min-height: 0 lets the flex item shrink below its content, so the list
     itself scrolls instead of being clipped by the rail's hidden overflow. */
  flex: 1;
  min-height: 0;
}

.content--app .filter-center,
.content--app .queries-main {
  min-height: 0;
  overflow-y: auto;
  /* The column scrolls itself: without this the scrollbar rides flush
     against the title-row buttons and card edges. */
  padding-right: 10px;
}

.content--app .detail {
  position: static;
  max-height: none;
  min-height: 0;
}

@media (max-width: 1100px) {
  body.app-shell {
    height: auto;
    overflow: visible;
  }

  body.app-shell .pane {
    overflow: visible;
  }

  .content--app {
    display: block;
    overflow: visible;
  }

  .content--app .filter-center,
  .content--app .queries-main {
    overflow: visible;
  }
}
}

/* ---------------------------------------------------------------------------
   Shared CodeMirror 6 chrome (#838, generalized out of #753's
   original ViewDefinition-only rules): the wrapper layout, focus treatment,
   and the async server lint's underline/gutter/tooltip colors
   that every CodeMirror mount in this crate shares, for the `.code-editor`
   wrapper `code-editor.js`'s `HfsCodeEditor.mount` inserts before the
   source textarea it replaces. Deliberately OUTSIDE the `tokens, base,
   components, pages` layer system above: CodeMirror mounts its own
   `<style>` via `StyleModule.mount` at runtime, and that stylesheet is
   unlayered. Per the CSS Cascade Layers spec, *any* unlayered declaration
   always beats *every* layered one, regardless of selector specificity — so
   a `.cm-editor` override written inside `@layer components` would silently
   lose to CodeMirror's own defaults no matter how many classes it stacked.
   Living here, unlayered, is what lets these rules actually override the
   library's baseTheme (the focus outline in particular) without
   `!important`, which the rest of this file deliberately avoids (see the
   `.kv-grid--flush` comment above). Only the CodeMirror-facing rules need
   this; everything about `.code-editor__source--mounted` (the hidden
   textarea) is ordinary layout, but it lives in this same unlayered block
   for one clear read.

   What is deliberately NOT here: sizing (min/max-height) and non-JSON token
   colors. Sizing is per editor — `.editor__doc` (shared JSON editor panes)
   and `.sql-editor` further down each set their own heights, because
   different editors in this crate need different footprints even though
   they share every other rule below. The JSON token palette (`.cmt-json-*`,
   right below) IS here, generalized off View Definitions' own original
   copy (#840): every editor in this crate that shows outer JSON — View
   Definitions today, the Library Details editor next (#840) — reads the
   same five classes, so one shared rule set beats a per-editor copy. A
   language injected into JSON's own string values (`.cmt-fp-*`, FHIRPath)
   is not shared the same way and stays scoped to the editor that injects
   it (`.vd-editor`, further down). */

/* JSON token colors — the same variables the Resource Editor's raw JSON
   view (.jt--key et al.) already uses, so a JSON document's outer shape
   reads identically wherever it appears in the UI. */
.code-editor .cmt-json-key { color: var(--json-key); }
.code-editor .cmt-json-string { color: var(--json-string); }
.code-editor .cmt-json-number { color: var(--json-number); }
.code-editor .cmt-json-literal { color: var(--json-literal); font-weight: 600; }
.code-editor .cmt-json-punct { color: var(--muted); }

.code-editor {
  width: 100%;
}

.code-editor .cm-editor {
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  background: var(--input-bg, var(--surface));
}

.code-editor .cm-editor.cm-focused {
  /* Same focus treatment as .json-editor:focus, the textarea this replaces. */
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.code-editor .cm-scroller {
  overflow: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.5;
}

.code-editor .cm-content {
  color: var(--text);
  padding: 12px 0;
}

.code-editor .cm-gutters {
  background: transparent;
  color: var(--muted);
  border-right: 1px solid var(--surface-border);
}

.code-editor .cm-activeLine,
.code-editor .cm-activeLineGutter {
  background: var(--accent-soft);
}

/* #821: `.cm-gutters`' own `color: var(--muted)` above, over the active
   line's `--accent-soft` wash, measures 4.44:1 in the light theme — just
   under WCAG AA's 4.5:1 (axe-caught: `a11y.spec.ts`'s targeted lint-panel/
   tooltip states are the first scan to ever exercise a focused, active-line
   editor). The active line's own content already renders full-strength
   (`.cm-content`'s `color: var(--text)`, never dimmed) rather than muted;
   its gutter number matching that is the same fix, not a one-off color. */
.code-editor .cm-activeLineGutter {
  color: var(--text);
}

.code-editor .cm-selectionBackground {
  background: var(--accent-soft);
}

/* Row -> editor cross-highlight (#843, vd-editor.js): a CodeMirror line
   decoration (its own `cm-line` wrapper gains this class alongside), the
   same `--accent-soft` wash `.editor-row--hit` uses for the form side of the
   JSON<->form link. */
.code-editor .cm-line--hit {
  background: var(--accent-soft);
}

/* drawSelection()'s caret defaults to a fixed black border-left (CM6's
   unthemed base — its `&dark` variant never applies here, see the tooltip
   comment below), invisible over a dark `--input-bg`. Tying it to the
   theme's own text color keeps it visible, and exactly matching, in both
   themes without depending on CodeMirror's internal light/dark state. */
.code-editor .cm-cursor {
  border-left-color: var(--text);
}

.code-editor .cm-matchingBracket {
  background: var(--accent-soft);
  outline: 1px solid var(--accent);
  border-radius: 2px;
}

/* bracketMatching()'s unmatched-bracket indicator (gate feedback: the
   muddy, hardcoded box sitting right on top of an unclosed `(` in a
   FHIRPath expression, exactly where the async lint's own squiggly
   underline already marks the same error). CM6's only rule for it is
   `&.cm-focused .cm-nonmatchingBracket{backgroundColor:"#bb555544"}` —
   unthemed (fires regardless of light/dark), ignoring this app's palette
   entirely, and redundant once a lint diagnostic already covers the same
   span with its own (themed) underline. Cleared rather than recolored: the
   squiggle alone (pure text-decoration) already reads as the single,
   clean "VSCode-like" error indicator. Selector matches CM6's own
   `.cm-editor.cm-focused .cm-nonmatchingBracket` compound one extra class
   over - a plain `.code-editor .cm-nonmatchingBracket` ties CM6's
   specificity and loses, since its <style> mounts (and so cascades) after
   this sheet. */
.code-editor .cm-editor.cm-focused .cm-nonmatchingBracket {
  background-color: transparent;
}

/* Lint hover tooltip chrome (#753): @codemirror/lint's own
   `.cm-tooltip` styling only exists under an internal `&light`/`&dark`
   state — set once, globally, from whether any loaded theme extension
   opted into `EditorView.darkTheme`, which no mount in this crate ever
   does (the browser only knows syntax, not which app theme is active).
   CodeMirror therefore always renders its `&light` variant, a hardcoded
   `#f5f5f5`/`#bbb` box, while the tooltip's *text* still inherits this
   page's real `--text` — white-on-`#f5f5f5` in dark, and a box that
   matches neither surface in light. Recolored here as an ordinary app
   popover instead: the same surface/border/radius/shadow recipe
   `.menu__panel` already uses, so it reads as this UI's own chrome, not a
   library default. Reachable — and, since #821, also *actionable* — three
   ways: hovering the underline, resting the cursor inside it (both native
   `@codemirror/lint` behavior, left at its default `hoverTime`), or
   Ctrl+. (`vd-editor.js`'s own `Mod-.` binding, which applies a lone fix
   directly and opens the quick-fix menu next to the cursor for several,
   styled just below); Ctrl-Shift-M opens the
   bottom `cm-panel-lint` list of every diagnostic at once (`lintKeymap`,
   also wired in `vd-editor.js`), styled together with this tooltip further
   down since both render the identical `.cm-diagnostic` markup. */
.code-editor .cm-tooltip {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  box-shadow: var(--surface-shadow);
  overflow: hidden;
}

.code-editor .cm-tooltip-lint {
  max-width: 440px;
  /* No card padding: each diagnostic is its own padded block below, so the
     divider between blocks can run edge to edge. */
  padding: 0;
}

/* One block per diagnostic in the hover card (#1757): the message on the
   first row, the rule code under it, and the fix buttons on a row of their
   own. The DOM order @codemirror/lint emits (message, buttons, code) is
   kept; `order` below only changes the visual sequence. The severity cue is
   a dot instead of the side stripe (the stripe is the bottom panel's). */
.code-editor .cm-tooltip-lint .cm-diagnostic {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 8px;
  row-gap: 4px;
  padding: 12px 14px 12px 32px;
  border-left: 0;
}

.code-editor .cm-tooltip-lint .cm-diagnostic::before {
  content: "";
  position: absolute;
  left: 14px;
  top: 17px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
}

.code-editor .cm-tooltip-lint .cm-diagnostic-error::before {
  background: var(--danger);
}

.code-editor .cm-tooltip-lint .cm-diagnostic-warning::before {
  background: var(--warn);
}

.code-editor .cm-tooltip-lint .cm-diagnostic + .cm-diagnostic {
  border-top: 1px solid var(--surface-border);
}

.code-editor .cm-tooltip-lint .cm-diagnosticText {
  flex: 0 0 100%;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.35;
}

.code-editor .cm-tooltip-lint .cm-diagnosticSource {
  order: 1;
  flex: 0 0 100%;
  font-size: 11px;
}

.code-editor .cm-tooltip-lint .cm-diagnosticAction {
  order: 2;
  margin: 6px 0 0;
  height: 28px;
  padding: 0 10px;
  font-size: 12px;
  border-radius: 7px;
}

/* CM6's own `.cm-diagnostic` base sets `marginLeft: -1px` so a diagnostic's
   severity bar sits flush against its container's 1px border. Zeroed back
   when the hover card still drew that bar: against the card's rounded,
   clipped corners the bar's square ends rendered as an oddly clipped (and,
   at the anti-aliased edge, washed-out) pill (gate feedback). Since #1757
   the hover card draws a severity dot instead (`border-left: 0` under
   `.cm-tooltip-lint`), so the only bar left is the bottom lint panel's,
   which this keeps inside the panel's own edge. */
.code-editor .cm-diagnostic {
  margin-left: 0;
}

/* Quick-fix action buttons (#821): one per `Fix` the diagnostic
   carries, rendered by @codemirror/lint itself inside `.cm-diagnostic` (the
   tooltip) and each `<li>` of `.cm-panel-lint` alike. CM6's own baseTheme
   hardcodes a dark `#444` pill with white text, unthemed and out of step
   with everything else in this popover - recolored here as this app's own
   secondary-button recipe (`.btn`'s own surface/border/radius, scaled down
   to the diagnostic row's small type size) instead. The hover card's own
   `.cm-tooltip-lint .cm-diagnosticAction` rule above resizes them (#1757). */
.code-editor .cm-diagnosticAction {
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  padding: 3px 8px;
  margin-left: 8px;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: 6px;
  cursor: pointer;
}

.code-editor .cm-diagnosticAction.is-hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

/* The app's own focus indicator (#682, same recipe as `.btn:focus-visible`
   above) - these buttons are reached by Tab while the tooltip/panel has
   focus, not just by mouse. */
.code-editor .cm-diagnosticAction:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* The diagnostic's own `source` (#821: the lint's `code`, e.g.
   "unknown-key") - an intentionally untranslated technical tag, so it reads
   as secondary/reference text rather than part of the message itself. CM6's
   own baseTheme already dims it (`opacity: .7`); recolored to `--muted` so
   both themes keep AA contrast against `--surface` rather than an
   arbitrary opacity over whatever `--text` happens to be. */
.code-editor .cm-diagnosticSource {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--muted);
  opacity: 1;
}

/* Server lint presentation (#753): the underline, gutter
   marker, and tooltip/panel border CM6's own @codemirror/lint baseTheme
   bakes fixed reds/oranges into (an inline SVG data URI for the underline
   and the gutter dot, which cannot reference a CSS variable) are redrawn
   here with --danger/--warn so both severities theme correctly in light and
   dark. Nothing here blocks Save — diagnostics stay purely informational. */
.code-editor .cm-lintRange-error {
  background-image: none;
  text-decoration: underline wavy var(--danger);
}

.code-editor .cm-lintRange-warning {
  background-image: none;
  text-decoration: underline wavy var(--warn);
}

.code-editor .cm-gutter-lint .cm-lint-marker {
  width: 8px;
  height: 8px;
  margin: 0.5em auto 0;
  border-radius: 50%;
}

.code-editor .cm-lint-marker-error {
  background: var(--danger);
}

.code-editor .cm-lint-marker-warning {
  background: var(--warn);
}

.code-editor .cm-diagnostic-error,
.code-editor .cm-diagnostic-warning {
  /* CM6's own base is a 5px bar - thinned to a subtle accent stripe
     (VSCode's own hover card uses a similarly thin severity rule), not a
     block. This stripe belongs to the bottom lint panel; the hover card
     replaces it with a dot (see `.cm-tooltip-lint .cm-diagnostic`). */
  border-left-width: 3px;
}

.code-editor .cm-diagnostic-error {
  border-left-color: var(--danger);
}

.code-editor .cm-diagnostic-warning {
  border-left-color: var(--warn);
}

/* jsonParseLinter()'s local syntax pass reports a single point, not a range,
   so CM6 renders it as a small triangle (.cm-lintPoint) rather than an
   underline — same --danger/--warn recoloring, different selector. */
.code-editor .cm-lintPoint::after {
  border-bottom-color: var(--danger);
}

.code-editor .cm-lintPoint-warning::after {
  border-bottom-color: var(--warn);
}

/* The quick-fix menu (#1757): what Ctrl+. opens next to the cursor when more
   than one fix applies - a `showTooltip` tooltip, so it inherits the
   `.cm-tooltip` surface above. One `__group` per diagnostic (its message as
   `__label`), one `__item` button per fix. */
.code-editor .cm-quickfix {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  min-width: 220px;
  max-width: 360px;
}

.code-editor .cm-quickfix__group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.code-editor .cm-quickfix__label {
  padding: 6px 10px 4px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.code-editor .cm-quickfix__item {
  display: flex;
  align-items: center;
  width: 100%;
  height: 34px;
  padding: 0 10px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  color: var(--text);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 7px;
  cursor: pointer;
}

.code-editor .cm-quickfix__item.is-hover,
.code-editor .cm-quickfix__item:focus-visible {
  background: var(--accent-soft);
  border-color: var(--accent);
  outline: none;
}

/* The bottom lint panel (#821: opened by Ctrl-Shift-M, `lintKeymap`; F8 only
   jumps to the next diagnostic) - CM6 appends it as a sibling of
   `.cm-scroller` inside `.cm-editor`'s own flex column, so it only ever
   eats into the scroller's share of `.cm-editor`'s fixed height (`app.css`'s
   own "ViewDefinition editor" section further down sets that height, min
   320px/max 70vh or 100% inside the guided-form stretch pairing); the
   editor itself never grows or shrinks to make room, so opening it causes
   no layout shift outside the card. Every diagnostic renders as the exact
   same `.cm-diagnostic`/`.cm-diagnosticAction`/`.cm-diagnosticSource`
   markup the hover tooltip above uses, one per `<li>` - already themed by
   those same rules; only the panel's own chrome (surface, the scrollable
   list CM6 caps at 100px tall by default, the selected row, and the close
   button) needs its own recoloring here. */
.code-editor .cm-panel.cm-panel-lint {
  background: var(--surface);
  color: var(--text);
  border-top: 1px solid var(--surface-border);
}

.code-editor .cm-panel.cm-panel-lint ul {
  background: var(--surface);
}

.code-editor .cm-panel.cm-panel-lint ul li {
  border-bottom: 1px solid var(--surface-border);
}

/* #821: the panel's own `<li>` no longer carries `[aria-selected]` at all
   (`vd-editor.js`'s own update listener strips it, alongside `role="option"`,
   for the `nested-interactive` fix below — mirroring its true/false onto
   this plain `[data-selected]` attribute first) so this selector never
   fights `@codemirror/lint`'s own baseTheme rules for the same attribute —
   including its `ul:focus [aria-selected]` variant, which swapped in the
   OS `Highlight`/`HighlightText` system colors the moment the panel
   actually had keyboard focus (`openLintPanel` focuses it on open) and
   measured a 2.05:1 contrast ratio against `.cm-diagnosticSource`'s own
   `--muted` text in axe's own reported environment; `data-selected` simply
   has no such competing rule to lose to. */
.code-editor .cm-panel.cm-panel-lint ul li[data-selected="true"] {
  background: var(--accent-soft);
  color: var(--text);
}

/* #821 (axe `target-size`, WCAG 2.2 AA): CM6's own baseTheme sizes this
   purely from its `font-size`/line-height/padding, landing at 28.7x18px —
   under the 24x24 minimum. Fixed to the same `.icon-button` recipe (above)
   every other icon-only control in this crate already uses, rather than a
   one-off size just for this button. */
.code-editor .cm-panel.cm-panel-lint button[name="close"] {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  color: var(--muted);
  background: none;
  border: none;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.code-editor .cm-panel.cm-panel-lint button[name="close"].is-hover {
  color: var(--text);
}

.code-editor .cm-panel.cm-panel-lint button[name="close"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Completion popup chrome (#821, code-editor.js's `completion` option):
   `.cm-tooltip-autocomplete` is itself a `.cm-tooltip` (recolored as an app
   popover above — surface/border/radius/shadow, the same recipe
   `.menu__panel` uses), so only what is specific to the option list itself
   needs its own rules here: each row's layout, the label/matched-substring/
   detail text, its kind icon, and the selected row. `@codemirror/
   autocomplete`'s own baseTheme hardcodes the selected row
   (`&light .../li[aria-selected]{background:"#17c",color:"white"}`, the
   same always-`&light` gap the lint tooltip comment above explains) and the
   kind icons (a plain glyph per `Completion.type` via `content` — already
   text, never an image) — recolored below rather than turned off
   (`icons: false`), so each kind stays visually distinct. */
.code-editor .cm-tooltip-autocomplete ul {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}

.code-editor .cm-tooltip-autocomplete ul li {
  display: flex;
  align-items: center;
  gap: 6px;
}

.code-editor .cm-tooltip-autocomplete ul li[aria-selected] {
  background: var(--accent-soft);
  color: var(--text);
}

.code-editor .cm-completionIcon {
  opacity: 0.7;
}

.code-editor .cm-completionIcon-property::after { color: var(--json-key); }
.code-editor .cm-completionIcon-function::after,
.code-editor .cm-completionIcon-method::after { color: var(--fp-function); }
.code-editor .cm-completionIcon-variable::after { color: var(--fp-constant); }

.code-editor .cm-completionLabel {
  color: var(--text);
}

.code-editor .cm-completionMatchedText {
  text-decoration: none;
  color: var(--accent-text);
  font-weight: 600;
}

.code-editor .cm-completionDetail {
  margin-left: auto;
  padding-left: 8px;
  font-style: normal;
  color: var(--muted);
  white-space: nowrap;
}

/* The textarea code-editor.js mounts over: present for the form (its
   `name` attribute still submits), hidden from sight and the accessibility
   tree so it does not shadow the editor's own role="textbox" (CM6's
   default) landmark. */
.code-editor__source--mounted {
  display: none;
}

/* ---------------------------------------------------------------------------
   Shared JSON editor pane sizing (#753, generalized off View Definitions'
   own original copy in #840): `.editor__doc` marks a JSON editor's
   container inside a stretch pairing (`.editor__grid--stretch` further up
   this file, in the layered `pages` section, #843) — on View Definitions
   the `<form id="vd-editor-form">` around the editor carries it; any future
   consumer's own container does the same to get the identical "min 320px,
   max ~70vh, scrolls internally" footprint below.

   Sizing is on `.cm-editor` itself, not the `.editor__doc` wrapper:
   CodeMirror already makes it a flex column with no explicit height, so a
   plain min-height/max-height there is enough for `.cm-scroller`'s
   `height: 100%` to resolve once content forces the max-height clamp,
   without measuring anything in JS.
   --------------------------------------------------------------------------- */

.editor__doc .cm-editor {
  min-height: 320px;
  max-height: 70vh;
}

/* Inside the guided-form pairing (#843): the row itself already caps total
   height at 70vh and stretches both cards to match, so `.cm-editor`
   becomes its wrapper's `flex: 1` scrolling area — `height: 100%` of
   whatever the stretch pairing gives it — instead of clamping itself
   independently. The 320px floor above is untouched — that minimum applies
   either way. Written here, unlayered, beside the rule it overrides: a `pages`-
   layer override of an unlayered declaration always loses regardless of
   specificity (see this section's own header comment) — `max-height: none`
   in `@layer pages` would silently do nothing against the `max-height: 70vh`
   two lines up. */
.editor__grid--stretch .code-editor .cm-editor {
  height: 100%;
  max-height: none;
}

/* #752: the plain `<textarea>` fallback's own height — the same
   "min 320px, max ~70vh, scroll internally" shape `.cm-editor` gets above,
   so nojs and the pre-mount flash match the mounted editor's footprint
   instead of a fixed `rows` count. Scoped to `.editor__doc` rather than
   `.json-editor` itself, which every other page keeps its own sizing for —
   the Library-backed SQL pane below gets the identical treatment, scoped to
   its own smaller editor's dimensions (#839). */
.editor__doc textarea.json-editor {
  min-height: 320px;
  max-height: 70vh;
  overflow-y: auto;
}

/* Same reasoning as `.cm-editor` above for `max-height`: inside the stretch
   pairing the row supplies the 70vh cap, so this fallback's own copy of it
   yields — its 320px floor does not. `flex: 1` rides along here too (moved
   down from beside `.code-editor`'s own copy, further up this file) so the
   selector is not declared a second time. */
.editor__grid--stretch .editor__doc textarea.json-editor {
  flex: 1;
  max-height: none;
}

/* The Resource Editor's JSON pane (#1756, page and Resources modal): the code
   editor fills the body of the `.editor-json` card — inset like the card's
   header — at the footprint the read-only view had: at least 320px, at most
   ~70vh, scrolling inside itself (`.cm-scroller`), never the page. The card is
   stretched to the guided form's height by the grid, so `height: 100%` lets
   the editor take whatever that is up to the cap. Unlayered, beside the
   `.cm-editor` rules it overrides. */
.editor-json .code-editor--resource {
  flex: 1 1 auto;
  width: auto;
  min-height: 0;
  margin: 0 14px;
}

.editor-json .code-editor--resource .cm-editor {
  height: 100%;
  min-height: 320px;
  max-height: 70vh;
}

/* ---------------------------------------------------------------------------
   ViewDefinition editor (#753, generalized in #838; its JSON sizing and
   token colors generalized off this section in #840): only what is
   specific to this editor now lives here — the FHIRPath palette for the
   grammar its own script injects into the outer JSON.
   --------------------------------------------------------------------------- */

/* FHIRPath token colors — the injected grammar, scoped separately in
   vd-editor.js so these never bleed onto the outer JSON's own tokens even
   though several tags (string, number, ...) are literally the same Tag
   objects on both sides of the injection. */
.vd-editor .cmt-fp-path { color: var(--fp-path); }
.vd-editor .cmt-fp-function { color: var(--fp-function); }
.vd-editor .cmt-fp-keyword { color: var(--fp-keyword); font-weight: 600; }
.vd-editor .cmt-fp-operator { color: var(--fp-operator); }
.vd-editor .cmt-fp-literal { color: var(--fp-literal); }
.vd-editor .cmt-fp-constant { color: var(--fp-constant); font-weight: 600; }
.vd-editor .cmt-fp-comment { color: var(--fp-comment); font-style: italic; }

/* ---------------------------------------------------------------------------
   SQL editor (#838, sql-editor.js): mounted over the SQL pane's
   `<textarea name="sql">` on /ui/sql/queries and /ui/sql/views. Same
   min-height-grows / max-height-scrolls shape as `.editor__doc .cm-editor`
   above, just shorter — the SQL pane is a single query, not a full
   ViewDefinition document — and one token palette instead of two (no
   injected language). Not `.editor__doc` itself: the SQL pane is not part
   of a `.editor__grid--stretch` pairing. */

.sql-editor .cm-editor {
  min-height: 180px;
  max-height: 60vh;
}

/* #839: the SQL pane's plain `<textarea>` fallback matches the mounted
   CodeMirror editor's own min-height/max-height above — the same
   "nojs footprint equals the mounted one" contract `.editor__doc
   textarea.json-editor` gives a JSON editor pane, scoped here to
   `#lib-editor-form` instead since `.json-editor` itself keeps its generic
   sizing for every other page. */
#lib-editor-form textarea.json-editor {
  min-height: 180px;
  max-height: 60vh;
  overflow-y: auto;
}

/* SQL token colors — one class per @codemirror/lang-sql tag group this
   editor assigns (sql-editor.js has the full tag -> class mapping and why
   Identifier/Builtin are left uncolored). Keywords and boolean/null
   literals read as bold, matching the JSON/FHIRPath editor's own weight
   convention for "reserved word" tokens; comments read as italic. */
.sql-editor .cmt-sql-keyword { color: var(--sql-keyword); font-weight: 600; }
.sql-editor .cmt-sql-type { color: var(--sql-type); }
.sql-editor .cmt-sql-string { color: var(--sql-string); }
.sql-editor .cmt-sql-number { color: var(--sql-number); }
.sql-editor .cmt-sql-operator { color: var(--sql-operator); }
.sql-editor .cmt-sql-variable { color: var(--sql-variable); }
.sql-editor .cmt-sql-comment { color: var(--sql-comment); font-style: italic; }
.sql-editor .cmt-sql-punct { color: var(--sql-punct); }
.sql-editor .cmt-sql-literal { color: var(--sql-literal); font-weight: 600; }

/* Failed-line tint (#839, sql-editor.js): the class its `errorLineField`
   `StateField` decoration applies to the `.cm-line` of the 1-based line a
   `$sql-run` parse error names (`data-error-line` on `#run-notice`'s
   notice — SQLite execution errors carry no line and get no tint). Three
   classes deep beats `.code-editor .cm-activeLine`'s two above, so the tint
   still shows through when the cursor sits on the failed line. */
.sql-editor .cm-line.sql-editor__error-line {
  background: var(--danger-soft);
}

/* Phone width (#1670): nothing may make the page wider than the viewport.
   Toolbars wrap, long values break, and a table too wide to fit scrolls in
   its own container. */
@media (max-width: 520px) {
  .chart-card__head { flex-wrap: wrap; gap: 12px; }
  .chart-card__tools { flex-wrap: wrap; min-width: 0; }
  .query-results__head { flex-wrap: wrap; }
  .query-results__sort { margin-left: 0; min-width: 0; }
  .query-results__sort select { min-width: 0; max-width: 100%; }
  .history__locate { flex-wrap: wrap; }
  .history__id { flex: 1; min-width: 0; }
  .history__split { grid-template-columns: minmax(0, 1fr); }
  .history__controls { flex-wrap: wrap; }
  .diff-change__was,
  .diff-change__value { overflow-wrap: anywhere; min-width: 0; }
  .toolbar { flex-wrap: wrap; }
  #tenant-rows { overflow-x: auto; }
}
