/* Lituus platform UI — theme tokens + base styles.
 *
 * Rebuilt from docs/input-fields-gallery.html (the design artifact of record) —
 * the **warm, light-first** system: flat cards with an accent top-edge, a
 * purple + teal accent, soft pill badges, and the full field-input primitive.
 *
 * Theming is pure CSS custom properties swapped by a `data-theme` attribute the
 * server writes onto <html> from the user's individual.theme preference:
 *   - LIGHT (warm cream) is the :root base — the brand's primary web face, matching
 *     the dashboard aesthetic. (This reverses the previous dark-first base; there is
 *     no flash regression because the server writes the resolved data-theme into the
 *     initial HTML, so an explicit-theme user paints their theme immediately, and an
 *     Auto user correctly follows the OS.)
 *   - Auto/system (no data-theme attribute) defers to the OS via
 *     prefers-color-scheme; an explicit Light/Dark choice overrides it.
 * No JS applies the theme; the attribute is in the initial HTML, so there is no
 * flash-of-wrong-theme and nothing blocks Time-To-Interact.
 *
 * Fonts are self-hosted, latin-subset variable woff2 (see public/fonts/VENDOR.md) —
 * no external font host is ever contacted at runtime. (The gallery export bundles
 * Google Fonts; the shipped site does not — it keeps these @font-face rules.) */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;      /* variable: one file covers all weights */
  font-display: swap;
  src: url("/fonts/inter-var.woff2") format("woff2");
}
@font-face {
  font-family: "Raleway";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/raleway-var.woff2") format("woff2");
}

/* ---- Design tokens: type + shape (theme-independent) ---- */
:root {
  /* OI-00093: Inter-first product UI (quiet direction). Inter carries headings,
     labels and data; Raleway (--font-display) is reserved for the logo and the
     occasional brand/display moment only. */
  --font-head: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-body: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-display: "Raleway", "Segoe UI", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --radius: 10px;            /* cards, panels, containers */
  --radius-control: 6px;     /* OI-00093 (08/12): tighter radius on interactive controls */
  --maxw: 42rem;

  /* Field-control sizing tokens (OI-00093). The .ctl skin — shared by text / select /
     textarea / lookup / money inputs — reads these, so control density is a token swap,
     not a per-rule edit. The COMFORTABLE (default) values below preserve the accessible
     baseline: 44px touch floor + 16px font (no iOS zoom-on-focus). The `--density-compact`
     override (below :root) redefines them to Louis's Option C "compact admin" tier. The
     boolean checkbox (.choice) is deliberately NOT tokenised here — Louis's dense specimen
     left it fixed too. */
  --ctl-min-height: 44px;
  --ctl-pad-y: 0.55rem;
  --ctl-pad-x: 0.65rem;
  --ctl-font-size: 16px;
  --ctl-radius: var(--radius-control);
  --ctl-textarea-min-height: 92px;
}

/* Compact density — opt-in per user (profile menu → density switch; persisted in the
   `density` cookie, written onto <html data-density="compact"> server-side like the theme).
   Redefines ONLY the control-sizing tokens, so it's orthogonal to the theme (colour)
   tokens. Compact is knowingly denser than the accessible baseline (sub-44px target,
   sub-16px font → iOS may zoom on focus); it is opt-in and never the default, by design.
   Font is ~0.9rem — the faithful scale of Louis's dense tier (0.74rem against his 0.8rem
   base ≈ 92.5%) applied to our 16px base, not his absolute 0.74rem. */
:root[data-density="compact"] {
  --ctl-min-height: 34px;
  --ctl-pad-y: 0.3rem;
  --ctl-pad-x: 0.55rem;
  --ctl-font-size: 0.9rem;
  --ctl-radius: 4px;
  --ctl-textarea-min-height: 68px;
}

/* ---- Colour tokens: LIGHT (warm) is the base/primary face ---- */
:root {
  color-scheme: light;
  --bg: #f7f6f2;             /* warm paper — the document face */
  --surface: #ffffff;
  --surface-2: #f1efe9;
  --ink: #1a1a1a;
  --muted: #6f6d66;
  --line: #e7e4dc;
  --accent: #8b5cf6;         /* Ether purple */
  --accent-strong: #7c3aed;  /* darkens on light for AA on paper */
  --accent-soft: #ede9fe;
  --on-accent: #ffffff;
  --teal: #0d9488;
  --teal-soft: #cdeae5;
  --code-bg: #f1efe9;
  /* field primitives */
  --field-bg: #ffffff;
  --field-border: #dcd8ce;   /* stronger than --line: an input needs a real edge */
  --field-ring: rgba(124, 58, 237, 0.16);
  /* semantic validation hues (distinct from the accent: accent = brand, not good/bad) */
  --danger: #dc2626;
  --danger-ink: #be3455;
  --danger-soft: #fce7ec;
  --ok: #16a34a;
  --ok-ink: #15803d;
  --ok-soft: #dcfce7;
  --warn: #b45309;
  /* budget/estimate battery hue — blue, distinct from good/bad and from the brand accent (OI-00059) */
  --info: #2563eb;
  --info-ink: #1d4ed8;
  --info-soft: #dbeafe;
}

/* ---- Dark token set, shared by the two ways Dark is reached (below) ---- */
[data-theme="dark"] {          /* explicit Dark — wins over the OS */
  color-scheme: dark;
  --bg: #0f0e13;
  --surface: #17161d;
  --surface-2: #221f2a;
  --ink: rgba(255, 255, 255, 0.9);
  --muted: rgba(255, 255, 255, 0.55);
  --line: rgba(255, 255, 255, 0.09);
  --accent: #a855f7;
  --accent-strong: #c084fc;  /* lightens on dark */
  --accent-soft: rgba(168, 85, 247, 0.18);
  --on-accent: #ffffff;
  --teal: #2dd4bf;
  --teal-soft: rgba(45, 212, 191, 0.18);
  --code-bg: rgba(255, 255, 255, 0.05);
  --field-bg: rgba(255, 255, 255, 0.03);
  --field-border: rgba(255, 255, 255, 0.14);
  --field-ring: rgba(168, 85, 247, 0.32);
  --danger: #f87171;
  --danger-ink: #fca5a5;
  --danger-soft: rgba(244, 63, 94, 0.18);
  --ok: #34d399;
  --ok-ink: #6ee7b7;
  --ok-soft: rgba(52, 211, 153, 0.16);
  --warn: #fbbf24;
  --info: #60a5fa;
  --info-ink: #93c5fd;
  --info-soft: rgba(96, 165, 250, 0.18);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {    /* Auto/system on a dark OS — same dark set */
    color-scheme: dark;
    --bg: #0f0e13;
    --surface: #17161d;
    --surface-2: #221f2a;
    --ink: rgba(255, 255, 255, 0.9);
    --muted: rgba(255, 255, 255, 0.55);
    --line: rgba(255, 255, 255, 0.09);
    --accent: #a855f7;
    --accent-strong: #c084fc;
    --accent-soft: rgba(168, 85, 247, 0.18);
    --on-accent: #ffffff;
    --teal: #2dd4bf;
    --teal-soft: rgba(45, 212, 191, 0.18);
    --code-bg: rgba(255, 255, 255, 0.05);
    --field-bg: rgba(255, 255, 255, 0.03);
    --field-border: rgba(255, 255, 255, 0.14);
    --field-ring: rgba(168, 85, 247, 0.32);
    --danger: #f87171;
    --danger-ink: #fca5a5;
    --danger-soft: rgba(244, 63, 94, 0.18);
    --ok: #34d399;
    --ok-ink: #6ee7b7;
    --ok-soft: rgba(52, 211, 153, 0.16);
    --warn: #fbbf24;
    --info: #60a5fa;
    --info-ink: #93c5fd;
    --info-soft: rgba(96, 165, 250, 0.18);
  }
}
/* Explicit Light ([data-theme="light"]) needs no rule — it inherits the :root base. */

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font: 15px/1.5 var(--font-body);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: var(--font-head);
  color: var(--ink);
  line-height: 1.2;
}

h2 {
  margin-top: 2rem;
  font-size: 1.15rem;
  /* OI-00095 (06): quiet default heading — ink, not loud accent. Brand-moment headings
     opt into accent explicitly; the styled heading classes already set their own colour. */
  color: var(--ink);
}

a {
  color: var(--accent-strong);
  text-decoration: none;
}
a:hover {
  color: var(--accent);
}

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--code-bg);
  padding: 0.05rem 0.3rem;
  border-radius: 4px;
}

.container {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 1.5rem 1rem;
}

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

/* ===================================================================== *
 * FIELD SYSTEM — promoted from docs/input-fields-gallery.html.
 * The site-wide styled-input primitive + every widget per column shape.
 * See docs/input-fields-design.md for the taxonomy and rationale.
 * ===================================================================== */

/* ---- Section header + card ---- */
.sec {
  margin: 2.3rem 0 0.9rem;
}
.sec h2 {
  font-family: var(--font-head);
  font-size: 0.72rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 0.25rem;
  font-weight: 700;
}
.sec p {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
  text-wrap: pretty;
}

/* Flat card, quiet 1px border — the record/section container (OI-00093 06: the
   decorative accent top-edge was dropped for the quiet product direction). */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
  padding: 1.15rem 1.1rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 1.15rem;
}

/* ---- The field primitive: label -> control -> help ---- */
.field {
  display: flex;
  flex-direction: column;
  gap: 0.34rem;
}
.field > .lab {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 0.8rem;
  letter-spacing: 0.01em;
  color: var(--ink);
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
}
.req {                         /* required marker — accent asterisk */
  color: var(--accent-strong);
  font-weight: 700;
}
.optm {                        /* "optional" pill */
  font: 600 0.6rem/1 var(--font-mono);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--surface-2);
  border: 1px solid var(--line);
  padding: 0.22rem 0.45rem;
  border-radius: 999px;
}
.help {
  font-size: 0.75rem;
  color: var(--muted);
}
.cap {                         /* schema-annotation caption (design doc / demo) */
  font: 0.65rem/1.4 var(--font-mono);
  color: var(--muted);
  opacity: 0.72;
  word-break: break-word;
}
.cap b {
  color: var(--accent-strong);
  font-weight: 500;
}

/* ---- Control base: the shared skin for text-like inputs, select, textarea ---- */
.ctl {
  width: 100%;
  min-height: var(--ctl-min-height); /* comfortable: 44px touch-target floor */
  font: var(--ctl-font-size)/1.3 var(--font-body); /* comfortable 16px: prevents iOS zoom-on-focus */
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--ctl-radius);
  padding: var(--ctl-pad-y) var(--ctl-pad-x);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
  -webkit-appearance: none;
  appearance: none;
}
.ctl::placeholder {
  color: var(--muted);
}
.ctl:hover:not(:disabled) {
  border-color: color-mix(in srgb, var(--field-border) 55%, var(--ink));
}
.ctl:focus,
.ctl:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--field-ring);
}
textarea.ctl {
  min-height: var(--ctl-textarea-min-height);
  resize: vertical;
  line-height: 1.5;
}
.ctl:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  background: color-mix(in srgb, var(--field-bg) 70%, var(--muted) 8%);
}

/* Field validation states */
.field.is-error .ctl {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 22%, transparent);
}
.field.is-error .help {        /* help line becomes the error message */
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  background: var(--danger-soft);
  color: var(--danger-ink);
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  font-size: 0.73rem;
  font-weight: 500;
}
.field.is-ok .ctl {
  border-color: color-mix(in srgb, var(--ok) 60%, var(--field-border));
}

/* ---- Affix: prefix/suffix wrapper (currency symbol, % suffix, search icon) ---- */
.affix {
  display: flex;
  align-items: stretch;
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-control);
  overflow: hidden;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.affix:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--field-ring);
}
.affix .pre,
.affix .post {
  display: inline-flex;
  align-items: center;
  padding: 0 0.65rem;
  font: 500 0.9rem/1 var(--font-body);
  color: var(--muted);
  background: var(--surface-2);
  white-space: nowrap;
}
.affix .pre {
  border-right: 1px solid var(--field-border);
}
.affix .post {
  border-left: 1px solid var(--field-border);
}
.affix .ctl {
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  flex: 1 1 auto;
  min-width: 0;
}
.affix .ctl:focus {
  box-shadow: none;
}
.num {                         /* right-aligned tabular numerals (currency/percent) */
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ---- Select: custom caret + theme-matched option popup ---- */
.select-wrap {
  position: relative;
}
.select-wrap::after {
  content: "";
  position: absolute;
  right: 0.85rem;
  top: 50%;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}
select.ctl {
  padding-right: 2rem;
}
select.ctl option,
select.ctl optgroup {
  background-color: var(--surface);
  color: var(--ink);
}

/* ---- Two-up field grid (short paired fields at desktop width) ---- */
.two {
  display: grid;
  gap: 1.15rem;
}
@media (min-width: 40rem) {
  .two {
    grid-template-columns: 1fr 1fr;
  }
}

/* ---- Picklist chip (shared display primitive; colour set per value at render) ---- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  font: 600 0.78rem/1.3 var(--font-body);
  /* Same-hue soft badge: light fill (inline background) + dark ink (inline color);
     the border is that same dark ink via currentColor, so the boundary reads on both
     themes without a render-time contrast computation (OI-00004). */
  border: 1px solid currentColor;
  white-space: nowrap;
}
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

/* ---- Radio / checkbox groups ---- */
.choices {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
.choice {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 30px;
  cursor: pointer;
  font-size: 0.92rem;
}
.choice input[type="radio"],
.choice input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  margin: 0;
  background: var(--field-bg);
  border: 1.5px solid var(--field-border);
  cursor: pointer;
  display: grid;
  place-content: center;
  transition: border-color 0.12s, background 0.12s;
}
.choice input[type="radio"] {
  border-radius: 999px;
}
.choice input[type="checkbox"] {
  border-radius: var(--radius-control);
}
.choice input:hover {
  border-color: var(--accent);
}
.choice input:checked {
  background: var(--accent);
  border-color: var(--accent);
}
.choice input[type="radio"]:checked::after {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: var(--on-accent);
}
.choice input[type="checkbox"]:checked::after {
  content: "";
  width: 6px;
  height: 11px;
  margin-top: -2px;
  border: solid var(--on-accent);
  border-width: 0 2.5px 2.5px 0;
  transform: rotate(45deg);
}
.choice input:focus-visible {
  outline: 2px solid var(--accent-strong);
  outline-offset: 2px;
}

/* ---- Toggle switch (mobile-native boolean variant) ---- */
.switch {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  cursor: pointer;
  min-height: 32px;
}
.switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.track {
  flex: 0 0 auto;
  width: 46px;
  height: 28px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--field-border);
  position: relative;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  background: var(--muted);
  transition: transform 0.18s ease, background 0.15s ease;
}
.switch input:checked + .track {
  background: var(--accent);
  border-color: var(--accent);
}
.switch input:checked + .track::after {
  transform: translateX(18px);
  background: var(--on-accent);
}
.switch input:focus-visible + .track {
  box-shadow: 0 0 0 3px var(--field-ring);
}

/* ---- Slider (numeric variant, e.g. probability) ---- */
.slider-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
}
input[type="range"].rng {
  -webkit-appearance: none;
  appearance: none;
  flex: 1 1 auto;
  height: 6px;
  border-radius: 999px;
  background: var(--surface-2);
  outline: none;
}
input[type="range"].rng::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  background: var(--accent);
  border: 3px solid var(--surface);
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}
input[type="range"].rng::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border-radius: 999px;
  background: var(--accent);
  border: 3px solid var(--surface);
  cursor: pointer;
}
input[type="range"].rng:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 3px var(--field-ring);
}
.slider-val {
  flex: 0 0 auto;
  min-width: 3.2rem;
  text-align: right;
  font: 700 0.95rem/1 var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--accent-strong);
}

/* The `hidden` attribute must win over components that set an explicit display
   (e.g. .token/.affix flex), so the lookup can toggle token vs search state. */
[hidden] {
  display: none !important;
}

/* ---- Lookup (FK search) + its results menu ---- */
.lookup {
  position: relative;
}
.lookup .token {
  max-width: 100%;
}
.lookup .token-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lookup .affix .ico {
  color: var(--muted);
}
.lookup-menu {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 4px);
  z-index: 10;
  background: var(--surface);
  border: 1px solid var(--field-border);
  border-radius: var(--radius);
  box-shadow: 0 12px 30px rgba(20, 10, 40, 0.14);
  padding: 0.25rem;
  display: none;
  max-height: 15rem;
  overflow-y: auto;
}
.lookup:focus-within .lookup-menu {
  display: block;
}
.lookup-menu li {
  list-style: none;
}
.lookup-opt {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius-control);
  cursor: pointer;
}
.lookup-opt:hover,
.lookup-opt:focus-visible {
  background: var(--accent-soft);
  outline: none;
}
.lookup-opt .t {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--ink);
}
.lookup-opt .s {
  font-size: 0.74rem;
  color: var(--muted);
}
/* Polymorphic target picker (OI-00165): entity-type group heading in the results menu. */
.lookup-group {
  padding: 0.4rem 0.6rem 0.2rem;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}
.lookup-group:not(:first-child) {
  border-top: 1px solid var(--line);
  margin-top: 0.2rem;
}

/* ---- Selected-lookup token (resolved *_name, clearable, stores the id) ---- */
.token {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.4rem 0.35rem 0.7rem;
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-radius: 999px;
  font-size: 0.88rem;
  color: var(--accent-strong);
  font-weight: 600;
  max-width: 100%;
}
.token .x,
.tag .x {
  display: inline-grid;
  place-content: center;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: transparent;
  border: 0;
  color: var(--muted);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
}
.token .x:hover,
.tag .x:hover {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  color: var(--accent-strong);
}

/* ---- Tag box (multi-value / ;-separated addresses) ---- */
.tagbox {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;
  padding: 0.35rem 0.45rem;
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-control);
}
.tagbox:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--field-ring);
}
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.2rem 0.2rem 0.2rem 0.55rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  font-size: 0.82rem;
}
.tagbox input {
  flex: 1 1 6rem;
  min-width: 6rem;
  border: 0;
  background: transparent;
  color: var(--ink);
  font: 16px/1 var(--font-body);
  padding: 0.3rem;
}
.tagbox input:focus {
  outline: none;
}

/* ---- Segmented control (theme/width toggles, polymorphic type-picker) ---- */
.seg {
  display: inline-flex;
  padding: 2px;
  gap: 1px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 999px;
}
.seg button {
  font: 600 0.74rem/1 var(--font-body);
  cursor: pointer;
  color: var(--muted);
  background: transparent;
  border: 0;
  padding: 0.32rem 0.68rem;
  border-radius: 999px;
  min-height: 30px;
}
.seg button[aria-pressed="true"] {
  /* OI-00095 (04): quiet selected surface, not a solid accent pill (Louis's shared
     interaction states — light accent surface + accent text). */
  background: var(--accent-soft);
  color: var(--accent-strong);
}
.seg button:focus-visible {
  outline: 2px solid var(--accent-strong);
  outline-offset: 2px;
}
.seg.wrap-seg {                /* wrapping variant for the polymorphic picker */
  flex-wrap: wrap;
  border-radius: var(--radius);
}
.seg.wrap-seg button {
  border-radius: var(--radius-control);
}

/* ---- Colour (chip colour_rgba — admin only) ---- */
.colour-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}
.swatch {
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  border-radius: var(--radius-control);
  border: 1px solid var(--field-border);
  padding: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.swatch::-webkit-color-swatch-wrapper {
  padding: 3px;
}
.swatch::-webkit-color-swatch {
  border: 0;
  border-radius: 5px;
}
.hexbox {
  flex: 1 1 auto;
}

/* ---- File dropzone (Sources layer) ---- */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 1.4rem 1rem;
  border: 1.5px dashed var(--field-border);
  border-radius: var(--radius);
  background: var(--field-bg);
  text-align: center;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.dropzone:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.dropzone .big {
  color: var(--ink);
  font-weight: 600;
  font-size: 0.92rem;
}
.dropzone .lk {
  color: var(--accent-strong);
  text-decoration: underline;
}

/* ---- Action buttons (the .btn system; save = Post->Redirect->Get) ---- */
.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
/* A one-button POST form in the header actions row (OI-00167 "Take notes") sits in
   the flex flow as if it were the bare button. */
.record-actions form {
  display: contents;
}
.btn {
  font: 600 0.9rem/1 var(--font-body);
  cursor: pointer;
  min-height: 44px;
  padding: 0 1.05rem;
  border-radius: var(--radius-control);
  border: 1px solid transparent;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}
/* Compact button — for inline affordances like a related list's "New <child>". */
.btn-sm {
  min-height: 32px;
  padding: 0 0.7rem;
  font-size: 0.8rem;
}
.btn-primary {
  background: var(--accent);
  color: var(--on-accent);
}
.btn-primary:hover:not([aria-disabled="true"]) {
  background: var(--accent-strong);
}
.btn-secondary {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--field-border);
}
.btn-secondary:hover:not([aria-disabled="true"]) {
  /* OI-00093 (12): quiet secondary surface + dark text on hover; never the strong
     accent fill/text reserved for true primary actions (Louis's interaction states). */
  background: var(--surface-2);
  border-color: color-mix(in srgb, var(--field-border) 55%, var(--ink));
  color: var(--ink);
}
.btn-danger {
  background: transparent;
  color: var(--danger-ink);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}
.btn-danger:hover {
  background: var(--danger-soft);
}
.btn:focus-visible {
  outline: 2px solid var(--accent-strong);
  outline-offset: 2px;
}
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}
/* aria-disabled (not the `disabled` attribute) is used for the create-on-contact New
   button so it stays hoverable — a real `disabled` control swallows pointer events, and
   with them the native `title` tooltip that carries the "created on Contact" explanation.
   Because it stays hoverable, hold each variant at its RESTING surface on hover: the
   guarded .btn-*:hover rules skip it, so without this the generic button:hover (accent
   fill) would win and flash it purple. */
.btn-primary[aria-disabled="true"]:hover {
  background: var(--accent);
}
.btn-secondary[aria-disabled="true"]:hover {
  background: var(--surface);
  border-color: var(--field-border);
  color: var(--ink);
}

/* ---- View mode: the read-only rendering of each field ---- */
.view-grid {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}
.vf {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.vf .vlab {
  font: 700 0.66rem/1 var(--font-head);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.vf .vval {
  font-size: 0.98rem;
  color: var(--ink);
}
.vf .vval.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.vf .vempty {                  /* muted "— not set —", never a blank gap */
  color: var(--muted);
  font-style: italic;
}
.badge-soft {                  /* quiet metadata badge (rollup / generated / age) */
  display: inline-flex;
  align-items: center;
  font: 600 0.7rem/1 var(--font-body);
  padding: 0.24rem 0.5rem;
  border-radius: 999px;
  background: var(--danger-soft);
  color: var(--danger-ink);
}
.badge-soft.teal {
  background: var(--teal-soft);
  color: var(--teal);
}

/* ===================================================================== *
 * APP SHELL — search + nav + footer + auth screens.
 * (Not part of the gallery; re-themed to the tokens above.)
 * ===================================================================== */

/* Site-wide search section, above the nav. Carries the brand wordmark plus the
   search form. Mobile-first: brand + search stacked and centred. */
.site-search {
  position: relative; /* positioning context for the profile menu (mobile: pinned top-right) */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  padding: 0.85rem 1rem;
}

.site-search-form {
  text-align: center;
}

/* Desktop: brand pinned to the left edge, search centred in the page. The
   symmetric 1fr side columns keep the centre column visually centred whatever
   the brand's width. */
@media (min-width: 40rem) {
  .site-search {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 1rem;
  }

  .site-search .brand {
    grid-column: 1;
    justify-self: start;
  }

  .site-search-form {
    grid-column: 2;
  }
}

/* The site-wide search input reuses the field control skin. */
.site-search input {
  font: 16px/1.3 var(--font-body);
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-control);
  padding: 0.5rem 0.7rem;
  min-width: min(22rem, 80vw);
}
.site-search input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--field-ring);
}

/* Primary nav — a Salesforce-Classic-style row of tabs (left) with the persona
   controls (right). The tab strip scrolls horizontally on narrow screens so it
   stays a single row rather than wrapping. */
.site-nav {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: 1rem;
  padding: 0 1rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-head);
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
}
/* OI-00093 (02): boxed app-mark lockup. The mark asset is self-boxed, rendered as-is;
   a thin vertical divider separates it from the product wordmark. */
.brand-mark {
  display: block;
  width: 26px;
  height: 26px;
}
.brand-divider {
  width: 1px;
  height: 1.15rem;
  background: var(--line);
}
.brand-name {
  /* OI-00093 (01/02): the product wordmark is a brand moment — Raleway (--font-display),
     not the Inter that --font-head now carries. Review fix: was silently Inter. */
  font-family: var(--font-display);
  line-height: 1;
}
/* OI-00095 (02): the product lockup centred atop bare-shell auth pages
   (oauth-consent, persona-select). Login keeps the full studio logo. */
.bare-brand {
  display: flex;
  justify-content: center;
  margin: 0 0 1.5rem;
}

.nav-tabs {
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;        /* no-JS fallback: scroll the strip */
}

/* JS-enhanced (priority-plus): stop scrolling — overflowing tabs move into the
   "More" dropdown, which must be free to escape the nav box. */
.nav-tabs--managed {
  overflow: visible;
}

.nav-tabs-list {
  display: flex;
  align-items: flex-end;
  height: 100%;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav-tab a,
.nav-more-btn {
  display: block;
  padding: 0.6rem 0.9rem;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  /* OI-00095 (04): underline tabs, not folder blocks — a transparent baseline border the
     active tab colours; -1px overlaps the .site-nav bottom rule. */
  border-bottom: 2px solid transparent;
  /* Square the underline: .nav-more-btn is a <button> and would otherwise inherit the
     pill --radius-control, rounding its underline while its anchor tab siblings stay
     flush. Harmless on the anchors (already 0). */
  border-radius: 0;
  margin-bottom: -1px;
}

.nav-tab a:hover,
.nav-more-btn:hover,
.nav-more.is-open .nav-more-btn {
  color: var(--ink);
  /* Underline tabs: colour only, no fill. `transparent` also neutralises the generic
     button:hover accent fill on the .nav-more-btn <button>. */
  background: transparent;
}

.nav-tab a.is-active {
  color: var(--accent-strong);
  border-bottom-color: var(--accent);
}

/* The "More" tab: a <button> styled to match the tabs, plus its dropdown. */
.nav-more {
  position: relative;
}

.nav-more-btn {
  border: 0;
  background: transparent;
  cursor: pointer;
}

.nav-more-caret {
  display: inline-block;
  transition: transform 0.15s ease;
}

.nav-more.is-open .nav-more-caret {
  transform: rotate(180deg);
}

.nav-more-menu {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 20;
  display: none;
  min-width: 12rem;
  margin: 0;
  padding: 0.25rem;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.nav-more.is-open .nav-more-menu {
  display: block;
}

/* Dropdown entries: full-width rows, not folder tabs. */
.nav-more-menu a {
  border-radius: var(--radius-control);
  color: var(--ink);
  font-weight: 500;
}

.nav-more-menu a:hover {
  color: var(--ink);
  background: var(--accent-soft);
}

.nav-more-menu a.is-active {
  /* OI-00095 (04): quiet selected surface for the overflowed active tab — a dropdown
     row, so a soft accent surface, not the inline strip's underline. */
  color: var(--accent-strong);
  background: var(--accent-soft);
}

/* ---- Profile menu — top-right account badge + flyout (OI-00031). Consolidates
        the profile link, persona switch, theme toggle and logout. Mirrors the
        priority-plus "More" dropdown pattern above. ---- */

/* Mobile-first: the badge is pinned to the top-right corner of the search band
   (which is position:relative). Desktop reclaims it into the search grid below. */
.profile-menu {
  position: absolute;
  top: 0.85rem;
  right: 1rem;
}

.profile-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: 999px;
}
.profile-badge:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--field-ring);
}

.profile-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
}
.profile-avatar--lg {
  width: 2.5rem;
  height: 2.5rem;
  font-size: 0.95rem;
}
/* OI-00159: the avatar as a Drive-hosted photo rather than initials. Fills the same
   round frame; object-fit crops non-square uploads to the circle. Keeps the accent
   background so a broken/blocked image reads as an accent disc, not a broken-image glyph. */
.profile-avatar--photo {
  object-fit: cover;
  background: var(--accent);
}

/* OI-00093 (13): persistent identity in the account trigger. Hidden on mobile
   (avatar-only); revealed on desktop in the media query below. */
.profile-badge-identity,
.profile-badge-caret {
  display: none;
}
.profile-badge-name {
  color: var(--ink);
  font: 600 0.85rem/1.15 var(--font-body);
  max-width: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.profile-badge-org {
  color: var(--muted);
  font: 0.72rem/1.15 var(--font-body);
  max-width: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.profile-badge-caret {
  color: var(--muted);
  font-size: 0.7rem;
}

.profile-flyout {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 30;
  display: none;
  min-width: 15rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  overflow: hidden;
}
.profile-menu.is-open .profile-flyout {
  display: block;
}

.profile-flyout-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.8rem 0.9rem;
  border-bottom: 1px solid var(--line);
}
.profile-identity {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.profile-name {
  color: var(--ink);
  font-size: 0.95rem;
}
.profile-org {
  color: var(--muted);
  font-size: 0.8rem;
}

.profile-flyout-body {
  padding: 0.25rem;
}

/* Flyout rows: full-width entries (links + the logout button share one skin). */
.profile-item {
  display: block;
  width: 100%;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius-control);
  color: var(--ink);
  font: inherit;
  text-align: left;
  text-decoration: none;
}
.profile-item:hover {
  background: var(--accent-soft);
}
.profile-item--button {
  border: 0;
  background: transparent;
  cursor: pointer;
}

.profile-flyout-section {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 0.9rem;
  border-top: 1px solid var(--line);
}
.profile-flyout-label {
  color: var(--muted);
  font-size: 0.8rem;
}

.profile-flyout-foot {
  padding: 0.25rem;
  border-top: 1px solid var(--line);
}

/* Desktop: reclaim the badge into the search grid's right column (static flow),
   so it sits inline at the right edge rather than absolutely pinned. */
@media (min-width: 40rem) {
  .site-search .profile-menu {
    position: relative;
    top: auto;
    right: auto;
    grid-column: 3;
    justify-self: end;
  }
  /* OI-00093 (13): reveal the persistent identity + caret and give the trigger a
     quiet pill affordance (hover only; rest stays transparent). */
  .profile-badge {
    padding: 0.25rem 0.65rem 0.25rem 0.3rem;
  }
  .profile-badge:hover {
    background: var(--surface-2);
  }
  .profile-badge-identity {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
  }
  .profile-badge-caret {
    display: inline;
  }
}

.site-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1rem;
  border-top: 1px solid var(--line);
  color: var(--muted);
}

/* ---- Theme switcher (interim: sets a cookie; individual.theme drives it later) ---- */
.theme-switch {
  display: inline-flex;
  gap: 1px;
  padding: 2px;
  background: var(--surface-2);
  border-radius: 999px;
  font-size: 0.75rem;
}
.theme-switch a {
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  color: var(--muted);
  text-decoration: none;
}
.theme-switch a[aria-current="true"] {
  /* OI-00095 (04): quiet selected surface, not a solid accent pill (matches .seg). */
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.hero h1 {
  margin: 0 0 0.25rem;
  font-size: 1.9rem;
}

/* ---- Bare shell: login / persona-select / errors ---- */
.bare {
  display: flex;
  min-height: 100vh;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}

.bare-card {
  width: 100%;
  max-width: 22rem;
  padding: 1.75rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.bare-card h1 {
  margin-top: 0;
}

.bare-card label {
  display: block;
  margin: 0.75rem 0;
  color: var(--muted);
  font-size: 0.9rem;
}

.bare-card input {
  display: block;
  width: 100%;
  padding: 0.55rem;
  margin-top: 0.3rem;
  font: inherit;
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-control);
}
.bare-card input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--field-ring);
}

/* ---- Buttons (default/fallback accent button — the .btn system above is preferred) ---- */
button,
.persona-list button {
  font: inherit;
  cursor: pointer;
  color: var(--on-accent);
  background: var(--accent);
  border: 0;
  border-radius: var(--radius-control);
  padding: 0.6rem 0.9rem;
}
button:hover {
  background: var(--accent-strong);
}

.bare-card > form > button {
  width: 100%;
  margin-top: 0.75rem;
}

.persona-list {
  list-style: none;
  padding: 0;
}
.persona-list button {
  width: 100%;
  margin-top: 0.5rem;
  text-align: left;
}

/* ---- Login screen ---- */
/* Everything on the sign-in card is centred; scoped to .login so the shared bare
   shell (persona-select) is untouched. */
.login {
  text-align: center;
}
.login h1 {
  margin: 0 0 0.25rem;
}
.login .muted {
  margin: 0 0 1.5rem;
}
.login .error {
  margin: 0 0 1rem;
  color: var(--danger);
  font-size: 0.9rem;
}

.login-logo {
  margin-bottom: 1.5rem;
}
.brand-logo {
  width: 150px;
  height: auto;
}

/* Login logo swap — light is the base face (violet mark on paper); the white mark
   shows on the dark theme, reached the two usual ways (explicit dark + Auto-on-dark-OS). */
.brand-logo--on-dark  { display: none; }          /* white mark */
.brand-logo--on-light { display: inline-block; }  /* violet mark, light base */
[data-theme="dark"] .brand-logo--on-dark  { display: inline-block; }
[data-theme="dark"] .brand-logo--on-light { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .brand-logo--on-dark  { display: inline-block; }
  :root:not([data-theme]) .brand-logo--on-light { display: none; }
}

/* "Sign in with Google" — Google's branded button (white style, official 4-colour
   G mark). Its colours are fixed by Google's brand guidelines, so they are NOT
   tokenised. A block flex box centred in the card, so the theme switcher below it
   drops onto its own line. */
.btn-google {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  padding: 0 0.9rem;
  height: 40px;
  background: #ffffff;
  color: #1f1f1f;
  border: 1px solid #747775;
  border-radius: 4px;
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 500;
  text-decoration: none;
  transition: box-shadow 0.15s ease, background-color 0.15s ease;
}
.btn-google:hover {
  background: #f7f8f8;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 3px 1px rgba(0, 0, 0, 0.15);
}
.btn-google:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.btn-google__icon {
  display: inline-flex;
  flex: 0 0 auto;
}

/* The theme switcher sits on its own line beneath the button, centred by the
   .login text-align. */
.login .theme-switch {
  margin-top: 1.5rem;
}

/* ---- OAuth consent screen (OI-00066/OI-00081) ---- */
/* Wrapping the page in .oauth-consent (unlike persona-select, which has no wrapper)
   takes the form out from directly under .bare-card, so it needs its own full-width
   button rule rather than inheriting `.bare-card > form > button`. Approve/Deny use
   the shared .btn system (.btn-primary / .btn-secondary) instead of the generic
   accent-purple <button> fallback, so Deny reads as the non-default action. */
.oauth-consent form button {
  display: block;
  width: 100%;
  margin-top: 0.75rem;
  justify-content: center;
}
/* Extra breathing room + centred (the buttons above are full-width/left-edge-aligned,
   so the switcher needs its own centring rather than inheriting text-align). */
.oauth-consent .theme-switch {
  display: flex;
  width: fit-content;
  margin: 2rem auto 0;
}

/* ===================================================================== *
 * MOTION — tasteful animation (OI-00042). Pure CSS + native APIs, no deps.
 * Extends the existing 0.12–0.18s ease micro-transition idiom. Everything here
 * is progressive (unsupported browsers get today's instant behaviour) and is
 * neutralised by the prefers-reduced-motion guard directly below.
 * ===================================================================== */

/* Tier 1a — cross-document View Transitions. Server-rendered MPA: the browser
   cross-fades between full-page navigations (list → record → edit). No JS. */
@view-transition {
  navigation: auto;
}

/* Tier 1b — open/close menu reveal. The "More" nav dropdown, profile flyout and
   lookup results menu toggle display:none; animate that fade+slide natively via
   @starting-style + display's allow-discrete transition (no JS state change —
   the existing .is-open / :focus-within toggles still drive it). */
.nav-more-menu,
.profile-flyout,
.lookup-menu {
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.12s ease, transform 0.12s ease,
    display 0.12s allow-discrete;
}
.nav-more.is-open .nav-more-menu,
.profile-menu.is-open .profile-flyout,
.lookup:focus-within .lookup-menu {
  opacity: 1;
  transform: translateY(0);
}
@starting-style {
  .nav-more.is-open .nav-more-menu,
  .profile-menu.is-open .profile-flyout,
  .lookup:focus-within .lookup-menu {
    opacity: 0;
    transform: translateY(-4px);
  }
}

/* Tier 2a — button press. Hover states already exist; give the *press* a tactile
   nudge (the primary also eases the hover colour swap now it has a transition). */
.btn {
  transition: transform 0.08s ease, background-color 0.12s ease,
    border-color 0.12s ease, color 0.12s ease;
}
.btn:active:not(:disabled) {
  transform: translateY(1px);
}
.btn-primary:active:not(:disabled) {
  transform: translateY(1px) scale(0.98);
}

/* Tier 2b — save-in-flight. form-submit.js adds .is-loading to the submit button
   on POST; hide its label and spin a ring so the gap before the redirect isn't
   dead air. Spinner uses --on-accent (the primary Save/Create button is accent). */
.btn.is-loading {
  position: relative;
  color: transparent !important;
  pointer-events: none;
}
.btn.is-loading::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border: 2px solid var(--on-accent);
  border-top-color: transparent;
  border-radius: 999px;
  animation: btn-spin 0.6s linear infinite;
}
@keyframes btn-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
    animation: none !important;   /* also kill keyframe animations, not just transitions */
  }
  /* Opt out of cross-document page transitions entirely for reduced-motion users. */
  @view-transition {
    navigation: none;
  }
}

/* ===================================================================== *
 *  Record page — templated view (docs/page-layout-design.md)
 *  Generic layout: header + field sections + related-list tables. Themed
 *  via the shared tokens so it holds up in both Light and Dark.
 * ===================================================================== */

/* Record pages run full-bleed — flow to the screen edges rather than the
   reading-width default container (only the container's gutter remains). Object
   list pages (OI-00044) run full-bleed too — a wide table wants the room. */
.container:has(.record),
.container:has(.record-missing),
.container:has(.list-page) {
  max-width: none;
}
/* Edit / create forms are the exception: a single input column gains nothing from the
   full-bleed room a wide record view or list table wants, so pull them back to ~2/3 of
   a standard desktop (960px). Same specificity as the full-bleed rule above but later in
   source order, so it wins for the .record-form page (which also carries .record). */
.container:has(.record-form) {
  max-width: 60rem;
}

/* Record body layout. Mobile: single column — header, then detail sections,
   then related lists stacked (existing block margins provide the spacing).
   Desktop (>=64rem): two columns — detail sections at up to 1/3 on the left,
   capped at a readable 28rem on wide screens, and related lists in the remaining
   space on the right. The header spans the full width above. */
.record {
  display: grid;
  grid-template-columns: 1fr;
  column-gap: 2rem;
}
.record-header {
  grid-column: 1 / -1;
}
@media (min-width: 64rem) {
  .record {
    grid-template-columns: minmax(0, min(33.333%, 28rem)) minmax(0, 1fr);
    align-items: start;
  }
  /* A record with no related lists has nothing for the right column — let the
     detail sections use the full width rather than stranding 2/3 empty. */
  .record--no-related {
    grid-template-columns: 1fr;
  }
}

/* Record-view panels + panel-level tabs (OI-00055). A panel is one column of the
   record view (Details left, Related right), each an instance of the reusable
   panel-tab widget. A single-tab panel renders no tab bar (below), so it is
   visually identical to the pre-OI-00055 view. Panels stack on mobile (this gap),
   sit side-by-side on desktop via the .record grid. Distinct from the within-list
   .tabs/.tab widget (grid.js). */
.panel--related {
  margin-top: 1.5rem;
}
/* Desktop: both panels start together under the header, so their tab bars line up —
   drop the stacked-mode gap. This override MUST come after the base rule above, or
   equal specificity + source order would let the 1.5rem win even on desktop. */
@media (min-width: 64rem) {
  .panel--related {
    margin-top: 0;
  }
}
/* Tabbed list page (OI-00087): the panel is the full-width main area (no .record grid
   to constrain it), so it fills the page. Just space it under the list header. */
.panel--list {
  margin-top: 0.25rem;
}
/* Let each record column hold its fractional share instead of stretching to a wide
   table's min-content — a grid/flex item defaults to min-width:auto (=min-content), so a
   long, unbreakable table cell would otherwise force the 2/3 panel wider and squeeze the
   1/3 detail panel. Paired with the fixed table layout + cell ellipsis below, wide cells
   now clip instead of push. */
.record > .panel {
  min-width: 0;
}
.panel-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 1.1rem;
  border-bottom: 1px solid var(--line);
}
.panel-tab {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  /* Square the underline: as a <button> this would inherit the pill --radius-control
     (line ~1415), which rounds the 2px underline's ends into a short lifted marker on
     narrow tabs. Underline tabs must sit flush, matching the anchor-based .nav-tab. */
  border-radius: 0;
  margin-bottom: -1px;
  padding: 0.5rem 0.85rem;
  font: 600 0.9rem/1.2 var(--font-head);
  color: var(--muted);
  cursor: pointer;
}
.panel-tab:hover {
  color: var(--ink);
  /* Neutralise the generic `button:hover` accent fill (it would land the active
     tab's accent-strong text on an accent-strong ground → unreadable). These are
     underline tabs: colour + border only, no fill, in both themes. */
  background: transparent;
}
.panel-tab.is-active {
  color: var(--accent-strong);
  border-bottom-color: var(--accent);
}
.panel-tabpanel[hidden] {
  display: none;
}
/* "Coming soon" placeholder tab body (e.g. org's Contact Hierarchy) — a centred,
   muted stub. Theme tokens so it reads in light + dark. */
.tab-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  padding: 2.5rem 1rem;
  text-align: center;
  border: 1px dashed var(--line);
  border-radius: 8px;
  background: var(--surface-2);
}
.tab-placeholder-title {
  margin: 0;
  font: 600 0.95rem/1.2 var(--font-head);
  color: var(--ink);
}
.tab-placeholder .muted {
  margin: 0;
  max-width: 32rem;
}

/* History tab (OI-00055) — a denser table (a lot per row) grouped by change-set.
   The group header carries the actor/via/timestamp; leaf rows carry field + Old/New.
   With JS off the flat table shows the actor block on every row (a readable log). */
.history-table {
  font-size: 0.8rem;
}
.history-table td,
.history-table th {
  padding: 0.4rem 0.6rem;
  white-space: normal; /* Old/New values wrap rather than force horizontal scroll */
  vertical-align: top;
}
.hist-actor {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
}
.hist-who {
  font-weight: 600;
  color: var(--ink);
}
.hist-via,
.hist-at {
  color: var(--muted);
  font-size: 0.72rem;
}
.hist-change {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.hist-k {
  color: var(--muted);
  font-weight: 600;
  margin-right: 0.15rem;
}
.hist-old {
  color: var(--muted);
}

/* Grouped view (JS on): a spanning header row per change-set + indented leaf rows. */
.hist-group-row > .hist-group-cell {
  background: var(--surface-2);
  padding: 0;
}
.hist-group-toggle {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  width: 100%;
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0.45rem 0.6rem;
  font: inherit;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.hist-caret {
  color: var(--muted);
  font-size: 0.7rem;
  line-height: 1.4;
}
.hist-group-head .hist-actor {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}
.hist-group-count {
  margin-left: auto;
  min-width: 1.2em;
  padding: 0 0.4em;
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-size: 0.7rem;
  text-align: center;
}
.hist-leaf-row > td:first-child {
  padding-left: 1.6rem;
}

.record-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  padding-bottom: 1rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--line);
}
.record-eyebrow {
  margin: 0 0 0.15rem;
  font: 600 0.72rem/1.2 var(--font-head);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.record-title {
  margin: 0;
  font: 700 1.7rem/1.15 var(--font-head);
  color: var(--ink);
}
.record-header-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.85rem;
}
.record-meta {
  font-size: 0.85rem;
  color: var(--ink);
}
.record-meta-k {
  color: var(--muted);
  margin-right: 0.15rem;
}

/* Sections — flat cards, quiet 1px border (OI-00093 06: accent top edge dropped). */
.record-sections {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}
.rec-section {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.15rem 1.15rem;
}
.rec-section-title {
  margin: 0 0 0.85rem;
  font: 600 0.78rem/1.2 var(--font-head);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Field grid — single column on mobile; two columns at width when cols-2. */
.field-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.85rem 1.5rem;
}
@media (min-width: 40rem) {
  .field-grid.cols-2 {
    grid-template-columns: 1fr 1fr;
  }
  /* OI-00165: a 3-column section (the time-entry Time & billing card walks the maths
     across the row) — uses horizontal space to save vertical room. */
  .field-grid.cols-3 {
    grid-template-columns: 1fr 1fr 1fr;
  }
}
/* A field placement flagged fullWidth spans the whole grid (both columns of a
   2-col section; a no-op in a 1-col section, which is already full width). */
.fv-full {
  grid-column: 1 / -1;
}
.fv-label {
  font: 600 0.75rem/1.3 var(--font-head);
  color: var(--muted);
  margin-bottom: 0.15rem;
}
.fv-value {
  font-size: 0.95rem;
  color: var(--ink);
  word-break: break-word;
}
.fv-value a {
  color: var(--accent-strong);
}
.fv-empty {
  color: var(--muted);
  font-style: italic;
}
.fv-money {
  font-variant-numeric: tabular-nums;
}
.fv-multiline {
  white-space: pre-wrap;
}
/* Rendered Markdown (OI-00107) — pre-rendered to sanitized HTML server-side
   (renderMarkdown, `html: false`) and inlined directly into the page DOM (a known,
   closed tag set — no iframe, unlike .fv-html-frame's arbitrary untrusted HTML). Themed
   via the same --ink/--muted/--border tokens as the rest of the record view. */
.fv-markdown {
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--ink);
}
.fv-markdown > :first-child {
  margin-top: 0;
}
.fv-markdown > :last-child {
  margin-bottom: 0;
}
.fv-markdown h1,
.fv-markdown h2,
.fv-markdown h3,
.fv-markdown h4,
.fv-markdown h5,
.fv-markdown h6 {
  font: 600 1em/1.3 var(--font-head);
  margin: 1.2em 0 0.4em;
}
.fv-markdown p,
.fv-markdown ul,
.fv-markdown ol,
.fv-markdown blockquote,
.fv-markdown table {
  margin: 0 0 0.75em;
}
.fv-markdown ul,
.fv-markdown ol {
  padding-left: 1.4em;
}
.fv-markdown a {
  color: var(--accent-strong);
}
.fv-markdown code {
  font-family: var(--font-mono);
  background: var(--code-bg);
  padding: 0.1em 0.3em;
  border-radius: 4px;
  font-size: 0.85em;
}
.fv-markdown pre {
  font-family: var(--font-mono);
  background: var(--code-bg);
  padding: 0.75em 1em;
  border-radius: 6px;
  overflow-x: auto;
  margin: 0 0 0.75em;
}
.fv-markdown pre code {
  background: none;
  padding: 0;
}
.fv-markdown blockquote {
  border-left: 3px solid var(--border, rgba(0, 0, 0, 0.14));
  padding-left: 0.9em;
  color: var(--muted);
}
.fv-markdown table {
  border-collapse: collapse;
}
.fv-markdown th,
.fv-markdown td {
  border: 1px solid var(--border, rgba(0, 0, 0, 0.14));
  padding: 0.35em 0.6em;
}
.fv-markdown hr {
  border: none;
  border-top: 1px solid var(--border, rgba(0, 0, 0, 0.14));
  margin: 1em 0;
}
/* Rendered untrusted HTML (e.g. an email body) — a sandboxed, script-less iframe (see
   field-value.eta). The email's own CSS is isolated by the iframe; the frame keeps a
   FIXED LIGHT canvas in both light and dark themes (a deliberate theming exception —
   email HTML is authored against white). Fixed height because a no-script sandbox can't
   self-report content height, so it scrolls internally. */
.fv-html-frame {
  display: block;
  width: 100%;
  height: 32rem;
  border: 1px solid var(--border, rgba(0, 0, 0, 0.14));
  border-radius: 6px;
  background: #ffffff;
}
/* Composite value — each part on its own line (e.g. actor / "via {agent}" /
   when), so attribution reads as a small stacked block rather than one run-on. */
.fv-composite {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.fv-conn {
  color: var(--muted);
  font-style: italic;
  margin-right: 0.3rem;
}

/* ---- Edit / new form page (OI-00029) — the write-side of the record page. ---- */
/* An <a> styled as a .btn (the Cancel link) must shed the link underline/colour. */
a.btn {
  text-decoration: none;
}
.form-error {
  margin-bottom: 1.25rem;
  padding: 0.7rem 0.9rem;
  border-radius: var(--radius);
  background: var(--danger-soft);
  color: var(--danger-ink);
  border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent);
  font-size: 0.9rem;
}
/* Edit/create form actions (OI-00062). One set right-aligned in the header (its
   flex parent handles the alignment), a second in a fixed bottom dock that reveals as
   the header set scrolls off. Both live outside <form> and submit via form=. */

/* Header set doubles as the scroll-driven timeline subject: the dock's reveal is
   driven by this element exiting the top of the viewport. */
.form-actions--header {
  view-timeline-name: --header-actions;
  view-timeline-axis: block;
}

/* Delete sits first in the action row, held apart from the Save/Cancel pair by an
   extra end-margin (on top of the row gap) so the destructive button never abuts the
   primary one (misclick guard). A fixed margin, not `auto`: the header row is sized to
   its content, so an auto margin would have no free space to consume there — it only
   works in a full-width flex parent (WI-00328). */
.form-actions-delete {
  margin-inline-end: 1.5rem;
}

/* The article scopes the named timeline so the (fixed, non-descendant-in-layout but
   still DOM-descendant) dock can consume it. */
.record-form {
  timeline-scope: --header-actions;
}
/* Reserve space at the page bottom so the fixed dock never covers the footer (or the
   last fields). The dock is out of flow, so nothing reserves its height otherwise;
   :has() scopes this to the pages that actually render a dock — no global whitespace. */
body:has(.form-actions--dock) {
  padding-bottom: 5.5rem;
}

/* Fixed, centred dock. Revealed once the header actions leave the top, via a
   pure-CSS scroll-driven animation. Where animation-timeline is unsupported it is
   ignored and this 0s animation snaps to its fill (the `to` keyframe = visible) —
   a visible-by-default fallback. `visibility` (not just opacity) so the hidden bar
   can't intercept clicks over the content beneath it. */
.form-actions--dock {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 20;
  justify-content: center;
  padding: 0.85rem 1rem;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(6px);
  border-top: 1px solid var(--line);
  animation: form-dock-reveal both;
  animation-timeline: --header-actions;
  animation-range: exit;
}
@keyframes form-dock-reveal {
  from {
    opacity: 0;
    visibility: hidden;
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
  }
}

/* Address section — the composed single-line address, then the map stacked
   beneath it, each spanning the full width of the section. */
.address-block {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.address-line {
  margin: 0;
  font-size: 0.95rem;
  color: var(--ink);
}
.section-map {
  min-height: 220px;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--surface-2);
}
.map-frame {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 220px;
  border: 0;
}
.map-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  height: 100%;
  min-height: 220px;
  padding: 1rem;
  text-align: center;
}
.map-placeholder-title {
  margin: 0;
  font: 600 0.95rem/1.2 var(--font-head);
  color: var(--ink);
}
.map-placeholder .muted {
  margin: 0;
  font-size: 0.82rem;
}

/* Map stand-in — reads as a deliberate "map goes here" panel, not an error. A
   faint grid + dashed edge evoke a map tile; drops in the real iframe once a key
   is configured. */
.map-stub {
  border: 1px dashed var(--field-border);
  color: var(--muted);
  background-color: var(--surface-2);
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 28px 28px;
  background-position: center;
}
.map-stub-pin {
  color: var(--accent);
  opacity: 0.75;
}

/* Related lists ---------------------------------------------------------- */
.record-related {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  /* Stacking gap between the two panels lives on .panel--related now (OI-00055),
     so this block no longer carries a top margin of its own. */
}
.related-list {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.15rem 1.15rem;
}
.related-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.related-title,
.grid-title {
  margin: 0;
  font: 700 1.05rem/1.2 var(--font-head);
  color: var(--ink);
}
/* The related list's own "New <child>" button (top-right); list pages have none. */
.related-new {
  flex: none;
}

/* Object list (collection) pages (OI-00027) — reuse the related-list table component
   whole. The page owns the heading (list-header), so the reused table's own section
   title is redundant and hidden; the tabs + table below carry the page. */
.list-header {
  margin-bottom: 1.1rem;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
}
.list-eyebrow {
  margin: 0 0 0.15rem;
  font: 600 0.72rem/1.2 var(--font-head);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.list-title {
  margin: 0;
  font: 700 1.7rem/1.15 var(--font-head);
  color: var(--ink);
}
.list-page .related-title {
  display: none;
}
.list-capped {
  margin: 0.85rem 0 0;
  font-size: 0.85rem;
}

/* Tabs — one fetched set, server-partitioned; buttons toggle panels. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.75rem;
  border-bottom: 1px solid var(--line);
}
.tab {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  /* Square the underline — see .panel-tab: a <button> would inherit the pill radius and
     round the underline ends. Flush, to match the anchor-based nav tabs. */
  border-radius: 0;
  margin-bottom: -1px;
  padding: 0.4rem 0.7rem;
  font: 600 0.85rem/1.2 var(--font-head);
  color: var(--muted);
  cursor: pointer;
}
.tab:hover {
  color: var(--ink);
  /* Same fix as .panel-tab:hover — keep the generic button:hover accent fill off
     these within-list underline tabs so the active tab stays legible. */
  background: transparent;
}
.tab.is-active {
  color: var(--accent-strong);
  border-bottom-color: var(--accent);
}
.tab-count {
  display: inline-block;
  min-width: 1.2em;
  padding: 0 0.35em;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--muted);
  font-size: 0.72rem;
  text-align: center;
}
.tab.is-active .tab-count {
  background: var(--accent-soft);
  color: var(--accent-strong);
}

/* Table — semantic, server-rendered; overflow scoped to its own wrapper so a
   wide table never drags the page body sideways (mandatory, per the design). */
.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
/* WI-00349: bound each table's OWN height so it becomes a vertical scroll container — that
   is what makes the sticky `<thead>` (below) actually pin. Without a bounded height the
   wrapper grows to fit all rows, the PAGE scrolls instead, and `position: sticky; top: 0`
   sticks to a wrapper that never scrolls — so the header scrolled away (the reported bug).
   Related lists cap at ~10 rows (scroll within each; the page scrolls between tables); a
   full-page list — and the WBS — fills the viewport so the page shows a single scrollbar.
   The 30rem / 16rem figures are deliberately round and easy to tune. */
.table-wrap:not(.work-tree-wrap) {
  overflow-y: auto;
  max-height: 30rem; /* related / record-page lists: ~10–11 rows + the sticky header */
}
.list-page .table-wrap:not(.work-tree-wrap) {
  /* Full-page list: fill the viewport minus the surrounding chrome (nav, list header,
     toolbar, pager, footer) so there's one scrollbar — the table's. Tune the 16rem.
     Extra `:not()` only to outrank the related-list default unambiguously (same class
     count otherwise). */
  max-height: calc(100dvh - 16rem);
}
/* The WBS fills the viewport like a full-page list (its own scrollbar, sticky header). Its
   subtree box overlays (grid.js drawTreeBoxes) are children of this positioned wrapper and
   are redrawn on the wrapper's own scroll, so they stay aligned under internal scroll. */
.work-tree-wrap {
  overflow-y: auto;
  max-height: calc(100dvh - 16rem);
}
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.data-table th,
.data-table td {
  text-align: left;
  padding: 0.55rem 0.7rem;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
/* The primary (title/name) column is the only unbounded one — a long value would widen
   the table and (with min-width:0 on the record panels) is the thing that squeezed the
   1/3 detail panel. Cap + ellipsis it so it clips with "…" instead of pushing; the other
   columns keep sizing to their content, so dates / chips / roles stay fully visible.
   The cap is on the cell content (a block link, or the cell itself for non-link primary
   columns) so it holds under the table's auto layout. */
.data-table td.cell-primary {
  max-width: 22rem;
}
.data-table td.cell-primary a {
  display: inline-block; /* shrink to content, but cap + clip when longer than max-width */
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}
/* WI-00349: the primary column was the only bounded one — every OTHER free-text cell
   (a resolved FK name like the timesheet "Target", a role, a note) sized to its longest
   content under the auto table-layout and dragged the whole table wide. Cap them the same
   way: nowrap is already inherited (above), so max-width + overflow + ellipsis clips a long
   value with "…" instead of pushing. On the shared component class, so EVERY current and
   future list / related-list table is bounded by construction — a new free-text column
   can't reintroduce the blow-out. Scoped away from the WBS (`.work-tree-table`), which owns
   its own `table-layout: fixed` widths + truncation (and resets these on mobile). A column
   that genuinely needs to be wider opts out deliberately via `.cell-primary`. */
.data-table:not(.work-tree-table) td:not(.cell-primary) {
  max-width: 24rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* WI-00349: pin the header so column labels stay visible however far you scroll a long
   list / related list / WBS. The page body is the scroll container and `.site-nav` is NOT
   fixed (it scrolls away), so `top: 0` sticks the header to the viewport top with no offset.
   `.work-tree-table` shares `.data-table`, so this one rule covers the WBS too (its mobile
   thead is visually hidden, so sticky is inert there). border-collapse can drop the th's
   bottom border while stuck, so carry the divider as an inset shadow; opaque background so
   rows never show through; z-index above the WBS box-layer lines (z-index: 2). */
.data-table thead th {
  font: 600 0.72rem/1.2 var(--font-head);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--surface);
  box-shadow: inset 0 -1px 0 var(--line);
}
.data-table th.sortable {
  cursor: pointer;
  user-select: none;
}
.data-table th.sortable:hover {
  color: var(--ink);
}
.data-table th.sortable::after {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  margin-left: 0.35em;
  vertical-align: middle;
  opacity: 0.35;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
}
.data-table th.sortable[aria-sort="ascending"]::after {
  opacity: 0.9;
  border-top: 0;
  border-bottom: 5px solid currentColor;
}
.data-table th.sortable[aria-sort="descending"]::after {
  opacity: 0.9;
}
.data-table th.sortable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.data-table tbody tr:last-child td {
  border-bottom: 0;
}
.data-table tbody tr.row-link {
  cursor: pointer;
}
.data-table tbody tr.row-link:hover {
  background: var(--surface-2);
}
.data-table td.cell-primary a {
  color: var(--accent-strong);
  font-weight: 600;
  text-decoration: none;
}
.data-table td.cell-primary a:hover {
  text-decoration: underline;
}
.table-empty {
  color: var(--muted);
  font-style: italic;
  margin: 0.25rem 0 0;
}

/* Client grid (OI-00049) — /js/grid.js builds a TanStack-driven grid over the flat
   transport table. The toolbar (search) sits above the grid; the pager below. With
   JS off, none of this renders — the flat table alone stays as the working fallback. */
/* OI-00093 (11): one compact toolbar — identity + count + tabs + filter + action,
   table left quiet below. */
.grid-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  margin-bottom: 0.6rem;
}
.grid-identity {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
}
.grid-count {
  font: 600 0.8rem/1 var(--font-body);
  color: var(--muted);
}
/* Tabs inline in the toolbar (partitions) — drop the standalone bottom margin. */
.grid-tabs {
  margin-bottom: 0;
}
/* Push the filter + action to the right edge of the toolbar. */
.grid-search {
  margin-left: auto;
  width: min(16rem, 100%);
  padding: 0.4rem 0.65rem;
  font: 400 0.85rem/1.2 var(--font-body);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
}
.grid-search:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}
/* "No matches" inside the grid body (search filtered everything out). */
.data-table td.table-empty-cell {
  color: var(--muted);
  font-style: italic;
  text-align: center;
  padding: 0.9rem 0.7rem;
}
/* OI-00093 (05a): quiet range footer — "‹ 1–25 of 42 ›" left, "Display N" right.
   Low-contrast; replaces the rejected explicit Columns/Page X of Y/Prev/Next. */
.grid-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  margin-top: 0.7rem;
}
.grid-pager-nav {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.grid-pager-range {
  font: 0.8rem/1.2 var(--font-body);
  color: var(--muted);
}
.grid-pager-btn {
  appearance: none;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  cursor: pointer;
}
.grid-pager-arrow {
  padding: 0.1rem 0.5rem;
  font: 1.05rem/1 var(--font-body);
  color: var(--muted);
}
.grid-pager-btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent-strong);
}
.grid-pager-btn:disabled {
  color: var(--muted);
  cursor: default;
  opacity: 0.5;
}
.grid-pager-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.grid-pager-display {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font: 0.8rem/1.2 var(--font-body);
  color: var(--muted);
}
.grid-pager-size {
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  padding: 0.1rem 0.35rem;
}

.record-missing {
  text-align: center;
  padding: 3rem 1rem;
}

/* =====================================================================
 * Universal search results page (OI-00061). A reading-width column of
 * grouped hits; the endpoint ranks within each group. Reuses the field +
 * button skins and the shared tokens so it holds in Light and Dark.
 * ===================================================================== */
.search-page {
  max-width: 52rem;
  margin: 0 auto;
}
.search-header {
  margin-bottom: 1.25rem;
}
.search-form {
  display: flex;
  gap: 0.6rem;
  margin-top: 0.35rem;
}
.search-form input {
  flex: 1 1 auto;
  font: 16px/1.3 var(--font-body);
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-control);
  padding: 0.55rem 0.75rem;
}
.search-form input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--field-ring);
}
.search-summary {
  margin: 0 0 1.5rem;
  font-size: 0.9rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  align-items: baseline;
}
.search-back {
  font: 600 0.85rem/1.2 var(--font-head);
  color: var(--accent-strong);
  text-decoration: none;
}
.search-back:hover {
  text-decoration: underline;
}
.search-empty {
  padding: 2rem 0;
}
.search-group {
  margin-bottom: 1.75rem;
}
.search-group-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0 0 0.5rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--line);
}
.search-group-name {
  font: 600 0.78rem/1.2 var(--font-head);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.search-group-count {
  font: 600 0.78rem/1.2 var(--font-head);
}
.search-hits {
  list-style: none;
  margin: 0;
  padding: 0;
}
.search-hit + .search-hit {
  border-top: 1px solid var(--line);
}
.search-hit-link {
  display: block;
  padding: 0.6rem 0.7rem;
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
}
.search-hit-link:hover {
  background: var(--surface-2);
}
.search-hit-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.search-hit-label {
  display: block;
  font: 600 1rem/1.3 var(--font-body);
  color: var(--ink);
}
.search-hit-snippet {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.87rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.search-more {
  display: inline-block;
  margin-top: 0.6rem;
  padding-left: 0.7rem;
  font: 600 0.85rem/1.2 var(--font-head);
  color: var(--accent-strong);
  text-decoration: none;
}
.search-more:hover {
  text-decoration: underline;
}
.search-hit mark {
  background: var(--accent-soft);
  color: inherit;
  border-radius: 3px;
  padding: 0 1px;
}
/* ── Dependency Gantt (OI-00058) ─────────────────────────────────────────────
   frappe-gantt is themed via its own --g-* custom properties (default: light
   greys) and only flips dark on html[data-theme=dark] — it has no
   prefers-color-scheme fallback. We remap --g-* onto our brand tokens, scoped to
   .gantt-container, and add the auto/system-dark path frappe lacks, so the chart
   tracks the app in Light, Dark, AND Auto. */
.gantt-section { margin-top: 1.5rem; }
.gantt-wrap { border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }

.gantt-container {
  --g-arrow-color: var(--muted);
  --g-bar-color: var(--surface-2);
  --g-bar-border: var(--line);
  --g-tick-color: var(--line);
  --g-tick-color-thick: var(--line);
  --g-actions-background: var(--surface-2);
  --g-border-color: var(--line);
  --g-text-muted: var(--muted);
  --g-text-light: var(--on-accent);
  --g-text-dark: var(--ink);
  --g-progress-color: var(--accent-soft);
  --g-handle-color: var(--accent);
  --g-header-background: var(--surface);
  --g-row-color: var(--surface);
  --g-row-border-color: var(--line);
  --g-today-highlight: var(--accent);
  --g-weekend-highlight-color: var(--surface-2);
}

/* Auto/system dark — the app goes dark via prefers-color-scheme with no data-theme
   attribute; frappe would otherwise stay light. Mirror the app's dark set. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .gantt-container {
    --g-arrow-color: var(--muted);
    --g-bar-color: var(--surface-2);
    --g-bar-border: var(--line);
    --g-progress-color: var(--accent-soft);
    --g-handle-color: var(--accent-strong);
    --g-header-background: var(--surface);
    --g-row-color: var(--surface);
    --g-text-dark: var(--ink);
    --g-text-muted: var(--muted);
  }
}

/* Connection handle (patch piece #1) — always visible + branded (frappe hides
   resize handles until hover; the connect affordance should be discoverable). */
.gantt .handle.connect { fill: var(--accent); opacity: 0.85; cursor: crosshair; }
.gantt .handle.connect:hover, .gantt .handle.connect.active { opacity: 1; }
/* Rubber-band + drop-target highlight (patch piece #2). */
.gantt .rubber-band { stroke: var(--accent); stroke-width: 2; stroke-dasharray: 4 3; pointer-events: none; }
.gantt .bar-wrapper.connect-target .bar { stroke: var(--accent-strong); stroke-width: 3; }
/* Dependency arrows are clickable to delete (patch piece #5). */
.gantt .arrow-path { pointer-events: stroke; cursor: pointer; }
.gantt .arrow-path:hover { stroke: var(--danger); stroke-width: 2; }
/* Focal item stands out; foreign (cross-project, read-only) items are muted + dashed. */
.gantt .bar-wrapper.gantt-focal .bar { stroke: var(--accent-strong); stroke-width: 2; }
.gantt .bar-wrapper.gantt-foreign .bar { opacity: 0.55; stroke-dasharray: 4 3; }
.gantt .bar-wrapper.gantt-foreign .handle.connect { display: none; }
/* OI-00095 (01): gantt text inherits Inter — the vendored frappe-gantt CSS hardcodes
   font-family:Helvetica on data labels; override here rather than patch the vendored file. */
.gantt text, .gantt .bar-label { font-family: var(--font-head); }

.gantt-status { font-size: 0.85rem; margin: 0.5rem 0 0; min-height: 1.2em; }
.gantt-status.is-ok { color: var(--ok-ink, var(--ok)); }
.gantt-status.is-error { color: var(--danger-ink, var(--danger)); }
.gantt-note { font-size: 0.8rem; margin: 0.25rem 0 0; }
.gantt-fallback-edges { font-size: 0.85rem; color: var(--muted); margin-top: 0.5rem; }

/* ── Work Item hierarchy (OI-00057) ─────────────────────────────────────────
   Owned server-rendered tree-grid inside the Related panel's WBS tab. JavaScript
   only toggles [hidden] on descendant rows, so the complete table remains the
   fallback. */
.work-tree-section {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  padding: 1rem;
  min-width: 0;
}
.work-tree-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.65rem;
}
.work-tree-summary { margin: 0; color: var(--muted); font-size: 0.8rem; }
.work-tree-actions { flex-wrap: wrap; justify-content: flex-end; }
/* Fixed layout so every column, and so every battery, keeps a stable width regardless of
   content: a long fee figure or a warning wraps inside its column instead of stretching
   it. Progress, Time and Fees share one width, so their bars line up (OI-00059). */
.work-tree-table { table-layout: fixed; width: 100%; }
/* OI-00126: Progress/Time/Fees kept equal and just wide enough for the "Progress (i)"
   header to stay on ONE line (11%); the Work item name column takes the rest. The three
   metric columns are identical so their bars line up exactly. */
.work-tree-table th:nth-child(1) { width: 40%; }  /* Work item — as wide as possible */
.work-tree-table th:nth-child(2) { width: 10%; }  /* Status */
.work-tree-table th:nth-child(3),                 /* Progress */
.work-tree-table th:nth-child(5),                 /* Time */
.work-tree-table th:nth-child(6) { width: 11%; }  /* Fees (Progress/Time/Fees equal) */
.work-tree-table th:nth-child(4) { width: 9%; }   /* Priority (WI-00312) */
.work-tree-table th:nth-child(7) { width: 8%; }   /* Assignee */
.work-tree-item {
  display: flex;
  align-items: flex-start;
  gap: 0.35rem;
  padding-left: calc((var(--tree-depth) - 1) * 1.25rem);
  min-width: 14rem;
}
.work-tree-toggle,
.work-tree-toggle-spacer {
  flex: 0 0 1.35rem;
  width: 1.35rem;
  height: 1.35rem;
}
.work-tree-toggle {
  appearance: none;
  display: inline-grid;
  place-items: center;
  padding: 0;
  margin: 0.05rem 0 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.work-tree-toggle:hover { background: var(--surface-2); color: var(--ink); }
.work-tree-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.work-tree-toggle svg { transition: transform 120ms ease; }
.work-tree-toggle[aria-expanded="false"] svg { transform: rotate(-90deg); }
.work-tree-copy { flex: 1 1 auto; min-width: 0; }
.work-tree-copy > a {
  display: flex !important;
  width: 100%;
  gap: 0.38rem;
  align-items: baseline;
  max-width: 34rem !important;
}
.work-tree-number {
  flex: 0 0 auto;
  color: var(--ink);                       /* OI-00126 item 2: WI number is black, not muted grey */
  font: 400 0.75rem/1.3 var(--font-head);  /* unbolded (was 600) */
}
.work-tree-name { overflow: hidden; text-overflow: ellipsis; }
.work-tree-meta { display: block; margin-top: 0.15rem; color: var(--muted); font-size: 0.75rem; }

/* Narrow screens turn each tree row into a compact card so all four values remain
   readable without a sideways page/table scroll. Indentation still communicates
   depth, capped naturally by the schema's 16-level maximum. */
@media (max-width: 39.99rem) {
  .work-tree-section { padding: 0.8rem; }
  .work-tree-head { align-items: flex-start; flex-direction: column; }
  .work-tree-actions { justify-content: flex-start; }
  /* Cards, not a bounded scroll box, on mobile — undo the desktop height cap. */
  .work-tree-wrap { overflow-x: visible; overflow-y: visible; max-height: none; }
  .work-tree-table,
  .work-tree-table tbody,
  .work-tree-table tr,
  .work-tree-table td { display: block; width: 100%; }
  .work-tree-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .work-tree-table tr { padding: 0.55rem 0; border-bottom: 1px solid var(--line); }
  .work-tree-table tr:last-child { border-bottom: 0; }
  .work-tree-table td,
  .work-tree-table td.cell-primary { max-width: none; padding: 0.22rem 0.25rem; border: 0; white-space: normal; }
  /* Match the desktop first-child selector's specificity: its width:52% otherwise
     wins over the generic mobile td width and crushes deep Work Item names. */
  .work-tree-table td:first-child { width: 100%; }
  .work-tree-table td:not(.work-tree-primary) {
    display: grid;
    grid-template-columns: 5.5rem minmax(0, 1fr);
    gap: 0.5rem;
    padding-left: calc(1.95rem + (var(--tree-depth, 1) - 1) * 1.25rem);
  }
  .work-tree-table td:not(.work-tree-primary)::before {
    content: attr(data-label);
    color: var(--muted);
    font: 600 0.7rem/1.5 var(--font-head);
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }
  .work-tree-item { min-width: 0; }
  .work-tree-copy > a { max-width: 100% !important; white-space: normal; }
}

/* OI-00059 Work Breakdown Structure rollups — Progress/Time/Fees batteries, Forecast,
   and the Project-level batteries. Fill colour carries meaning: blue = in progress,
   green = complete and low risk, red = at risk (over, or a closed item at least 25%
   under). Never orange, and never colour alone (numbers + a ⚠ label stay visible). */
.work-tree-table th:nth-child(n + 3) { white-space: normal; }
/* OI-00126 (battery Option 2): every metric cell is bar + one number line (warnings are an
   inline ⚠ icon, not a reserved row; Fees is compact so it never wraps), so all three cells
   are the same height. That lets the Progress/Time/Fees bars align across columns AND sit
   vertically centred in the row. */
.work-tree-table td.wt-metric { vertical-align: middle; }
/* The battery fills its column (fixed layout guarantees the column is a stable width),
   so all three bars are identical. Long numbers wrap inside the column, never widen it. */
.work-tree-table td.wt-metric .wt-cell { width: 100%; }
.work-tree-table td.wt-metric .wt-battery { width: 100%; display: block; }
.wt-cell { display: flex; flex-direction: column; gap: 0.2rem; }

.wt-battery {
  position: relative;
  height: 0.5rem;
  border-radius: 999px;
  background: var(--surface-2);      /* neutral track */
  box-shadow: inset 0 0 0 1px var(--line);
  overflow: hidden;
}
.wt-bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  height: 100%;
  border-radius: 999px;
  background: var(--info);           /* in progress — blue (the default) */
}
.wt-battery.wt-ok .wt-bar-fill { background: var(--ok); }        /* complete — green */
.wt-battery.wt-risk .wt-bar-fill,
.wt-battery.is-over .wt-bar-fill { background: var(--danger); }  /* at risk — red */

/* OI-00126: WBS metric text is unbolded (400 — Jon's "unbold it all") and kept to ONE line
   (nowrap) so all metric cells are the same height and the bars align across columns. */
.wt-nums { display: flex; gap: 0.3rem; align-items: baseline; flex-wrap: nowrap; white-space: nowrap; overflow: hidden; font: 400 0.78rem/1.2 var(--font-head); }
/* OI-00126 (Option 2): a warning is a small ⚠ on the number line (full text in the cell
   tooltip), adding no height so the bars stay aligned. */
.wt-warn-icon { color: var(--danger); margin-left: 4px; flex: 0 0 auto; }
.wt-of { color: var(--muted); font-weight: 400; font-size: 0.72rem; }
.wt-progress .wt-nums { font-size: 0.72rem; }

/* Fixed-height warning row reserved in EVERY Time/Fees cell so the bars keep equal top
   coordinates whether neither, one or both cells warn. */
/* Fixed single-line height (OI-00126 item 1): every reserved slot is the SAME height
   regardless of the warning text, so within a warning row the Progress/Time/Fees batteries
   stay aligned under vertical-align: middle. The full text remains in the DOM (the cell's
   title tooltip carries it); a long warning ellipsises rather than wrapping to a 2nd line. */
.wt-warn {
  height: 1.05rem;
  line-height: 1.05rem;
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--danger);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wt-warn.is-empty { color: transparent; }

/* OI-00126 (Option B): box an expanded parent together with its children. An expanded
   parent row is tinted; every nested row (depth > 1) carries a left accent — the subtree
   spine; and a divider CLOSES the group after its last child (Jon's "make it clear when one
   item's children end"). The end rows are marked .wt-subtree-end by renderTreeGrid (a row
   whose next visible row steps back to a shallower depth). Scales to any nesting depth. */
/* The approved box is drawn as an OVERLAY layer (drawTreeBoxes in grid.js) positioned over
   the tree — so the fixed-column <table> + our shared grid component stay intact (no fork).
   Each expanded parent + its children is enclosed in a real bordered box; nested subtrees
   nest naturally (an inner box is indented to the child's depth). A subtle tint still marks
   each box's header (the expanded parent row). */
/* `isolation` makes this a self-contained stacking context so the layers below and the sticky
   header (WI-00349) order against each other here, not against the page. */
.work-tree-wrap { position: relative; isolation: isolate; }
/* The box layer sits BEHIND the (transparent) table: the header shade shows through UNDER the
   row content (not dimming it), and the accent border shows through the transparent cells.
   The table carries NO z-index (WI-00349): a stacking context here would trap the sticky
   `thead` below the box-line layer, so the purple lines painted over the header on scroll.
   Without it the sticky header (z-index 3) escapes to sit above the line layer (z-index 2). */
.work-tree-table { position: relative; }
.wt-box-layer { position: absolute; inset: 0; pointer-events: none; }
.wt-box-layer.is-fill { z-index: -1; } /* header shade — behind the rows (doesn't dim content) */
.wt-box-layer.is-line { z-index: 2; } /* box border — above the rows (over the row hairlines) */
.wt-box {
  position: absolute;
  border: 1.5px solid var(--accent);
  border-radius: 8px;
  background: transparent;
  pointer-events: none;
}
/* Light-grey shade on each box's header (the expanded parent row). Drawn at the box's own
   coordinates, so it starts at the box's left edge and never exits it — including for a
   nested (child) parent, whose shade stays inside its own inner box. */
.wt-box-head { position: absolute; background: var(--surface-2); border-radius: 8px 8px 0 0; pointer-events: none; }

/* OI-00126 item 9: the derived Progress battery on a record's Details card. Reuses the WBS
   .wt-battery so the visual matches the tree; a compact fixed-width bar with the % beside it. */
.fv-progress { display: flex; align-items: center; gap: 8px; }
.fv-progress .wt-battery { width: 84px; display: block; flex: 0 0 auto; }
.fv-progress-num { font: 500 0.82rem/1.2 var(--font-head); }

/* OI-00126 (Jon 31 Jul): in list / related tables a null value renders as a blank cell —
   not "— not set —". Applies to grid-relocated cells too (the span is in the DOM either way).
   Excludes the WBS (`.work-tree-table`, which shares `.data-table`): its only empties are the
   editable Status/Priority chips, whose em-dash is the inline-edit click target. The
   record-view Details grid (not `.data-table`) also keeps the explicit "— not set —". */
.data-table:not(.work-tree-table) .fv-empty { display: none; }

.wt-forecast { font: 400 0.82rem/1.2 var(--font-head); vertical-align: top; }

/* Inline chip editing (click the chip → pick a new value) for the WBS Status and
   Priority columns. Enhanced by work-item-tree.js; with JS off each chip is a plain,
   non-interactive label. */
.wbs-status[data-enhanced],
.wbs-priority[data-enhanced] { position: relative; cursor: pointer; display: inline-flex; border-radius: 6px; }
.wbs-status[data-enhanced]:focus-visible,
.wbs-priority[data-enhanced]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wbs-status.is-saving,
.wbs-priority.is-saving { opacity: 0.55; }
/* The picker is rendered on <body> and fixed-positioned by work-item-tree.js (top/left
   computed from the chip, flipped above when there's no room below). Fixed + on-body means
   it escapes the WBS table's overflow box: it spills over the page instead of forcing a
   scrollbar inside the component, and the page scrolls normally if it runs past the fold.
   max-height keeps a lone Work Item's menu on screen (it scrolls inside itself). */
.wbs-edit-menu {
  position: fixed;
  z-index: 60;
  min-width: 11rem;
  max-height: calc(100vh - 8px);
  overflow-y: auto;
  padding: 0.25rem;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
.wbs-edit-menu button {
  display: block;
  width: 100%;
  padding: 0.3rem 0.5rem;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.wbs-edit-menu button[aria-current="true"] { font-weight: 700; }
.wbs-edit-menu button:hover,
.wbs-edit-menu button:focus-visible { background: var(--surface-2); outline: none; }

/* Universal filters + group-by (OI-00164). One "Filter" opens a condition-builder; one
   "Group" buckets a flat list. Both are quiet toolbar buttons that brighten when active; the
   active-count badge reuses the .tab-count pill (added as a second class). */
.grid-tool {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  padding: 0.35rem 0.55rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--muted);
  font: 400 0.85rem/1 var(--font-body);
  cursor: pointer;
}
.grid-tool:hover { color: var(--ink); border-color: var(--accent); }
.grid-tool.is-active { color: var(--accent-strong); border-color: var(--accent); }
.grid-tool:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.grid-tool .grid-tool-label { line-height: 1; }
.grid-tool .grid-tool-count { margin-left: 0.1em; }

/* The condition-builder popover — same body-portalled, fixed, overflow-escaping shell as the
   chip editor, sized for a stack of condition rows. */
.filter-builder {
  position: fixed;
  z-index: 60;
  min-width: 20rem;
  max-width: min(30rem, calc(100vw - 8px));
  max-height: calc(100vh - 8px);
  overflow-y: auto;
  padding: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
.fb-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.1rem 0.2rem 0.4rem;
}
.fb-conj-label {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
}
.fb-conj {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  overflow: hidden;
}
.fb-conj-btn {
  border: 0;
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  font-size: 0.8rem;
  padding: 0.2rem 0.55rem;
  cursor: pointer;
}
.fb-conj-btn.is-active { background: var(--accent); color: #fff; }
.fb-conditions { display: flex; flex-direction: column; gap: 0.4rem; }
.fb-condition { display: flex; flex-direction: column; gap: 0.3rem; }
.fb-condition-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}
/* enum "is any of" → a compact trigger; the option list drops full-width below the row. */
.fb-ms-trigger {
  width: 100%;
  text-align: left;
  padding: 0.3rem 0.4rem;
  font: 400 0.85rem/1.2 var(--font-body);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  cursor: pointer;
}
.fb-ms-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
.fb-ms-list[hidden] { display: none; }
.fb-col, .fb-op, .fb-single, .fb-input {
  padding: 0.3rem 0.4rem;
  font: 400 0.85rem/1.2 var(--font-body);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
}
.fb-col { flex: 1 1 8rem; min-width: 6rem; }
.fb-op { flex: 0 1 auto; }
.fb-value { flex: 1 1 8rem; min-width: 6rem; display: inline-flex; }
.fb-input, .fb-single { width: 100%; }
.fb-col:focus-visible, .fb-op:focus-visible, .fb-single:focus-visible, .fb-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}
/* enum is-any-of → a bordered, scrollable checkbox list of chip / name options. */
.fb-enum {
  width: 100%;
  max-height: 9rem;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  padding: 0.15rem;
}
.fb-enum-opt {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.25rem 0.35rem;
  border-radius: 6px;
  cursor: pointer;
}
.fb-enum-opt:hover { background: var(--surface-2); }
.fb-enum-opt input { flex: none; cursor: pointer; }
.fb-enum-val { min-width: 0; }
.fb-empty { display: block; padding: 0.3rem 0.4rem; color: var(--muted); font-size: 0.85rem; }
.fb-remove {
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0 0.2rem;
  cursor: pointer;
}
.fb-remove:hover { color: var(--danger, #c2410c); }
.fb-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 0.5rem;
  padding-top: 0.4rem;
  border-top: 1px solid var(--line);
}
.fb-add, .fb-clear {
  border: 0;
  background: transparent;
  color: var(--accent-strong);
  font: inherit;
  font-size: 0.82rem;
  padding: 0;
  cursor: pointer;
}
.fb-add:hover, .fb-clear:hover,
.fb-add:focus-visible, .fb-clear:focus-visible { text-decoration: underline; outline: none; }
.fb-clear { color: var(--muted); }

/* The group-by menu — a small radio list of groupable columns. */
.group-menu {
  position: fixed;
  z-index: 60;
  min-width: 11rem;
  max-width: min(18rem, calc(100vw - 8px));
  max-height: calc(100vh - 8px);
  overflow-y: auto;
  padding: 0.25rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
.group-opt {
  display: block;
  width: 100%;
  text-align: left;
  border: 0;
  background: transparent;
  color: var(--ink);
  font: 400 0.85rem/1.2 var(--font-body);
  padding: 0.35rem 0.5rem;
  border-radius: 6px;
  cursor: pointer;
}
.group-opt:hover { background: var(--surface-2); }
.group-opt.is-active { color: var(--accent-strong); font-weight: 600; }

/* Group headers on a grouped flat list (OI-00164) — a full-width, collapsible band with the
   bucket label + row count. Mirrors the History tab's change-set group rows. */
.grouped-table .grp-row > .grp-cell { padding: 0; background: var(--surface-2); }
.grouped-table .grp-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  border: 0;
  background: transparent;
  color: var(--ink);
  font: 600 0.85rem/1.2 var(--font-body);
  padding: 0.45rem 0.6rem;
  cursor: pointer;
  text-align: left;
}
.grouped-table .grp-caret { color: var(--muted); font-size: 0.7rem; width: 0.8em; }
.grouped-table .grp-label { min-width: 0; }
.grouped-table .grp-count {
  margin-left: 0.15rem;
  color: var(--muted);
  font-weight: 500;
  font-size: 0.78rem;
}
.grouped-table .grp-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* OI-00120 (i) explainer popovers — Progress + Time/Fees column headers. Same
   fixed-position-on-<body> pattern as .wbs-edit-menu (escapes the table's overflow box,
   flips above/clamps to viewport), just holding static copy instead of options. With JS
   off the button has no listener, so it renders as an inert inline hint icon. */
.wt-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  margin-left: 0.3rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--muted);
  font: 700 0.65rem/1 var(--font-head);
  cursor: pointer;
  vertical-align: middle;
}
.wt-help:hover,
.wt-help:focus-visible { background: var(--surface); color: var(--ink); outline: none; }
.wt-help[aria-expanded="true"] { background: var(--accent); color: var(--surface); border-color: var(--accent); }

.wt-help-popover {
  position: fixed;
  z-index: 60;
  width: 19rem;
  max-width: calc(100vw - 16px);
  max-height: calc(100vh - 8px);
  overflow-y: auto;
  padding: 0.7rem 0.8rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
.wt-help-popover h3 { margin: 0 0 0.4rem; font: 600 0.82rem/1.3 var(--font-head); }
.wt-help-popover ul { margin: 0; padding-left: 1.1rem; }
.wt-help-popover li { margin-bottom: 0.35rem; font-size: 0.78rem; line-height: 1.4; color: var(--muted); }
.wt-help-popover li:last-child { margin-bottom: 0; }
/* WI-00318: field-level (i) explainers reuse this popover with a prose body paragraph. */
.wt-help-popover p { margin: 0; font-size: 0.78rem; line-height: 1.45; color: var(--muted); }
.wt-help-popover p + ul { margin-top: 0.4rem; }

/* WI-00318: hours + minutes duration input, and read-only derived displays on the form. */
.duration-field { display: flex; align-items: center; gap: 0.4rem; }
.duration-field .duration-part { width: 4.5rem; text-align: right; }
.duration-field .duration-unit { color: var(--muted); font-size: 0.85rem; }
.duration-readonly { margin: 0; padding: 0.45rem 0; font-size: 0.95rem; }
.duration-readonly .muted { color: var(--muted); font-size: 0.8rem; }
.field-display { margin: 0; padding: 0.45rem 0; font-size: 0.95rem; color: var(--ink); }

.wt-project {
  margin-bottom: 0.9rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-2);
}
.wt-project-title { margin: 0 0 0.55rem; font: 600 0.8rem/1.3 var(--font-head); color: var(--muted); }
.wt-project-agreed { margin-left: 0.35rem; color: var(--ink); }
.wt-project-batteries { display: grid; grid-template-columns: 1fr 1fr; gap: 0.9rem; }
.wt-pb { min-width: 0; }
.wt-pb-head { display: flex; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.3rem; font-size: 0.72rem; }
.wt-pb-label { color: var(--muted); }
.wt-pb-remainder { color: var(--ink); font-weight: 600; }
.wt-pb-remainder.is-risk { color: var(--danger); }
.wt-pb .wt-battery { height: 0.6rem; }

@media (max-width: 39.99rem) {
  .wt-project-batteries { grid-template-columns: 1fr; }
  .wt-metric .wt-cell { align-items: stretch; }
}

/* ── Work Item network diagram (OI-00116) ─────────────────────────────────────
   Rendered as a Related-panel tab next to the Work Breakdown Structure. The X6 canvas
   is themed with concrete colours resolved from these tokens at render time (SVG
   presentation attributes can't hold var()), so the JS re-renders on a theme change.
   The fallback list is the JS-off / assistive-tech view and is removed once the canvas
   mounts (the sibling WBS tab is the keyboard-navigable equivalent). */
.network-wrap { border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--surface); }
.network-canvas { height: 560px; width: 100%; position: relative; touch-action: none; }
/* X6 text should inherit the app's display font, not the SVG default. */
.network-canvas text { font-family: var(--font-head), sans-serif; }
.wi-node-name { text-decoration: underline; }

/* The JS-off fallback list (removed on mount). */
.network-fallback { padding: 0.5rem 0.75rem; }
.network-fallback-items,
.network-fallback-edges { list-style: none; margin: 0.5rem 0 0; padding: 0; font-size: 0.85rem; }
.network-fallback-items > li { padding: 0.15rem 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem; }
.network-fallback-items > li.is-external { color: var(--muted); }
.network-fallback-number { color: var(--muted); font-size: 0.78rem; }
.network-fallback-tag { font-size: 0.72rem; color: var(--muted); border: 1px solid var(--line); border-radius: 999px; padding: 0 0.4rem; }
.network-fallback-meta { color: var(--muted); font-size: 0.78rem; }
.network-fallback-subhead { margin: 0.75rem 0 0; font: 600 0.78rem/1.5 var(--font-head); text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }

.network-status { font-size: 0.85rem; margin: 0.5rem 0 0; min-height: 1.2em; }
.network-status.is-ok { color: var(--ok-ink, var(--ok)); }
.network-status.is-error { color: var(--danger-ink, var(--danger)); }
.network-note { font-size: 0.8rem; margin: 0.25rem 0 0; }

/* Narrow viewport: shorten the canvas. */
@media (max-width: 640px) {
  .network-canvas { height: 420px; }
}

/* ---- Billable "Auto" reset + confirm modal (OI-00165, WI-00342 #5) ---- */
/* A small inline "reset this field to its default" link, placed INSIDE a field cell so it
   doesn't consume a grid column (OI-00165 review). */
.field-reset {
  align-self: start;
  margin-top: 0.3rem;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: 0.72rem;
  color: var(--accent-strong);
  cursor: pointer;
}
.field-reset:hover {
  text-decoration: underline;
}
.field-reset[hidden] {
  display: none;
}
.modal-scrim {
  position: fixed;
  inset: 0;
  background: rgba(15, 18, 22, 0.4);
  display: grid;
  place-items: center;
  z-index: 1000;
  padding: 1rem;
}
.modal {
  /* Themed tokens (not hardcoded light) so the modal is correct in dark mode (OI-00165). */
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-panel, 12px);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35);
  max-width: 26rem;
  width: 100%;
  overflow: hidden;
}
.modal-body {
  padding: 1.1rem 1.2rem;
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--ink);
}
.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 0.8rem 1.2rem;
  background: var(--surface-2);
  border-top: 1px solid var(--line);
}

