/* Cyber Academy — components.
 *
 * One section per primitive, headed with the namespace that renders it, class names BEM-like (`.block`,
 * `.block__element`, `.block--modifier`). The anatomy, states and measurements are the visual contract's
 * (`notes/design-rulebook.md`, components); every value is a token from tokens.css or a measurement the contract
 * states. Screen lanes add to this file only through the Coordinator.
 *
 * Shared rules: hover applies only where a pointer can hover; transitions are colour, opacity and shadow at
 * `--dur-fast`; keyboard focus is base.css's recipes. NO FRAMING STROKES: depth is the elevation ladder (`--elev-1`
 * raised, `--elev-3` overlay, `--elev-4` modal), state is a fill. A surface that holds fields declares `--holder`,
 * its own fill, and `--holder-below`, the surface one elevation beneath it: the inputs inside are recessed into it.
 */

/* == component: button (client.ui.action) ==
 * No stroke on any variant: a filled button is its fill, a secondary button a raised neutral fill (`--elev-1`), a
 * ghost button nothing until it is hovered. Hover lifts a raised button one step and steps its fill; pressed takes
 * the pressed fill in 60 ms and settles the shadow back to rest, never below it — nothing moves. Keyboard focus is the
 * `filled`, `raised` and `ghost` recipes in base.css; the variables below are what those recipes read. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--control-h);
  padding: 0 14px;
  border: 0;
  border-radius: var(--radius-sm);
  font: var(--weight-medium) var(--text-14) / var(--lh-14) var(--font-sans);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard), opacity var(--dur-fast) var(--ease-standard);
}
.btn:active { transition-duration: var(--dur-instant); }
.btn:has(> .btn__icon:not(:empty)) { padding-left: 12px; }
.btn--sm { height: var(--control-h-sm); padding: 0 10px; font-size: var(--text-13); line-height: var(--lh-13); }
.btn__icon { display: inline-flex; flex: none; }
.btn__icon:empty { display: none; }
.btn[aria-busy="true"] .btn__icon svg { animation: btn-spin var(--dur-spin) var(--ease-linear) infinite; }
@keyframes btn-spin { to { transform: rotate(360deg); } }

.btn--primary {
  --fill-hover: var(--accent-hover);
  --fill-shadow-2: var(--elev-2-fill);
  background-color: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--elev-1-fill);
}
.btn--destructive {
  --fill-hover: var(--destructive-hover);
  --fill-shadow-2: var(--elev-2-destructive);
  background-color: var(--destructive);
  color: var(--destructive-ink);
  box-shadow: var(--elev-1-destructive);
}
.btn--secondary { background-color: var(--control-fill); color: var(--text-1); box-shadow: var(--elev-1); }
.btn--destructive-secondary { background-color: var(--control-fill); color: var(--destructive-fg); box-shadow: var(--elev-1); }
.btn--ghost { background-color: transparent; color: var(--text-2); }
@media (hover: hover) {
  .btn--primary:hover:not(:disabled):not([aria-disabled="true"]):not(:focus-visible),
  .btn--destructive:hover:not(:disabled):not([aria-disabled="true"]):not(:focus-visible) { background-color: var(--fill-hover); box-shadow: var(--fill-shadow-2); }
  .btn--secondary:hover:not(:disabled):not([aria-disabled="true"]):not(:focus-visible),
  .btn--destructive-secondary:hover:not(:disabled):not([aria-disabled="true"]):not(:focus-visible) { background-color: var(--control-fill-hover); box-shadow: var(--elev-2); }
  .btn--ghost:hover:not(:disabled):not([aria-disabled="true"]):not(:focus-visible) { background-color: var(--surface-hover); color: var(--text-1); }
}
.btn--primary:active:not(:disabled):not([aria-disabled="true"]) { background-color: var(--accent-pressed); box-shadow: var(--elev-1-fill); }
.btn--destructive:active:not(:disabled):not([aria-disabled="true"]) { background-color: var(--destructive-pressed); box-shadow: var(--elev-1-destructive); }
.btn--secondary:active:not(:disabled):not([aria-disabled="true"]),
.btn--destructive-secondary:active:not(:disabled):not([aria-disabled="true"]) { background-color: var(--control-fill-pressed); box-shadow: var(--elev-1); }
.btn--ghost:active:not(:disabled):not([aria-disabled="true"]) { background-color: var(--surface-pressed); }

/* Disabled keeps the geometry and loses every lift, glow and press response. */
.btn:disabled, .btn[aria-disabled="true"] { cursor: default; }
.btn:disabled, .btn[aria-disabled="true"]:not([aria-busy="true"]) { box-shadow: none; }
.btn--primary:disabled, .btn--destructive:disabled,
.btn--primary[aria-disabled="true"]:not([aria-busy="true"]), .btn--destructive[aria-disabled="true"]:not([aria-busy="true"]) { background-color: var(--surface-2); color: var(--text-disabled); }
.btn--secondary:disabled, .btn--destructive-secondary:disabled,
.btn--secondary[aria-disabled="true"]:not([aria-busy="true"]), .btn--destructive-secondary[aria-disabled="true"]:not([aria-busy="true"]) { background-color: var(--control-fill); color: var(--text-disabled); }
.btn--ghost:disabled, .btn--ghost[aria-disabled="true"]:not([aria-busy="true"]) { color: var(--text-disabled); }
/* A busy button keeps its own look while it waits; only the spinner says so. */
.btn[aria-busy="true"] { opacity: 0.8; }

/* The visible reason a control is unavailable, beside it (below it on phone). */
.disabled-reason { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--text-2); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }

/* == Text-only controls ==
 * One colour-only hover convention across primitives and console layouts. Surface controls (ghost buttons,
 * sidebar/menu rows, tiles and tags) retain their fill/lift recipes. Authored prose links keep their underline.
 * Focus recipes stay independent: a pointer never draws their keyboard cue. */
:is(.text-action, .data-table__name, .data-table__sort, [data-focus="text"], .import-step__edit,
    .contact-link, .entitlement-name, .report-activity__quiet > summary) {
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-standard);
}
@media (hover: hover) {
  :is(.text-action, .data-table__name, .data-table__sort, [data-focus="text"], .import-step__edit,
      .contact-link, .entitlement-name, .report-activity__quiet > summary):hover:not(:disabled):not([aria-disabled="true"]) {
    color: var(--text-hover);
  }
}

/* == component: text action — navigation outside prose (`a.text-action`) ==
 * Keep the existing keyboard underline, without drawing it on pointer hover. The padding preserves its geometry. */
.text-action { color: var(--text-2); text-decoration: none; padding-bottom: 1px; }
.text-action:visited { color: var(--text-2); }
.text-action:is(:focus, :active) { color: var(--text-1); }
.text-action:focus-visible {
  text-decoration: none;
  background: linear-gradient(currentColor, currentColor) no-repeat 0 100% / 100% 1px;
  border-radius: 0;
}

/* == component: icon badge (client.ui.icon/badge) ==
 * A glyph on its own disc as one SVG. The disc is the circle inside it, in `--badge-disc` (the surface unless the
 * holder says otherwise); the glyph is `currentColor`. A placement class lifts it: the rounded box only shapes the
 * elevation shadow to the disc. */
.icon-badge { display: block; flex: none; overflow: visible; }
.icon-badge__disc { fill: var(--badge-disc, var(--surface)); }

/* == component: icon button (client.ui.action/icon-button) ==
 * Ghost styling: nothing at rest, the hover fill under a pointer, the pressed fill while pressed. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-button);
  height: var(--icon-button);
  flex: none;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background-color: transparent;
  color: var(--text-2);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.icon-btn:active { transition-duration: var(--dur-instant); }
@media (hover: hover) { .icon-btn:hover:not(:disabled) { background-color: var(--surface-hover); color: var(--text-1); } }
.icon-btn:active:not(:disabled) { background-color: var(--surface-pressed); }
.icon-btn[aria-pressed="true"], .icon-btn[aria-expanded="true"] { background-color: var(--surface-hover); color: var(--text-1); }
.icon-btn:disabled { color: var(--text-disabled); cursor: default; }

/* == component: tooltip (client.ui.tooltip, and the icon button's own) ==
 * The wrapper only: its bubble is the hover card below, drawn in the overlay layer. */
.tooltip { position: relative; display: inline-flex; }

/* == component: inputs — every text, e-mail, password, search, number and date input, textarea, select and dropdown
 * trigger (client.ui.field, client.ui.common, and `[data-focus="well"]` on a trigger rendered elsewhere) ==
 * THE NORMATIVE INPUT RULE (`notes/design-rulebook.md`, components; the operator's decision recorded in ADR-0041):
 * an input is RECESSED into the surface that holds it.
 *   rest            the holder's own colour and a subtle inset shadow; no border, no external shadow, no underline
 *   hover           a small step toward the surface beneath the holder, and a modestly deeper inset
 *   focus, active   the surface beneath the holder (`--holder-below`), a deeper inset, and the field's label takes
 *                   the focus colour — the whole of the focus treatment: no ring, no dash, no lift, no glow
 * Every surface that holds fields declares `--holder` and `--holder-below`; the formulas are declared here, on the
 * input itself, because a custom property is resolved where it is declared. The colour and depth change animate
 * with the shared motion tokens; nothing changes size. `bb ops/check-css-framing.clj` evaluates these formulas on
 * every holder in both modes: value text ≥ 4.5:1 in every state, and the focus surface differs from the rest.
 * An error tints the recess toward the serious colour and adds the icon in the field and the message below; a
 * success tints it toward the on-track colour. Disabled is flat — no recess — in muted ink; read-only keeps its
 * recess and its ink but never takes the hover. Autofill keeps the recessed surface. */
.control, .input, [data-focus="well"] {
  --field: var(--holder);
  --field-hover: color-mix(in srgb, var(--holder) 50%, var(--holder-below));
  --field-focus: var(--holder-below);
  --field-error: color-mix(in srgb, var(--holder) 60%, var(--status-serious-tint));
  --field-error-focus: color-mix(in srgb, var(--holder-below) 60%, var(--status-serious-tint));
  --field-success: color-mix(in srgb, var(--holder) 60%, var(--status-on-track-tint));
  --field-success-focus: color-mix(in srgb, var(--holder-below) 60%, var(--status-on-track-tint));
  --field-ink: var(--text-1);
}
/* A checkbox or radio keeps the check recipe: a filled well ≥ 3:1 against its holder. */
.check__box {
  --well: color-mix(in srgb, var(--holder) var(--well-mix), var(--well-toward));
  --well-hover: color-mix(in srgb, var(--holder) var(--well-mix-hover), var(--well-toward));
  --well-disabled: color-mix(in srgb, var(--holder) var(--well-mix-disabled), var(--well-toward));
}

/* == component: code cells — the login code as six digit boxes (client.views.login/code-field) ==
 * Six one-digit inputs, each an input well (`.input`), so each takes the input rule on its own: only the box holding
 * the caret recesses. They share the row's width; the digit sits centred in the mono face. */
.code-cells { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--space-2); }
.code-cells > .input { height: var(--control-h-lg); padding: 0; text-align: center; font: var(--weight-medium) 20px / 1 var(--font-mono); caret-color: var(--text-1); }
.field:has(> .code-cells > .input:focus) > .field-label { color: var(--focused-label-ink); }

/* == component: text field, select, search, file (client.ui.field) == */
.form-field { display: flex; flex-direction: column; gap: 6px; max-width: var(--field-max); }
.form-field--wide { max-width: none; }
.form-field__heading { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.form-field__label { font: var(--weight-medium) var(--text-13) / var(--lh-13) var(--font-sans); color: var(--text-1); transition: color var(--dur-fast) var(--ease-standard); }
/* The label is part of the focus treatment: customer mode darkens the brand for text on
 * every holder while operator mode retains its own focus ink. The label keeps its weight on focus. While its field
 * is disabled the label is muted. */
.form-field:has(:is(.control, .input, [data-focus="well"]):focus) > .form-field__label,
.form-field:has(:is(.control, .input, [data-focus="well"]):focus) > .form-field__heading > .form-field__label,
.search-field-group:has(.control:focus) > .form-field__label,
.field:has(> .control-box > :is(.control, .input):focus) > .field-label { color: var(--focused-label-ink); }
.form-field:has(:is(.control, .input, [data-focus="well"]):disabled) > .form-field__label,
.form-field:has(:is(.control, .input, [data-focus="well"]):disabled) > .form-field__heading > .form-field__label,
.field:has(> .control-box > :is(.control, .input):disabled) > .field-label { color: var(--text-3); }
.form-field__help { font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); color: var(--text-3); }
.form-field__error { display: flex; gap: 6px; align-items: flex-start; font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); color: var(--status-serious-fg); }
.form-field__error svg { margin-top: 1px; }
.form-stack { display: flex; flex-direction: column; gap: var(--space-4); }

/* The shared result of a value committed in place. A success leaves after the feedback duration. */
.autosave { display: inline-flex; align-items: center; gap: var(--space-1); min-width: 0; color: var(--text-2); font: var(--weight-regular) var(--text-12) / var(--lh-12) var(--font-sans); }
.autosave__icon { display: inline-flex; flex: none; color: var(--status-on-track-fg); }
.autosave--error { color: var(--status-serious-fg); overflow-wrap: anywhere; }
.autosave--saved { animation: autosave-fade var(--dur-feedback) var(--ease-standard) forwards; }
@keyframes autosave-fade { 0%, 70% { opacity: 1; } 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .autosave--saved { animation: autosave-fade 2000ms steps(1, end) forwards; }
}

/* The box around an input: it hosts what an input cannot, the error icon inside the field. */
.control-box { position: relative; display: block; }
.control-box__status { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); display: inline-flex; color: var(--status-serious-fg); pointer-events: none; }
.control-box__status:empty { display: none; }
.control-box:has(> .control-box__status:not(:empty)) > :is(.control, .input) { padding-right: 36px; }
.select-box > .control-box__status { right: 32px; }
.select-box:has(> .control-box__status:not(:empty)) > .control { padding-right: 56px; }
.password-box > .control-box__status { right: 40px; }
.password-box:has(> .control-box__status:not(:empty)) > .control { padding-right: 64px; }

.control, .input {
  display: block;
  width: 100%;
  height: var(--control-h);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  font: var(--weight-regular) var(--text-14) / var(--lh-14) var(--font-sans);
}
.control, .input, [data-focus="well"] {
  background-color: var(--field);
  color: var(--field-ink);
  box-shadow: var(--inset-rest);
  transition: background-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.control::placeholder, .input::placeholder { color: var(--text-3); opacity: 1; }
@media (hover: hover) {
  :is(.control, .input, [data-focus="well"]):hover:not(:disabled):not([readonly]):not(:focus) {
    box-shadow: var(--inset-hover);
  }
  /* A validation tint stays under the pointer: only the neutral surface steps toward the one beneath. */
  :is(.control, .input, [data-focus="well"]):hover:not(:disabled):not([readonly]):not(:focus):not([aria-invalid="true"]):not([data-invalid="true"], [data-invalid="true"] *):not([data-validation="success"]) {
    background-color: var(--field-hover);
  }
}
:is(.control, .input, [data-focus="well"]):is(:focus, :active):not(:disabled) {
  background-color: var(--field-focus);
  box-shadow: var(--inset-focus);
}
/* Validation: a restrained tint of the recess, the icon inside the field and the message below; never a border. */
/* `data-invalid="true"` is the same fault said differently: L1's React Aria fields (on L1's own branch; nothing in
 * this tree emits it yet) cannot set aria-invalid on their trigger and mark the trigger or its wrapper instead. */
:is(.control, .input, [data-focus="well"]):is([aria-invalid="true"], [data-invalid="true"]),
[data-invalid="true"] :is(.control, .input, [data-focus="well"]) { background-color: var(--field-error); }
:is(.control, .input, [data-focus="well"]):is([aria-invalid="true"], [data-invalid="true"]):is(:focus, :active),
[data-invalid="true"] :is(.control, .input, [data-focus="well"]):is(:focus, :active) { background-color: var(--field-error-focus); }
:is(.control, .input, [data-focus="well"])[data-validation="success"] { background-color: var(--field-success); }
:is(.control, .input, [data-focus="well"])[data-validation="success"]:is(:focus, :active) { background-color: var(--field-success-focus); }
/* Read-only keeps the recess and never takes the hover. */
/* Disabled keeps a faint recess, so it still reads as a field, and never reacts. */
:is(.control, .input, [data-focus="well"]):disabled { background-color: var(--holder); box-shadow: var(--inset-disabled); color: var(--text-3); cursor: not-allowed; }
.control:disabled::placeholder, .input:disabled::placeholder { color: var(--text-3); }
:is(.control, .input)[readonly] { cursor: default; }
/* A trigger waiting for its options says so without moving: a progress cursor and secondary ink. */
:is(.control, [data-focus="well"])[aria-busy="true"] { cursor: progress; color: var(--text-2); }
/* Autofill must not repaint the recess: the browser's own fill is covered by an inset of the field's current
 * surface, and its text colour by the field's ink. */
:is(.control, .input):-webkit-autofill {
  -webkit-text-fill-color: var(--field-ink);
  caret-color: var(--field-ink);
  box-shadow: var(--inset-rest), inset 0 0 0 100px var(--field);
}
/* The recess layer paints first (on top); the cover beneath it hides the browser's own fill. The hover rule matches
 * the generic hover rule's chain so it wins on hover too; it stands alone so an engine that rejects the pseudo-class
 * drops only this rule. */
:is(.control, .input):-webkit-autofill:hover:not(:disabled):not([readonly]):not(:focus) {
  box-shadow: var(--inset-hover), inset 0 0 0 100px var(--field-hover);
}
:is(.control, .input):-webkit-autofill:is(:focus, :active):not(:disabled) {
  box-shadow: var(--inset-focus), inset 0 0 0 100px var(--field-focus);
}
.control--mono { font-family: var(--font-mono); }
.control--code { font: var(--weight-medium) var(--text-24) / var(--lh-24) var(--font-mono); letter-spacing: 0.25em; text-align: center; height: 52px; width: 200px; }
.control--lg { height: var(--control-h-lg); }
@media (pointer: coarse) { .control, .input { font-size: var(--text-16); line-height: var(--lh-16); } }
@media (max-width: 639px) { .control--code { width: 100%; } }

.select-box { position: relative; }
.select-box > .control { appearance: none; -webkit-appearance: none; padding-right: 36px; }
.select-box__chevron { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); color: var(--text-2); pointer-events: none; display: inline-flex; }

/* The show/hide toggle sits inside the input: its hover is a veil of the ink, its dash the input's ink. */
.password-box { position: relative; }
.password-box > .control { padding-right: 44px; }
.password-box > .tooltip { position: absolute; right: 2px; top: 50%; transform: translateY(-50%); }
.password-box .icon-btn {
  --surface-hover: color-mix(in srgb, var(--text-1) 10%, transparent);
  --surface-pressed: color-mix(in srgb, var(--text-1) 16%, transparent);
  --dash-color: var(--text-1);
  color: var(--text-2);
}
@media (hover: hover) { .password-box .icon-btn:hover:not(:disabled) { color: var(--text-1); } }

.search-field { position: relative; width: 320px; max-width: 100%; }
.search-field__icon { position: absolute; z-index: 1; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-3); display: inline-flex; pointer-events: none; }
.search-field > .control { padding-left: 36px; }
/* The labelled search group: its compact label above, the box, and an optional hint below. */
.search-field-group { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
/* A toolbar search's label is quieter than a form's: `text-2`, the table headers' colour, never full `text-1`; focus
 * still takes it to the focus colour. */
.search-field-group > .form-field__label { color: var(--text-2); }
@media (max-width: 639px) { .search-field { width: 100%; } }

/* The file button is the face of a visually hidden input that holds the focus; base.css draws the `raised` recipe on
 * the button while the input has keyboard focus. */
.file-field__picker { position: relative; display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.file-field__name { color: var(--text-2); overflow-wrap: anywhere; }

/* == component: colour editor (client.ui.colour-editor) ==
 * The trigger is filled with the colour it shows (an inline style from `#RRGGBB` only) and writes its text in the
 * derived ink, which any colour holds at 4.58:1 or more. It is a filled control on the `filled` focus recipe: the
 * dash in its own ink, a lift and the glow. Its fill never changes on hover or focus — a darkened fill could take the
 * ink below 4.5:1 for a colour the checks refuse — so the inline fill stands and only the lift moves. */
.colour-trigger {
  --fill-shadow-2: var(--elev-2);
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "label verdict" "value verdict";
  align-items: center;
  column-gap: var(--space-3);
  width: 100%;
  min-height: 64px;
  padding: var(--space-3) var(--space-4);
  border: 0;
  border-radius: var(--radius-sm);
  box-shadow: var(--elev-1);
  text-align: left;
  cursor: pointer;
  transition: box-shadow var(--dur-fast) var(--ease-standard);
}
@media (hover: hover) { .colour-trigger:hover { box-shadow: var(--elev-2); } }
.colour-trigger__label { grid-area: label; font: var(--weight-medium) var(--text-14) / var(--lh-14) var(--font-sans); }
.colour-trigger__value { grid-area: value; font: var(--weight-regular) var(--text-14) / var(--lh-14) var(--font-mono); }
.colour-trigger__verdict { grid-area: verdict; display: inline-flex; align-items: center; gap: 6px; font: var(--weight-medium) var(--text-13) / var(--lh-13) var(--font-sans); }
/* The editor is the content of an overlay popover (client.ui.overlay): its fields sit on the overlay surface, which
 * declares itself their holder. */
.colour-editor { display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-2); container-type: inline-size; }
/* Beside the picker where the card is wide enough — HEX, RGB and the two swatches in one column whose top meets the
 * colour field's — and under it on a narrow card. The checks and the actions span the card below either way. */
.colour-editor__main { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-5); align-items: start; }
.colour-editor__side { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.colour-picker {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  width: min(100%, 240px);
  padding-bottom: var(--space-2);
}
.colour-picker__area, .colour-picker__hue {
  position: relative;
  width: 100%;
  touch-action: none;
  cursor: crosshair;
  --dash-bottom: -7px;
  transition: box-shadow var(--dur-fast) var(--ease-standard);
}
.colour-picker__area { aspect-ratio: 1; border-radius: var(--radius-sm); }
.colour-picker__hue {
  height: 20px;
  border-radius: var(--radius-full);
  background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);
}
/* The pins hold on any colour under them: filled with the colour they stand on, inside a white ring that reads on the
 * dark half of the field and a thin dark one outside it that reads on the white corner — two 1-2 px rings, not a
 * frame, so the pin stays light. */
.colour-picker__area-thumb, .colour-picker__hue-thumb {
  position: absolute;
  width: 18px;
  height: 18px;
  border-radius: var(--radius-full);
  box-shadow: 0 0 0 2px #FFFFFF, 0 0 0 3px rgba(12, 14, 18, 0.55), 0 2px 6px rgba(12, 14, 18, 0.35);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.colour-picker__hue-thumb { top: 50%; width: 24px; height: 24px; }
.colour-editor__fields { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-4) var(--space-6); }
.colour-editor__hex { flex: 0 1 220px; min-width: 180px; }
.colour-editor__channels { display: flex; flex-direction: column; gap: 6px; flex: 0 1 300px; min-width: 0; margin: 0; padding: 0; border: 0; }
.colour-editor__legend { padding: 0; margin-bottom: 6px; font: var(--weight-medium) var(--text-13) / var(--lh-13) var(--font-sans); color: var(--text-1); }
.colour-editor__channel-row { display: grid; grid-template-columns: repeat(3, minmax(64px, 1fr)); gap: var(--space-2); }
.colour-editor__channel-row .form-field { max-width: none; }
.colour-editor__compare { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.colour-swatch { display: inline-flex; flex-direction: column; min-width: 128px; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); box-shadow: var(--elev-1); }
.colour-swatch__label { font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); }
.colour-swatch__value { font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-mono); }
.colour-editor__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); }
/* Wide card: the side column (after the base field rules, which it overrides). */
@container (min-width: 520px) {
  .colour-editor__main { grid-template-columns: 240px minmax(0, 1fr); gap: var(--space-6); }
  .colour-editor__fields { flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: var(--space-4); }
  .colour-editor__hex, .colour-editor__channels { flex: none; min-width: 0; }
  .colour-editor__compare { flex-wrap: nowrap; }
  .colour-editor__compare .colour-swatch { flex: 1 1 0; min-width: 0; }
  /* the column spans the picker's height and the swatches sit at its foot, level with the hue slider */
  .colour-editor__side { align-self: stretch; padding-bottom: var(--space-2); }
  .colour-editor__compare { margin-top: auto; }
}
/* The check summary: one announced sentence, then each refusal and warning by glyph and word, never colour alone. */
.colour-checks { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-3); }
.colour-checks__summary { display: flex; align-items: flex-start; gap: var(--space-2); margin: 0; color: var(--text-2); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }
.colour-checks__icon { display: inline-flex; flex: none; margin-top: 1px; color: var(--status-on-track-fg); }
.colour-checks[data-verdict="warning"] .colour-checks__icon { color: var(--status-behind-fg); }
.colour-checks[data-verdict="blocking"] .colour-checks__icon { color: var(--status-serious-fg); }
.colour-checks__list { display: flex; flex-direction: column; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
.colour-checks__item { display: flex; align-items: flex-start; gap: var(--space-2); max-width: 680px; font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); color: var(--text-1); }
.colour-checks__item > svg { flex: none; margin-top: 1px; }
.colour-checks__item strong { font-weight: var(--weight-semibold); }
.colour-checks__item--blocking > svg { color: var(--status-serious-fg); }
.colour-checks__item--warning > svg { color: var(--status-behind-fg); }
.colour-checks__shades { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-1); }

/* == component: labelled field (client.ui.common — field, select-field, file-control; client.ui.confirm) ==
 * The label wraps its input; help and errors sit after the label, outside it (client.ui.common explains why). The
 * input is the same well as `.control`. */
.field { display: block; margin-bottom: var(--space-4); }
.field-label { display: block; margin-bottom: 6px; font: var(--weight-medium) var(--text-13) / var(--lh-13) var(--font-sans); color: var(--text-1); transition: color var(--dur-fast) var(--ease-standard); }
.input { font: inherit; }
@media (pointer: coarse) { .input { font-size: var(--text-16); } }
.select { padding-right: var(--space-3); }
.field-note { margin: 6px 0 var(--space-4); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); color: var(--text-3); }
.field-error { margin: 6px 0 0; font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); color: var(--status-serious-fg); }
.readonly-value { padding: var(--space-1) 0; color: var(--text-2); }
.file-picker { position: relative; display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.file-picker > .btn { display: inline-flex; align-items: center; }
.file-name { color: var(--text-2); overflow-wrap: anywhere; }

/* == component: checkbox and radio (client.ui.field) ==
 * The whole label row is the hit area. The box is a well (≥ 3:1 against its holder, above); checked is the accent
 * fill with the tick in accent ink. The tick is drawn with two border sides rotated — a glyph technique, not a frame,
 * and one of the declared exceptions in `bb ops/check-css-framing.clj`. */
.check { display: inline-flex; align-items: center; gap: var(--space-2); min-height: 32px; cursor: pointer; }
@media (pointer: coarse) { .check { min-height: 44px; } }
.check__box {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0;
  border: 0;
  border-radius: var(--radius-xs);
  background-color: var(--well);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.check__box[type="radio"] { border-radius: var(--radius-full); }
@media (hover: hover) {
  .check:hover > .check__box:not(:checked):not(:disabled),
  .check__box:hover:not(:checked):not(:disabled) { background-color: var(--well-hover); }
}
.check__box:checked, .check__box[type="checkbox"]:indeterminate { background-color: var(--accent); }
.check__box[type="checkbox"]:checked::after {
  content: ""; position: absolute; left: 5px; top: 2px; width: 5px; height: 9px;
  border: solid var(--accent-ink); border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.check__box[type="checkbox"]:indeterminate::after { content: ""; position: absolute; left: 4px; top: 7px; width: 8px; height: 2px; background: var(--accent-ink); }
.check__box[type="radio"]:checked::after { content: ""; position: absolute; left: 5px; top: 5px; width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--accent-ink); }
.check__box:disabled { background-color: var(--well-disabled); cursor: default; }
.check__box:disabled:checked { background-color: var(--surface-pressed); }
.radio-group { display: flex; flex-direction: column; gap: var(--space-1); border: 0; margin: 0; padding: 0; min-width: 0; }
.radio-group__legend { padding: 0; margin-bottom: 6px; font: var(--weight-medium) var(--text-13) / var(--lh-13) var(--font-sans); color: var(--text-1); }

/* == component: segmented control (client.ui.segmented) ==
 * A track of the hover fill; the selected segment is a raised thumb (`surface`, operator `surface-3`, `--elev-1`)
 * with `text-1` — never the brand. Keyboard focus is the `segment` recipe in base.css. */
.segmented { display: inline-flex; padding: 2px; gap: 2px; border-radius: var(--radius-md); background-color: var(--surface-2); max-width: 100%; }
.segmented__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: calc(var(--control-h) - 4px);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background-color: transparent;
  color: var(--text-2);
  font: var(--weight-medium) var(--text-14) / var(--lh-14) var(--font-sans);
  white-space: nowrap;
  cursor: pointer;
  /* A cross-fade: --ease-standard front-loads nearly all of a change into its first few frames, which on two close
   * surfaces reads as a switch, not a fade. */
  transition: background-color var(--dur-slow) var(--ease-in-out), color var(--dur-slow) var(--ease-in-out),
              box-shadow var(--dur-slow) var(--ease-in-out);
}
@media (hover: hover) { .segmented__item:hover { color: var(--text-1); transition-duration: var(--dur-fast); } }
/* Selecting is a quick fade: the thumb fades in under the chosen label while that label fades up from faint, and the
 * label left behind fades down to the muted colour — both on --dur-base, so the change reads without dragging. The
 * animation starts when a segment becomes selected (and once on first render, where it is the same gentle fade). */
.segmented__item[aria-selected="true"] > *, .segmented__item[aria-checked="true"] > * {
  animation: segment-in var(--dur-slow) var(--ease-in-out);
}
@keyframes segment-in { from { opacity: 0.35; } }
.segmented__item[aria-selected="true"], .segmented__item[aria-checked="true"] {
  background-color: var(--surface);
  color: var(--text-1);
  box-shadow: var(--elev-1);
}
[data-mode="operator"] .segmented__item[aria-selected="true"],
[data-mode="operator"] .segmented__item[aria-checked="true"] { background-color: var(--surface-3); }
.segmented__count { color: var(--text-3); font-variant-numeric: tabular-nums; }
@media (max-width: 639px) { .segmented { display: flex; width: 100%; } .segmented__item { flex: 1; justify-content: center; } }

/* == component: tag (client.ui.tag) == */
.tag { display: inline-flex; align-items: center; height: 22px; padding: 0 var(--space-2); border-radius: var(--radius-xs); font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); letter-spacing: 0.01em; white-space: nowrap; align-self: center; flex: none; gap: var(--space-1); }
.tag--neutral { background: var(--surface-2); color: var(--text-2); }
.tag--info { background: var(--info-tint); color: var(--info-fg); }
.tag--success { background: var(--status-on-track-tint); color: var(--status-on-track-fg); }
.tag--warning { background: var(--status-behind-tint); color: var(--status-behind-fg); }
.tag--error { background: var(--status-serious-tint); color: var(--status-serious-fg); }
.tag--paused { background: var(--status-paused-tint); color: var(--status-paused-fg); }
.tag > svg { flex: none; }

/* == component: count badge (client.ui.tag/count-badge) ==
 * A heading's count as a compact filled pill: flat (elevation 0), `text-2` on `--badge-fill` (6.37:1 light, 7.08:1
 * dark), caption type in tabular figures. Its width is its digits' with even padding and never less than its height, so
 * one digit is a circle and more digits grow it into a capsule; it follows the title, so growing moves nothing before
 * it. It sits in the heading's line and centres itself on that line: `--heading-lh`
 * is the line height the heading's role class declares (base.css), so the pill is centred on the title's own line box
 * — on the last line when a title wraps — without the heading having to become a flex box. */
.count-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  height: var(--badge-h);
  min-inline-size: var(--badge-h);
  padding: 0 6px;
  margin-inline-start: var(--space-2);
  margin-top: calc((var(--heading-lh, var(--badge-h)) - var(--badge-h)) / 2);
  vertical-align: top;
  border-radius: var(--radius-full);
  background-color: var(--badge-fill);
  color: var(--text-2);
  font: var(--weight-semibold) var(--text-12) / var(--lh-12) var(--font-sans);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  white-space: nowrap;
}

/* == component: table (client.ui.table) ==
 * The card is raised, not outlined; the header is a half-step fill; rows are separated by their 44 px rhythm and a
 * hover fill, never a rule. A hovered or selected row becomes the holder of the checkbox in it. */
.table-card { --holder: var(--surface); --holder-below: var(--bg); background-color: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--elev-1); overflow: clip; }
.data-table { width: 100%; border-collapse: separate; border-spacing: 0; }
.data-table th {
  position: sticky;
  top: var(--sticky-offset, 0px);
  z-index: var(--z-sticky);
  height: 40px;
  padding: 0 var(--space-3);
  --holder: color-mix(in srgb, var(--surface) 60%, var(--surface-hover));
  background-color: var(--holder);
  color: var(--text-2);
  font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans);
  letter-spacing: 0.01em;
  text-align: left;
  white-space: nowrap;
}
.data-table tbody :is(a, button, input, select, textarea, [tabindex]) { scroll-margin-top: 40px; }
.data-table td { height: var(--row-h); padding: 10px var(--space-3); vertical-align: middle; transition: background-color var(--dur-fast) var(--ease-standard); }
@media (hover: hover) { .data-table tbody tr:hover td { --holder: var(--surface-hover); background-color: var(--surface-hover); } }
.data-table .is-numeric { text-align: right; font-variant-numeric: tabular-nums; }
.data-table .is-selection { width: 40px; padding-right: 0; }
/* The outer edge matches the first column's 12 px, so the header and the row menus sit as far from the right as the
 * first column sits from the left. */
.data-table .is-actions { width: 48px; padding: 0 var(--space-3) 0 var(--space-2); text-align: right; }
.data-table__name { color: var(--text-1); font-weight: var(--weight-medium); text-decoration: none; }
.data-table__secondary { display: block; color: var(--text-3); font-size: var(--text-13); line-height: var(--lh-13); overflow-wrap: anywhere; }
/* The sort button is exactly its text's height, so its focus dash sits below it, not across the letters. */
.data-table__sort { --dash-bottom: -5px; display: inline-flex; align-items: center; gap: var(--space-1); padding: 0; border: 0; background: none; color: inherit; font: inherit; letter-spacing: inherit; cursor: pointer; }
.data-table tr.is-selected td { --holder: var(--accent-tint); background-color: var(--accent-tint); }
@media (min-width: 640px) and (max-width: 1023px) { .data-table .is-low-priority { display: none; } }

.table-empty { padding: var(--space-10) var(--space-4); }

.table-pager { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3); flex-wrap: wrap; }
.table-pager__position { margin-right: auto; color: var(--text-2); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); font-variant-numeric: tabular-nums; }
.table-pager__more { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); width: 100%; }
.table-pager__more > .btn { width: 100%; }

/* Phone list items: filled items 4 px apart inside the card, no rules between them. */
.list-items { display: flex; flex-direction: column; gap: var(--space-1); list-style: none; margin: 0; padding: var(--space-1); }
.list-item {
  --holder: color-mix(in srgb, var(--surface) 50%, var(--surface-hover));
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-height: 56px;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background-color: var(--holder);
}
.list-item__primary { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.list-item__title { flex: 1; min-width: 0; font: var(--weight-semibold) 15px / 20px var(--font-sans); color: var(--text-1); overflow-wrap: anywhere; }
.list-item__meta { color: var(--text-2); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); overflow-wrap: anywhere; }
.list-item__extra { padding-top: var(--space-1); }

/* The table renders on tablet and desktop, the list items on phone — never both, never horizontal scroll. */
.responsive-table__list { display: none; }
@media (max-width: 639px) {
  .responsive-table__table { display: none; }
  .responsive-table__list { display: block; }
}

.bulk-bar {
  position: sticky;
  bottom: var(--space-4);
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  min-height: 56px;
  margin-top: var(--space-4);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-lg);
  --holder: var(--surface-overlay);
  --holder-below: var(--overlay-below);
  --surface-hover: var(--surface-overlay-hover);
  background-color: var(--surface-overlay);
  box-shadow: var(--elev-3);
}
.bulk-bar__count { color: var(--text-1); font-weight: var(--weight-medium); font-variant-numeric: tabular-nums; }
.bulk-bar__actions { margin-left: auto; display: flex; gap: var(--space-2); flex-wrap: wrap; }
@media (max-height: 700px) and (min-width: 640px) { .bulk-bar { bottom: var(--space-2); } }

/* == component: course progress label (client.ui.course-label) ==
 * Three layers inside a 28 px label: the tint base, the fill at the true fraction, and a 2 px bar under the fill in
 * the solid colour, which gives the fill's edge ≥ 3:1 against the tint so progress never relies on the pale fill. */
.course-label {
  --label-fg: var(--status-neutral-fg);
  --label-tint: var(--status-neutral-tint);
  --label-fill: transparent;
  --label-solid: transparent;
  position: relative;
  isolation: isolate;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  min-width: 0;
  max-width: 240px;
  padding: 0 10px 0 8px;
  border-radius: var(--radius-sm);
  background: var(--label-tint);
  color: var(--text-1);
  list-style: none;
}
.course-label--wide { width: 100%; max-width: 480px; }
.course-label--wide .course-label__name { flex: 1; }
.course-label[data-status="on-track"], .course-label[data-status="completed"] {
  --label-fg: var(--status-on-track-fg); --label-tint: var(--status-on-track-tint);
  --label-fill: var(--status-on-track-fill); --label-solid: var(--status-on-track-solid);
}
.course-label[data-status="behind"], .course-label[data-status="blocked"] {
  --label-fg: var(--status-behind-fg); --label-tint: var(--status-behind-tint);
  --label-fill: var(--status-behind-fill); --label-solid: var(--status-behind-solid);
}
.course-label[data-status="serious"] {
  --label-fg: var(--status-serious-fg); --label-tint: var(--status-serious-tint);
  --label-fill: var(--status-serious-fill); --label-solid: var(--status-serious-solid);
}
.course-label[data-status="paused"] {
  --label-fg: var(--status-paused-fg); --label-tint: var(--status-paused-tint);
  --label-fill: var(--status-paused-fill); --label-solid: var(--status-paused-solid);
}
.course-label__fill, .course-label__bar {
  position: absolute;
  left: 0;
  width: calc(var(--progress) * 100%);
  z-index: -1;
  animation: course-label-grow var(--dur-slow) var(--ease-standard);
}
.course-label__fill { top: 0; bottom: 0; background: var(--label-fill); border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.course-label[data-complete] .course-label__fill { border-radius: var(--radius-sm); }
.course-label__bar { bottom: 0; height: 2px; background: var(--label-solid); border-bottom-left-radius: var(--radius-sm); }
@keyframes course-label-grow { from { width: 0; } }
.course-label__icon { display: inline-flex; flex: none; color: var(--label-fg); }
.course-label__name {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: var(--weight-medium) var(--text-13) / var(--lh-13) var(--font-sans);
}
.course-label__status { flex: none; color: var(--label-fg); font: var(--weight-medium) var(--text-13) / var(--lh-13) var(--font-sans); }
.course-label__fraction { flex: none; color: var(--text-2); font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); font-variant-numeric: tabular-nums; }
.course-label[data-ended] .course-label__name { text-decoration: line-through; color: var(--status-neutral-fg); }
.course-label[data-tempo] { padding-right: 12px; }
/* The pace badge is one SVG (client.ui.icon/badge): its disc and glyph are drawn inside it; this places and lifts it. */
.course-label__pace {
  position: absolute;
  top: -8px;
  right: -8px;
  border-radius: var(--radius-full);
  box-shadow: var(--elev-1);
  color: var(--text-1);
}
.course-label { display: flex; }
.course-labels { display: flex; flex-wrap: wrap; gap: 10px; margin: 0; padding: 7px 0 0; list-style: none; }
/* In a list, a compact label is as wide as its content, up to 240 px, where a long course name truncates; several
 * labels share a line and wrap. */
.course-labels > .course-label { flex: 0 1 auto; }
.course-labels > .course-label--wide { flex-basis: 100%; max-width: 480px; }
.course-labels--full > .course-label { max-width: none; width: 100%; }
.course-labels__more { list-style: none; }

/* == component: stat tile and distribution bar (client.ui.tile) == */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-4); }
/* A link tile (client.ui.tile/link-tile): one compact raised card that is the link — the label at the left and the
 * figure at the right, both centred on one line in the same face; it lifts a level under the pointer, as a raised
 * control does, and takes the raised focus recipe from the keyboard. */
.link-tile {
  --holder: var(--surface);
  --holder-below: var(--bg);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 64px;
  padding: var(--space-3) var(--space-5);
  background-color: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  color: var(--text-1);
  text-decoration: none;
  transition: box-shadow var(--dur-fast) var(--ease-standard), background-color var(--dur-fast) var(--ease-standard);
}
.link-tile:visited { color: var(--text-1); }
@media (hover: hover) { .link-tile:hover { box-shadow: var(--elev-2); } }
.link-tile:active { box-shadow: var(--elev-1); transition-duration: var(--dur-instant); }
.link-tile__label { color: var(--text-2); font: var(--weight-medium) var(--text-16) / var(--lh-16) var(--font-sans); transition: color var(--dur-fast) var(--ease-standard); }
.link-tile:is(:hover, :focus-visible) .link-tile__label { color: var(--text-1); }
.link-tile__figure { font: var(--weight-medium) var(--text-18) / var(--lh-18) var(--font-sans); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* A link tile's optional parts stay inside its compact face: a caption line under the label, the tone's icon as a
 * round badge over the upper-right corner (the course label's pace badge, one size up), and a status distribution as a strip along the bottom edge, clipped to the card's corners by its own
 * holder so the card itself never clips its focus ring. */
.link-tile__text { display: flex; flex-direction: column; min-width: 0; }
/* A figure tile is the link tile's face with nothing to follow: no pointer, no lift, and a label that may carry its help
 * icon. A warning or an error rests the card on that status's pale tint; the badge over the corner says which. */
.link-tile--static { cursor: default; }
@media (hover: hover) { .link-tile--static:hover { box-shadow: var(--elev-1); } }
.link-tile--static .link-tile__label { color: var(--text-1); font: var(--weight-medium) var(--text-14) / var(--lh-14) var(--font-sans); }
.link-tile--static[data-tone="warning"] { background-color: var(--status-behind-tint); }
.link-tile--static[data-tone="error"] { background-color: var(--status-serious-tint); }
.link-tile__detail { color: var(--text-2); font: var(--weight-regular) var(--text-12) / var(--lh-12) var(--font-sans); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.link-tile__figure { flex: none; }
.link-tile__badge {
  position: absolute;
  top: -8px;
  right: -8px;
  border-radius: var(--radius-full);
  box-shadow: var(--elev-1);
}
.link-tile { isolation: isolate; }
.link-tile__strip { position: absolute; inset: 0; z-index: -1; border-radius: inherit; overflow: hidden; pointer-events: none; }
/* The distribution as the card's background: the strip's statuses in its order and proportions, each in the pale tint
 * the course progress label rests on, a card wide and beneath the text, which keeps its contrast on every tint. The
 * shares part by the strip's own 1 px gap, so each tint stands exactly over its segment (operator, 2026-09-28). */
.link-tile__fill { position: absolute; inset: 0; display: flex; gap: 1px; }
.link-tile__fill-segment { height: 100%; }
.link-tile__fill-segment--on-track { background: var(--status-on-track-tint); }
.link-tile__fill-segment--behind, .link-tile__fill-segment--blocked { background: var(--status-behind-tint); }
.link-tile__fill-segment--serious { background: var(--status-serious-tint); }
.link-tile__fill-segment--paused { background: var(--status-paused-tint); }
/* The key a distribution's tooltip carries: one row per status — the strip's solid swatch, its word, its count at the
 * right — then the caller's summary lines, all in the tooltip's caption face. */
/* A heading that carries an explanation (client.ui.overlay/help-tip with `heading`): the words and a small `info` mark
 * at the upper-right corner of the text as it wraps (measured: client.ui.overlay/place-mark!) are ONE button in the
 * heading's own face, size and colour; 14 px on the right keep room for the mark — the mark secondary in
 * `text-3` until the heading is hovered, focused or its tip pinned. */
.help-heading { --dash-x: calc(50% - 7px); position: relative; display: inline-block; padding: 0 14px 0 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.help-heading__mark { position: absolute; left: 100%; top: 0; display: inline-flex; margin: -0.2em 0 0 2px; color: var(--text-3); transition: color var(--dur-fast) var(--ease-standard); }
.help-heading:is(:hover, :focus-visible, [data-pinned]) .help-heading__mark { color: var(--text-1); }
/* A heading's help in the tooltip: what is counted, the conditions as a short list, the exclusion as a quieter note. */
.help-tip { max-width: 300px; }
.help-tip__title { margin: 0 0 var(--space-1); font-weight: var(--weight-medium); }
.help-tip__list { margin: 0; padding-left: var(--space-4); }
.help-tip__list > li + li { margin-top: 2px; }
.help-tip__note { margin: var(--space-2) 0 0; color: var(--text-2); }
.distribution-key { min-width: 200px; }
.distribution-key__list { display: grid; gap: var(--space-1); margin: 0; padding: 0; list-style: none; }
.distribution-key__row { display: flex; align-items: center; gap: var(--space-2); }
.distribution-key__swatch { flex: none; width: 10px; height: 10px; border-radius: var(--radius-xs); }
.distribution-key__word { flex: 1; }
.distribution-key__count { font-variant-numeric: tabular-nums; }
.distribution-key__summary { margin-top: var(--space-2); color: var(--text-2); }
.distribution-key__summary > p { margin: 0; }
.distribution-bar.distribution-bar--strip { position: absolute; inset-inline: 0; bottom: 0; height: 3px; margin: 0; border-radius: 0; gap: 1px; }
@media (max-width: 639px) {
  .stat-grid.stat-grid--links { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .link-tile { min-height: 56px; padding: var(--space-3) var(--space-4); }
}
@media (max-width: 639px) { .stat-grid { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); } }
.stat-tile { --holder: var(--surface); --holder-below: var(--bg); display: flex; flex-direction: column; gap: var(--space-1); min-height: 120px; padding: var(--space-5); background-color: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--elev-1); }
@media (max-width: 639px) { .stat-tile { padding: var(--space-4); } }
.stat-tile__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.stat-tile__label { color: var(--text-2); font: var(--weight-medium) var(--text-13) / var(--lh-13) var(--font-sans); }
/* A control that belongs to the label (what the figure counts) sits right after it; the negative block margin keeps
 * its full target without making the head taller than the label line. */
.stat-tile__label-group { display: inline-flex; align-items: center; gap: 2px; margin-block: calc((var(--lh-13) - var(--icon-button)) / 2); }
.stat-tile__tone { display: inline-flex; }
.stat-tile__tone--success { color: var(--status-on-track-fg); }
.stat-tile__tone--warning { color: var(--status-behind-fg); }
.stat-tile__tone--error { color: var(--status-serious-fg); }
.stat-tile__tone--info { color: var(--info-fg); }
.stat-tile__figure { color: var(--text-1); }
.stat-tile__sub { color: var(--text-3); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }
.stat-tile__link { align-self: flex-end; margin-top: auto; font-size: var(--text-13); }
.distribution-bar { display: flex; gap: 2px; height: 6px; margin-top: var(--space-2); border-radius: var(--radius-xs); overflow: hidden; background: var(--surface-2); }
.distribution-bar__segment { height: 100%; }
.distribution-bar__segment--on-track { background: var(--status-on-track-solid); }
.distribution-bar__segment--behind, .distribution-bar__segment--blocked { background: var(--status-behind-solid); }
.distribution-bar__segment--serious { background: var(--status-serious-solid); }
.distribution-bar__segment--paused { background: var(--status-paused-solid); }
.distribution-legend { margin-top: var(--space-1); color: var(--text-2); font: var(--weight-regular) var(--text-12) / var(--lh-12) var(--font-sans); }

/* == component: stat line (client.ui.stat-line) ==
 * Counted facts that wrap only between whole facts. Each item is unbreakable and carries the dot that LEADS it, in
 * `--stat-sep` of its own padding, so no dot ever follows the last item. The item list starts `--stat-sep` left of
 * the line and the line clips it: the leading space of whichever item starts a line — the first, or one wrapped onto
 * a new line — falls outside the line and its dot with it. The first item has no dot at all. The dot speaks as a
 * comma (the second `content`; the first is the fallback where alternative text is not understood). */
.stat-line { display: block; overflow: hidden; }
.stat-line__items { --stat-sep: 14px; display: flex; flex-wrap: wrap; row-gap: 2px; margin-inline-start: calc(-1 * var(--stat-sep)); }
.stat-line__item { position: relative; padding-inline-start: var(--stat-sep); white-space: nowrap; }
.stat-line__item + .stat-line__item::before {
  content: "·";
  content: "·" / ", ";
  position: absolute;
  inset-inline-start: 0;
  width: var(--stat-sep);
  color: var(--text-3);
  text-align: center;
}
.stat-line__value { font-variant-numeric: tabular-nums; }

/* == component: notice (client.ui.notice) == */
.notice {
  display: grid;
  grid-template-columns: 20px 1fr auto;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
}
.notice--info { background: var(--info-tint); --notice-fg: var(--info-fg); }
.notice--success { background: var(--status-on-track-tint); --notice-fg: var(--status-on-track-fg); }
.notice--warning { background: var(--status-behind-tint); --notice-fg: var(--status-behind-fg); }
.notice--error { background: var(--status-serious-tint); --notice-fg: var(--status-serious-fg); }
.notice__icon { display: inline-flex; color: var(--notice-fg); }
.notice__text { min-width: 0; }
.notice__title { color: var(--text-1); font: var(--weight-medium) var(--text-14) / var(--lh-14) var(--font-sans); }
.notice__body { color: var(--text-2); }
.notice__actions { display: flex; align-items: center; gap: var(--space-2); }
@media (max-width: 639px) {
  .notice { grid-template-columns: 20px 1fr; }
  .notice__actions { grid-column: 2; }
}

/* == component: empty, loading, error and status states (client.ui.state) == */
.empty-state { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); max-width: 360px; margin: 0 auto; padding: var(--space-10) var(--space-4); text-align: center; }
.empty-state__icon { color: var(--text-3); }
.empty-state__title { color: var(--text-1); }
.empty-state__body { color: var(--text-2); }
.empty-state__action { margin-top: var(--space-2); }
.skeleton { display: block; border-radius: var(--radius-xs); background: var(--surface-2); animation: skeleton-pulse var(--dur-pulse) ease-in-out infinite alternate; }
.skeleton--text { height: 12px; width: 60%; }
.skeleton--label { height: 28px; width: 160px; }
.skeleton--figure { height: 28px; width: 80px; }
@keyframes skeleton-pulse { from { opacity: 1; } to { opacity: 0.6; } }
.skeleton-rows { display: flex; flex-direction: column; }
.skeleton-rows__row { display: flex; align-items: center; gap: var(--space-4); height: var(--row-h); padding: 0 var(--space-3); }
.status-line { display: flex; align-items: center; gap: var(--space-2); color: var(--text-2); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }
.status-line__icon { display: inline-flex; color: var(--status-on-track-fg); }

/* == component: load state (client.ui.load-state, client.ui.page/empty-state) == */
.load-state { padding: var(--space-4) 0; }

/* == component: confirmation dialog (client.ui.dialog, client.ui.modal) ==
 * `.dialog-scope` is `client.views.dialog/focus-scope`'s wrapper: it holds the focus behaviour and draws nothing. The
 * dialog is rendered in the shell's overlay root and takes the overlay layer: a dialog opened over another overlay
 * stacks above it by opening later, and a dropdown opened in the dialog above the dialog. */
.dialog-scope { display: contents; }
.dialog-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: var(--backdrop);
  animation: dialog-backdrop-enter var(--dur-base) var(--ease-out);
}
@keyframes dialog-backdrop-enter { from { opacity: 0; } }
.dialog {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  width: min(440px, 100vw - 32px);
  max-height: calc(100dvh - 32px);
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  --holder: var(--surface-modal);
  --holder-below: var(--modal-below);
  --surface-hover: var(--surface-modal-hover);
  background-color: var(--surface-modal);
  box-shadow: var(--elev-4);
  color: var(--text-1);
  animation: dialog-enter var(--dur-base) var(--ease-out);
}
@keyframes dialog-enter { from { opacity: 0; transform: scale(var(--scale-enter)); } }
.dialog__title { color: var(--text-1); }
.dialog__body { color: var(--text-2); overflow-y: auto; }
.dialog__body > * + * { margin-top: var(--space-3); }
.dialog__subject { color: var(--text-1); font-weight: var(--weight-medium); overflow-wrap: anywhere; }
.dialog__actions { display: flex; justify-content: flex-end; gap: var(--space-2); flex-wrap: wrap; }
.dialog .field, .dialog .form-field { margin-bottom: 0; max-width: none; }
@media (max-width: 639px) {
  .dialog-backdrop { align-items: flex-end; padding: 0; }
  .dialog { width: 100%; border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
  .dialog__actions > .btn { flex: 1; }
}

/* == component: inline creation (client.ui.inline-create) == */
/* A full-width row: its focus dash sits at the content start, under the plus, not in the middle of the row. */
.inline-create__trigger {
  --dash-x: var(--space-4);
  --dash-shift: 0px;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  height: 44px;
  padding: 0 var(--space-4);
  border: 0;
  background-color: transparent;
  color: var(--accent);
  font: var(--weight-medium) var(--text-14) / var(--lh-14) var(--font-sans);
  text-align: left;
  cursor: pointer;
  /* Closing: the label is back at full opacity at once, under the form, and shows as the form fades away. */
  transition: background-color var(--dur-fast) var(--ease-standard), opacity 0s, visibility 0s;
}
[data-mode="customer"] .inline-create__trigger { color: var(--link); }
@media (hover: hover) { .inline-create__trigger:hover { background-color: var(--surface-hover); } }
/* The trigger and the form share one cell, the form on top. THE VISIBLE HEIGHT RUNS FROM THE TRIGGER'S 44 PX TO THE
 * FORM'S OWN, OVER THE WHOLE DURATION: the growing slot reserves the trigger's height as padding and pulls the form up
 * over it (a negative margin), so the part that animates (`0fr` to `1fr`) is only the height beyond 44 px. Animating
 * the whole form from nothing instead left a dead start, while it grew unseen under the trigger, and a hard stop where
 * the fold met the trigger mid-curve.
 *
 * Opening is the form mounting (its key changes with every opening), so it is an animation: the form's region fades in
 * over the trigger as the trigger's label fades out, the height grows on --ease-out, and the body fades in overlapping
 * the label's fade, never after an empty pause. Closing is `data-open` turning false on the same form, so it is a
 * transition, the inverse: the body fades out, the height folds on --ease-standard, which comes to rest at 44 px rather
 * than striking it, and only then does the region fade away to show the label beneath, already back at full opacity.
 * The body's fade-in fills only backwards: a `both` fill would hold its opacity and stop the closing transition from
 * starting. The closed form is `visibility: hidden` once folded: not focusable, not announced. */
.inline-create { --inline-create-h: 44px; display: grid; }
.inline-create > * { grid-area: 1 / 1; }
.inline-create[data-open="true"] > .inline-create__trigger {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-fast) var(--ease-standard), visibility 0s linear var(--dur-fast);
}
.inline-create__slot {
  display: grid;
  grid-template-rows: 1fr;
  padding-top: var(--inline-create-h);
  /* `clip`, not `hidden`: a hidden overflow is still a scroll container, so focusing the first field while the form
   * is short scrolled the content up and let it slide back as the form grew. A clipped box cannot scroll. */
  overflow: clip;
}
.inline-create[data-open="true"] > .inline-create__slot {
  animation: inline-create-open var(--dur-slow) var(--ease-out), inline-create-fade var(--dur-fast) var(--ease-standard);
}
.inline-create[data-open="false"] > .inline-create__slot {
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows var(--dur-slow) var(--ease-standard),
    opacity var(--dur-fast) var(--ease-standard) calc(var(--dur-slow) - var(--dur-fast)),
    visibility 0s linear var(--dur-slow);
}
@keyframes inline-create-open { from { grid-template-rows: 0fr; } }
.inline-create__form {
  margin-top: calc(-1 * var(--inline-create-h));
  overflow: clip;
  min-height: 0;
  --holder: var(--surface-hover);
  --holder-below: var(--hover-region-below);
  background-color: var(--surface-hover);
}
.inline-create__inner { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3) var(--space-4); }
.inline-create[data-open="true"] .inline-create__inner { animation: inline-create-fade var(--dur-base) var(--ease-out) var(--dur-instant) backwards; }
.inline-create[data-open="false"] .inline-create__inner { opacity: 0; transition: opacity var(--dur-fast) var(--ease-in); }
@keyframes inline-create-fade { from { opacity: 0; } }
.inline-create__row { display: flex; flex-direction: column; gap: var(--space-3); }
.inline-create__row .form-field { max-width: none; flex: 1; }
.inline-create__actions { display: flex; flex-direction: column; gap: var(--space-2); }
.inline-create__actions > .btn { width: 100%; }
.inline-create__help { color: var(--text-3); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }
@media (min-width: 768px) {
  .inline-create__row { flex-direction: row; align-items: flex-end; }
  .inline-create__actions { flex-direction: row; }
  .inline-create__actions > .btn { width: auto; }
}
.is-just-created { animation: just-created var(--dur-feedback) var(--ease-standard); }
@keyframes just-created { from { background: var(--accent-tint); } }

/* == component: avatar and identicon (client.ui.avatar) == */
.avatar {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: var(--avatar-size, 32px);
  height: var(--avatar-size, 32px);
  border-radius: var(--radius-full);
  overflow: hidden;
  background: var(--surface-2);
}
.avatar--24 { --avatar-size: 24px; }
.avatar--32 { --avatar-size: 32px; }
.avatar--40 { --avatar-size: 40px; }
.avatar--48 { --avatar-size: 48px; }
.avatar--72 { --avatar-size: 72px; }
.avatar > img, .avatar > svg { display: block; width: 100%; height: 100%; object-fit: cover; }

/* == component: menus and the profile menu (client.ui.menu) ==
 * `.menu` is the surface of a menu drawn in place; inside the overlay layer (`client.ui.overlay`) the popover is the
 * surface and the menu is only its list. Every row menu is in the overlay layer, out of the page's flow. */
.menu {
  min-width: 200px;
  padding: 6px;
  border-radius: var(--radius-lg);
  --holder: var(--surface-overlay);
  --holder-below: var(--overlay-below);
  --surface-hover: var(--surface-overlay-hover);
  background-color: var(--surface-overlay);
  box-shadow: var(--elev-3);
  transform-origin: top;
  animation: menu-enter var(--dur-base) var(--ease-out);
}
.overlay .menu { min-width: 0; padding: 0; border-radius: 0; background-color: transparent; box-shadow: none; animation: none; }
.menu__group + .menu__group { margin-top: var(--space-2); }
.menu__item[data-disabled] { color: var(--text-disabled); cursor: default; }
@keyframes menu-enter { from { opacity: 0; transform: translateY(calc(-1 * var(--move-sm))) scale(var(--scale-enter)); } }
.menu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 36px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--radius-md);
  background-color: transparent;
  color: var(--text-1);
  font: var(--weight-regular) var(--text-14) / var(--lh-14) var(--font-sans);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-standard);
}
@media (pointer: coarse) { .menu__item { height: 44px; } }
@media (hover: hover) { .menu__item:hover { background-color: var(--surface-hover); } }
.menu__item--destructive { color: var(--destructive-fg); }
/* Groups are separated by 8 px of space, never a drawn line; the element keeps its separator role. */
.profile-entry {
  display: grid;
  grid-template-columns: 48px 1fr;
  gap: var(--space-3);
  align-items: center;
  height: 72px;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text-1);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-standard);
}
@media (hover: hover) { .profile-entry:hover { background-color: var(--surface-hover); } }
.profile-entry__avatar { position: relative; width: 48px; height: 48px; }
.profile-entry__badge {
  --badge-disc: var(--surface-overlay);
  position: absolute;
  right: -2px;
  bottom: -2px;
  border-radius: var(--radius-full);
  box-shadow: var(--elev-1);
  color: var(--text-2);
}
.profile-entry__text { display: flex; flex-direction: column; min-width: 0; }
.profile-entry__name, .profile-entry__email { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.profile-entry__name { font: var(--weight-medium) var(--text-14) / var(--lh-14) var(--font-sans); }
.profile-entry__email { color: var(--text-2); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }

/* == component: overlay layer (client.ui.overlay) ==
 * One root per shell, the shell's last child. It draws no box of its own, so what it holds is positioned against the
 * document itself — no page element can clip, bury or make it inert — and React Aria keeps each card 8 px inside the
 * viewport. Everything in it takes ONE layer, `--z-overlay`, above the drawer; within the layer, overlays stack in the
 * order they opened. The library writes an extreme z-index inline on every card it positions; the layer token
 * replaces it, which is why that one declaration must be `!important`. `.overlay-scope` and `.overlay-anchor` only mark
 * where an overlay was opened from and draw nothing.
 *
 * Every floating card is `.overlay`: surface-overlay, radius lg, level 3, 6 px padding. It enters from 4 px toward
 * its anchor and from 0.98 over 200 ms and leaves in 150 ms — declared only where motion is welcome, so under reduced
 * motion there is no animation to wait for and a closing overlay is removed at once. */
.overlay-root { display: contents; }
.overlay-scope { display: contents; }
.overlay-anchor { display: none; }
.overlay {
  --overlay-from: translateY(calc(-1 * var(--move-sm)));
  --holder: var(--surface-overlay);
  --surface-hover: var(--surface-overlay-hover);
  min-width: 200px;
  max-width: calc(100vw - 16px);
  padding: 6px;
  border-radius: var(--radius-lg);
  background-color: var(--surface-overlay);
  box-shadow: var(--elev-3);
  color: var(--text-1);
  transform-origin: var(--trigger-anchor-point, top);
  z-index: var(--z-overlay) !important;
}
.overlay[data-placement="top"] { --overlay-from: translateY(var(--move-sm)); }
.overlay[data-placement="right"] { --overlay-from: translateX(calc(-1 * var(--move-sm))); }
.overlay[data-placement="left"] { --overlay-from: translateX(var(--move-sm)); }
@media (prefers-reduced-motion: no-preference) {
  .overlay[data-entering] { animation: overlay-enter var(--dur-base) var(--ease-out); }
  .overlay[data-exiting] { animation: overlay-exit var(--dur-exit) var(--ease-in) forwards; }
}
@keyframes overlay-enter { from { opacity: 0; transform: var(--overlay-from) scale(var(--scale-enter)); } }
@keyframes overlay-exit { to { opacity: 0; transform: var(--overlay-from) scale(var(--scale-enter)); } }
.overlay--popover {
  width: min(440px, calc(100vw - 16px));
  min-width: 320px;
  max-height: calc(100dvh - 16px);
  padding: var(--space-4);
  overflow-y: auto;
  scrollbar-gutter: stable;
  scroll-padding-block: var(--space-4);
}
.overlay--popover.overlay--wide { width: min(640px, calc(100vw - 16px)); }
.overlay--profile { width: min(300px, calc(100vw - 16px)); }
.overlay--listbox { display: flex; flex-direction: column; min-width: max(200px, var(--trigger-width, 0px)); }
.overlay--listbox.overlay--wide { width: max-content; max-width: min(480px, calc(100vw - 16px)); }
.overlay--tooltip {
  min-width: 0;
  max-width: 320px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans);
  pointer-events: none;
}
.overlay__body { display: contents; }
.overlay__dialog { display: block; }
@media (max-width: 639px) {
  .overlay--listbox { min-width: max(280px, var(--trigger-width, 0px)); }
  .overlay--popover { min-width: 0; }
}
@media (forced-colors: active) {
  .overlay { border: 1px solid CanvasText; }
}

/* == component: dropdowns (client.ui.dropdown) ==
 * The trigger is the well every input is (`.control`, the `well` focus recipe), laid out as a row: the chosen label,
 * then the chevron or the turning loader in the muted well ink. Every state of the trigger is the shared input
 * recipe's; this section restates none of it.
 * The list is a scroll region at most 320 px tall inside its card; an option is 36 px (44 px under a coarse pointer),
 * its label wraps, a second line may follow in `text-3`, and the chosen one carries a check and weight 500. The
 * highlighted option — by pointer, or by the arrows while focus stays in a combobox's input — takes the overlay's hover
 * fill, and under the keyboard the `item` dash at its content start. */
.dropdown__trigger { display: flex; align-items: center; text-align: left; cursor: pointer; }
.dropdown__value { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.dropdown__value[data-placeholder] { color: var(--well-muted); }
.dropdown__trigger:disabled .dropdown__value { color: var(--text-disabled); cursor: default; }
.dropdown__loader { display: inline-flex; }
.dropdown__loader svg { animation: btn-spin var(--dur-spin) var(--ease-linear) infinite; }
.combobox-box > .dropdown__input { padding-right: 36px; }
.combobox-box > .dropdown__toggle { position: absolute; top: 0; right: 0; width: 36px; height: 100%; padding: 0; border: 0; background: transparent; cursor: pointer; }
.dropdown__list { flex: 1 1 auto; min-height: 0; max-height: 320px; }
.dropdown__option {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 36px;
  padding: var(--space-2) 10px;
  border-radius: var(--radius-md);
  color: var(--text-1);
  font: var(--weight-regular) var(--text-14) / var(--lh-14) var(--font-sans);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-standard);
}
@media (pointer: coarse) { .dropdown__option { min-height: 44px; } }
.dropdown__option-text { display: flex; flex: 1; min-width: 0; flex-direction: column; }
.dropdown__option-label { overflow-wrap: anywhere; }
.dropdown__option-detail { color: var(--text-3); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); overflow-wrap: anywhere; }
.dropdown__check { display: inline-flex; visibility: hidden; color: var(--text-1); }
.dropdown__option[data-selected] .dropdown__option-label { font-weight: var(--weight-medium); }
.dropdown__option[data-selected] .dropdown__check { visibility: visible; }
.dropdown__option[data-hovered], .dropdown__option[data-focused] { background-color: var(--surface-hover); }
.dropdown__option[data-disabled] { color: var(--text-disabled); cursor: default; }
/* The `item` recipe draws the dash under DOM focus; a combobox keeps DOM focus in its input and highlights options
 * virtually, so the same dash is drawn for the library's `data-focus-visible` too. */
.dropdown__option[data-focus-visible]::after {
  content: "";
  position: absolute;
  left: var(--dash-x, 50%);
  bottom: var(--dash-bottom);
  transform: translateX(var(--dash-shift, -50%));
  width: var(--dash-w, var(--focus-dash-w));
  height: var(--focus-dash-h);
  border-radius: var(--radius-full);
  background: var(--dash-color, var(--focus-color));
  pointer-events: none;
}
.dropdown__empty { padding: var(--space-2) 10px; color: var(--text-3); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }
.dropdown__skeleton { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-3) 10px; }
.dropdown__skeleton-row { display: block; height: 12px; border-radius: var(--radius-xs); background-color: var(--surface-2); animation: skeleton-pulse var(--dur-pulse) var(--ease-linear) infinite alternate; }
.dropdown__skeleton-row:nth-child(2) { width: 80%; }
.dropdown__skeleton-row:nth-child(3) { width: 60%; }

/* == component: rows that lead to their record (client.ui.table, `row-links?`) ==
 * The whole row is a pointer target for its record and shows it with the pointer; the name link keeps the keyboard
 * stop, and its focus fills the row as a hover does. The actions cell is a protected zone: 56 px wide, never a pointer
 * target for the row, its 32 px icon button hit-tested over 44 × 44. */
.data-table--row-links tbody tr, .list-items--row-links .list-item { cursor: pointer; }
.data-table--row-links tbody tr:has(.row-link:focus-visible) td { --holder: var(--surface-hover); background-color: var(--surface-hover); }
.list-items--row-links .list-item:has(.row-link:focus-visible) { --holder: var(--surface-hover); background-color: var(--surface-hover); }
.data-table--row-links .is-actions { width: 56px; cursor: default; touch-action: manipulation; }
.data-table--row-links .is-actions .icon-btn::before { content: ""; position: absolute; inset: -6px; }

/* == component: section title (client.ui.page/section-title) == */
.section-title { margin: var(--space-6) 0 var(--space-3); font: var(--weight-semibold) var(--text-16) / var(--lh-16) var(--font-sans); color: var(--text-1); }
.section-title:first-child { margin-top: 0; }

/* == component: stepper (client.ui.page/stepper) ==
 * Filled markers, no rings: upcoming is the hover fill with `text-2`, the current step the accent fill, a done step
 * the accent tint with the accent check. The position is also `aria-current`, never colour alone. */
.stepper { display: flex; align-items: center; gap: var(--space-4); margin: 0; padding: 0; list-style: none; flex-wrap: wrap; }
.stepper__step { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--text-2); }
.stepper__marker {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-full);
  background-color: var(--surface-hover);
  color: var(--text-2);
  font: var(--weight-medium) var(--text-12) / 1 var(--font-sans);
  font-variant-numeric: tabular-nums;
}
.stepper__step--current { color: var(--text-1); font-weight: var(--weight-medium); }
.stepper__step--current .stepper__marker { background-color: var(--accent); color: var(--accent-ink); }
.stepper__step--done .stepper__marker { background-color: var(--accent-tint); color: var(--accent); }
.stepper__compact { display: none; color: var(--text-2); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }
@media (max-width: 639px) {
  .stepper { display: none; }
  .stepper__compact { display: block; }
}

/* == component: chart (client.ui.chart) == */
.chart-card { --holder: var(--surface); --holder-below: var(--bg); display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-5) var(--space-6); background-color: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--elev-1); min-width: 0; }
.chart-card__header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-4); min-height: 32px; }
/* The data first: the title (its explanation behind its own help mark), and at the right the chart's one headline
 * figure — large, tabular — with its unit quiet beside it, then the icon that swaps the chart for its table. */
.chart-card__title { flex: 1 1 11em; margin: 0; min-width: 0; color: var(--text-1); font: var(--weight-semibold) var(--text-16) / var(--lh-16) var(--font-sans); }
.chart-card__aside { display: flex; align-items: center; gap: var(--space-3); flex: none; }
.chart-card__figure { display: inline-flex; align-items: baseline; gap: var(--space-1); }
.chart-card__figure-value { color: var(--text-1); font: var(--weight-semibold) var(--text-24) / var(--lh-24) var(--font-sans); font-variant-numeric: tabular-nums; }
.chart-card__figure-unit { color: var(--text-2); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }
.chart-card__caption, .chart-card__note { color: var(--text-2); font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }
.chart-card__note { color: var(--text-3); }
.chart-card__plot { position: relative; width: 100%; }
.chart-card__table { display: block; }
/* The toggle hides one of the two with the hidden attribute, which a display rule would otherwise override. */
.chart-card__table[hidden], .chart-card__plot[hidden] { display: none; }
.chart-card__table .data-table th { white-space: normal; }
.chart-card__table .data-table th[scope="row"] { min-width: 9em; }
.chart-card__table .data-table td { white-space: nowrap; }
.chart-card__table .data-table th { position: static; }
.chart { display: block; width: 100%; height: 240px; overflow: visible; }
@media (max-width: 639px) { .chart { height: 200px; } }
.chart__grid { stroke: var(--chart-grid); stroke-width: 1; }
.chart__baseline { stroke: var(--chart-baseline); stroke-width: 1; }
.chart__axis-label, .chart__value { fill: var(--chart-axis); font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); font-variant-numeric: tabular-nums; }
.chart__value { fill: var(--text-2); }
.chart__series-1 { fill: var(--chart-series-1); }
.chart__series-2 { fill: var(--chart-series-2); }
.chart__neutral { fill: var(--chart-neutral); }
.chart__on-track { fill: var(--status-on-track-solid); }
.chart__behind, .chart__blocked { fill: var(--status-behind-solid); }
.chart__serious { fill: var(--status-serious-solid); }
.chart__paused { fill: var(--status-paused-solid); }
/* How an ended run ended: completed in the completed status colour; not passed and terminated in the neutral of the
 * not-completed label, the not-passed one hatched so the two differ by more than a shade. Test outcomes take the two
 * chart series. */
.chart__completed-passed { fill: var(--status-on-track-solid); }
.chart__completed-not-passed, .chart__terminated { fill: var(--status-paused-solid); }
.chart__passed { fill: var(--chart-series-1); }
.chart__below-threshold { fill: var(--chart-series-2); }
.chart__hatch-line { stroke: var(--surface); stroke-width: 1.5; }
.chart__gap { stroke: var(--surface); stroke-width: 2; }
.chart-rows { display: grid; grid-template-columns: fit-content(200px) minmax(0, 1fr); align-items: center; row-gap: 8px; column-gap: var(--space-3); }
.chart-rows__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--text-2); font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); }
.chart-rows__bar { display: block; width: 100%; height: 16px; overflow: visible; }
.chart-rows--valued { padding-right: 56px; }
@media (max-width: 639px) { .chart-rows { grid-template-columns: fit-content(120px) minmax(0, 1fr); } }
.chart-legend { display: flex; flex-wrap: wrap; gap: var(--space-4); margin: 0; padding: 0; list-style: none; color: var(--text-2); font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); }
.chart-legend__item { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend__swatch { width: 10px; height: 10px; border-radius: var(--radius-xs); }
.chart-legend__swatch--series-1 { background: var(--chart-series-1); }
.chart-legend__swatch--series-2 { background: var(--chart-series-2); }
.chart-legend__swatch--neutral { background: var(--chart-neutral); }
.chart-legend__swatch--on-track { background: var(--status-on-track-solid); }
.chart-legend__swatch--behind, .chart-legend__swatch--blocked { background: var(--status-behind-solid); }
.chart-legend__swatch--serious { background: var(--status-serious-solid); }
.chart-legend__swatch--paused { background: var(--status-paused-solid); }
.chart-legend__swatch--completed-passed { background: var(--status-on-track-solid); }
.chart-legend__swatch--completed-not-passed, .chart-legend__swatch--terminated { background: var(--status-paused-solid); }
.chart-legend__swatch--passed { background: var(--chart-series-1); }
.chart-legend__swatch--below-threshold { background: var(--chart-series-2); }
/* A hatched segment has a hatched swatch, so the legend names the pattern it shows. */
.chart-legend__swatch--blocked, .chart-legend__swatch--completed-not-passed { background-image: repeating-linear-gradient(45deg, transparent 0 2px, var(--surface) 2px 3.5px); }
.chart-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-1); min-height: 112px; padding: var(--space-4) 0; text-align: center; color: var(--text-2); }
.chart-empty__title { color: var(--text-1); font-weight: var(--weight-medium); }
.chart-columns { display: grid; grid-template-columns: 32px minmax(0, 1fr); grid-template-rows: 240px auto; column-gap: var(--space-2); row-gap: var(--space-1); }
@media (max-width: 639px) { .chart-columns { grid-template-rows: 200px auto; } }
.chart-columns > .chart { height: 100%; }
.chart-columns__y { position: relative; }
.chart-columns__tick { position: absolute; right: 0; transform: translateY(-50%); color: var(--chart-axis); font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); font-variant-numeric: tabular-nums; }
.chart-columns__x { grid-column: 2; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); color: var(--chart-axis); font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); text-align: center; font-variant-numeric: tabular-nums; }
.chart-columns__x > span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
@media (max-width: 639px) {
  .chart-columns__x > span { white-space: normal; text-overflow: clip; overflow-wrap: anywhere; }
  .chart-columns--dense .chart-columns__x > span { white-space: nowrap; overflow: visible; }
  .chart-columns--dense .chart-columns__x > span:nth-child(even) { visibility: hidden; }
}
.chart-rows__value { fill: var(--text-2); font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); font-variant-numeric: tabular-nums; }

/* On a phone the chart text steps up one role: captions and notes to body, legends, axes and row labels to small,
 * so a chart is read at arm's length (rulebook, charts). */
@media (max-width: 639px) {
  .chart-card__caption, .chart-card__note { font-size: var(--text-14); line-height: var(--lh-14); }
  .chart-legend, .chart-rows__label, .chart-columns__tick, .chart-columns__x { font-size: var(--text-13); line-height: var(--lh-13); }
  .chart-rows__value { font-size: var(--text-13); }
}

/* == forced colours: the boundaries a control needs to be found at all ==
 * Forced colours drop shadows and replace fills with the system's, so a recessed input, a raised button, a checkbox
 * and a segmented track would vanish. Here, and only here, they take a 1 px boundary in a system colour — the
 * platform exception declared in `bb ops/check-css-framing.clj`. The selected segment takes the system selection
 * colours, since its raised thumb is gone too. */
@media (forced-colors: active) {
  .control, .input, .check__box, .btn, .icon-btn, .segmented { border: 1px solid ButtonText; }
  .menu, .dialog, .hover-card, .bulk-bar { border: 1px solid CanvasText; }
  .segmented__item[aria-selected="true"], .segmented__item[aria-checked="true"] {
    forced-color-adjust: none;
    background-color: Highlight;
    color: HighlightText;
  }
  .segmented__item[aria-selected="true"] .segmented__count,
  .segmented__item[aria-checked="true"] .segmented__count { color: inherit; }
}

/* == component: hover card (client.ui.overlay/hover-card; the tooltip, the course label, the service-account tag) ==
 * The bubble a pointer or keyboard focus shows beside an element, drawn in the overlay layer (so above the sticky table
 * header and outside every clipping card), placed by script above its host or below it without room. It looks as the
 * tooltip always has, and fades in on the shared motion. */
.hover-card {
  position: absolute;
  z-index: var(--z-tooltip);
  width: max-content;
  max-width: 320px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background-color: var(--surface-overlay);
  box-shadow: var(--elev-3);
  color: var(--text-1);
  font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans);
  white-space: normal;
  pointer-events: none;
  animation: hover-card-in var(--dur-fast) var(--ease-out);
}
@keyframes hover-card-in { from { opacity: 0; } }
