/* GENERATED by shared/console-ui/sync.mjs — do not edit. Edit shared/console-ui/. */
/* ============================================================================
   THIS IS THE DESIGN. Both the Archive console and the CMS are built from it.

   Do not hardcode a colour, size, spacing, radius, duration or z-index
   anywhere else, and do not invent a value that is not here. If something
   genuinely has no token, ADD IT HERE FIRST — with a comment saying what it
   is for — then use it by name. A raw hex or px inside admin/index.html or
   archive-console/public/style.css is a bug, not a local decision.

   console-ui / tokens.css
   The single source of truth for both owner tools: the Archive console
   (archive-console, served from Hermes) and the CMS (admin, served locally).

   Rules for this file:
   - Semantic names only. `--text`, not `--ink`. `--line`, not `--hair`.
   - Every raw colour, size and duration used by either tool is declared here
     exactly once. If a value appears in an app stylesheet, it is a bug.
   - Light is the reference theme. Dark redefines only what changes.
   - The four portfolio front ends do NOT consume this file. Their contract is
     assets/design-system.js and is unrelated.
   ========================================================================= */

:root {
  /* Light only. There is no dark theme: one appearance, one set of values,
     nothing to keep in step. */
  color-scheme: light;

  /* --- Type -------------------------------------------------------------
     One family, six sizes. Berkeley Mono sets wider than a sans at the same
     size, so leading is a token rather than a per-rule afterthought. */
  --font-mono: "Berkeley Mono", "SF Mono", Monaco, ui-monospace, monospace;

  --type-caption: 10px;   /* counts, unit suffixes, chip text */
  --type-label: 11px;     /* field labels, buttons, filter controls */
  --type-body: 13px;      /* default; body element inherits this */
  --type-subtitle: 15px;  /* long-form editing: description, essay */
  --type-title: 19px;     /* page title */
  --type-heading: 22px;   /* work detail title */

  --leading-tight: 1.25;  /* titles */
  --leading-snug: 1.4;    /* labels, chips, table cells */
  --leading-body: 1.55;   /* paragraphs and inputs */

  --track-label: 0.08em;  /* uppercase labels only */
  --track-tight: -0.015em;/* 19px and up */

  --weight-regular: 400;
  --weight-bold: 700;

  /* --- Space ------------------------------------------------------------
     A 2px-based ramp. Nothing between two steps; pick the nearer step. */
  --space-1: 2px;
  --space-2: 4px;
  --space-3: 6px;
  --space-4: 8px;
  --space-5: 10px;
  --space-6: 12px;
  --space-8: 16px;
  --space-10: 20px;
  --space-12: 24px;
  --space-16: 32px;
  --space-20: 40px;
  --space-24: 48px;

  /* --- Size -------------------------------------------------------------
     Three control heights, no more. Compact for dense repeated rows (a media
     row's field grid), default everywhere else, touch for the Save bar and
     any control that ends an edit. */
  --control-h-compact: 22px;
  --control-h: 28px;
  --control-h-touch: 36px;

  --card-w: 280px;          /* archive grid minimum tile */
  --panel-w: 390px;         /* inspector */
  --panel-w-min: 320px;
  --dialog-w-narrow: 480px; /* sign in, confirm, single question */
  --dialog-w-wide: 1120px;  /* work detail, asset chooser, media preview */
  --shell-max: 1320px;      /* CMS main column */
  --shell-gutter: var(--space-8);
  --header-h: 44px;

  --radius-ui: 2px;

  /* --- Layer ------------------------------------------------------------- */
  --z-base: 0;
  --z-sticky: 10;
  --z-header: 20;
  --z-savebar: 30;
  --z-dialog: 40;
  --z-toast: 50;
  --z-skip-link: 100;   /* above everything, only while focused */

  /* --- Icon -------------------------------------------------------------- */
  --size-check: 13px;
  --size-thumb: 56px;
  --size-textarea-min: 132px;
  --size-filter-field: 200px;
  /* A justified wall: each tile's basis is its aspect ratio × this. The floor
     must stay below the narrowest basis (0.5 × basis) or portrait tiles clamp
     wider than their ratio and break the row's shared height. */
  --size-tile-basis: 175px;
  --size-tile-min: 80px;
  --size-filter-field-wide: 320px;
  --size-data-title-col: 280px;  /* Data table: the column that stays put */
  --size-data-col: 180px;        /* Data table: every other column */
  /* The fixed save bar's height, so a page can leave room under its content. */
  --savebar-h: calc(var(--control-h-touch) + 2 * var(--space-3) + var(--size-hairline));
  /* The Work popup save bar wraps to two lines on narrow screens. */
  --savebar-mobile-h: calc((2 * var(--control-h-touch)) + (4 * var(--space-3)) + var(--size-hairline));
  /* A crop or preview stage inside a dialog: bounded by the viewport, capped. */
  --stage-w-max: 760px;
  --stage-h-max: 56vh;
  --underline-offset: 3px;
  --size-hairline: 1px;  /* the one rule weight; also the visually-hidden box */

  --icon-sm: 12px;
  --icon: 16px;
  --icon-lg: 20px;
  --icon-stroke: 1.25;

  /* --- Opacity ----------------------------------------------------------- */
  --opacity-disabled: 0.45;
  --opacity-hover: 0.86;

  /* --- Surface ----------------------------------------------------------
     Neutral grey. No warm tint: the artwork carries the colour, the chrome
     does not compete with it. */
  --surface: #f2f2f2;
  --surface-raised: #ffffff;
  --surface-subtle: #e4e4e4;
  --surface-sunken: #eaeaea;

  /* --- Text -------------------------------------------------------------
     Contrast against --surface-raised (#ffffff), the lightest ground:
       text   21.0:1   any size
       muted   7.0:1   any size, including 10px labels
       faint   3.1:1   NON-ESSENTIAL only — placeholder, disabled, unit hints.
                       Never a field label. */
  --text: #000000;
  --text-muted: #5c5c5c;
  --text-faint: #8a8a8a;
  --text-inverse: #ffffff;

  /* --- Line -------------------------------------------------------------
     `line` separates, `line-strong` bounds. A control that can be typed in or
     pressed uses line-strong on hover/focus so its edge clears 3:1. */
  --line: #cccccc;
  --line-strong: #5c5c5c;

  /* --- State ------------------------------------------------------------
     Red, green, yellow, blue — flat and saturated, no tinting toward the
     chrome. Each text value clears 4.5:1 on white.

     Yellow is the exception. No yellow dark enough to pass as small text on
     white still reads as yellow — it reads as mud. So yellow is only ever a
     FILL, with black text on it: 15.9:1, and unmistakably yellow. Warning
     *words* on a white ground are plain black beside a yellow mark. */
  --success: #00802b;   /*  5.1:1  */
  --error:   #d10000;   /*  5.6:1  */
  --warning: #ffcc00;   /* a fill, never text. Black on it = 15.9:1 */
  --info:    #0057d9;   /*  6.2:1  */

  --success-fill: #00a838;
  --error-fill:   #ff2020;
  --warning-fill: #ffcc00;
  --info-fill:    #0057d9;

  --success-wash: #e2f6e9;
  --error-wash:   #ffe8e8;
  --warning-wash: #fff6cc;
  --warning-ink: #000000;   /* text that sits on the yellow */
  --info-wash:    #e4eeff;

  /* Black selects and black focuses. Colour is reserved for state — a blue
     button competes with the work, which is the only thing on screen allowed
     to carry colour. */
  --selected-bg: #000000;
  --selected-text: #ffffff;
  /* Nothing marks an unsaved field with colour. The save bar carries the count. */
  --dirty-mark: var(--warning);
  --dirty-text: var(--text);

  /* --- Media ------------------------------------------------------------
     Media always sits on true black so a still is judged
     against a constant, and letterbox bars are honest rather than cropped. */
  --media-bg: #000000;
  --media-text: #ffffff;
  --scrim: rgba(0, 0, 0, 0.76);

  /* --- Composed ---------------------------------------------------------- */
  --border-ui: 1px solid var(--line);
  --border-ui-strong: 1px solid var(--line-strong);
  --focus-ring: 2px solid var(--text);
  --focus-offset: -2px;
  --shadow-dialog: 0 0 0 100vmax var(--scrim);
  --shadow-popup: 0 20px 80px rgba(0, 0, 0, 0.35);
  --motion: 150ms ease-out;

  --scrollbar-bg: var(--surface);
  --scrollbar-thumb: var(--line);
  --scrollbar-thumb-hover: var(--line-strong);

  /* --- Existing owner compositions ----------------------------------------
     These preserve the adopted screens exactly, including their older compact
     measurements. They are not a new spacing ramp or a portfolio design. */
  --size-full: 100%;              /* fill the containing box */
  --viewport-w: 100vw;            /* viewport-bounded owner dialogs */
  --viewport-h: 100vh;            /* fallback viewport height */
  --viewport-dynamic-h: 100dvh;   /* panels with browser chrome accounted for */
  --size-scrollbar: var(--space-4);
  --size-rule-strong: var(--space-1);
  --space-row-inset: 3px;         /* retained compact source-row inset */
  --space-relation-gap: .25rem;   /* root-relative relation stack, not body px */
  --size-em: 1em;                 /* current-font marker and line-clamp unit */
  --size-character: 1ch;          /* table/control text alignment */
  --clip-hidden: inset(50%);      /* accessibility: hide paint, not semantics */
  --transform-skip-hidden: translateY(-200%); /* offscreen until focused */
  --colour-clear: transparent;   /* explicit unpainted borders/surfaces */

  --leading-control: 1;
  --leading-inspector-title: 1.2; /* retained compact inspector heading */
  --leading-preview-state: 1.35; /* wrapped source preview status */
  --leading-textarea-compact: 1.45;
  --leading-media-flag: 1.6;
  --track-field: 0.02em;          /* Archive field metadata */
  --track-brand: 0.03em;          /* Archive's quiet header brand */
  --track-media-count: 0.06em;

  --z-local-sticky: 1;           /* actions/first column within their panel */
  --z-media-label: 2;            /* selection and labels over artwork */
  --z-archive-panel: var(--z-header); /* retained panel layer below header */
  --z-archive-header: var(--z-dialog); /* existing header/overlay ordering */
  --opacity-primary-hover: 0.72; /* Archive's retained primary control */
  --opacity-reversed: .65;       /* reversed relation evidence */
  --opacity-pulse-high: 0.85;
  --opacity-pulse-low: 0.4;
  --duration-preview-shimmer: 1.4s;
  --duration-sentinel-pulse: 1.2s;
  --duration-reduced: 0.01ms;     /* eliminate transitions without event loss */
  --motion-retained-row: 150ms ease-out; /* legacy field/row easing; reduced duration still overrides */

  --ratio-preview-fallback: 3 / 2; /* unknown preview shape, never a crop */
  --ratio-still-fallback: 4 / 3;
  --ratio-video-fallback: 16 / 9;
  --ratio-square: 1;             /* contained file tile, not media dimensions */
  --ratio-work-fallback: 1.4;     /* existing unknown Archive cover basis */
  --grow-work-fallback: 1.3333;   /* CMS flex multiplier, not CSS ratio grammar */
  --grow-trailing-space: 999999;  /* justified wall's non-stretching last row */
  --size-tile-basis-compact: 140px; /* CMS at <=900px */
  --size-tile-basis-mobile: 120px;  /* CMS at <=640px */
  --size-preview-row-w: 54px;
  --size-preview-row-h: 50px;
  --size-preview-media-max: 340px;
  --size-preview-empty: var(--size-tile-basis-mobile);
  --size-continuous-status: var(--size-thumb);
  --size-empty-list: var(--size-filter-field);
  --size-filter-bar-min: var(--space-20);
  --size-textarea-compact: var(--size-preview-row-w);
  --size-inspector-preview: 520px;
  --size-inspector-preview-short: 360px;
  --size-source-grid-min: 820px;
  --size-source-row-min: 68px;
  --size-source-row-mobile: 100px;
  --size-source-empty: 72px;
  --size-source-preview-h: 52vh;
  --size-source-preview-mobile-h: 34vh;
  --size-source-attachment-w: 96px;
  --size-source-thumb: 96px;
  --size-source-attachment-h: 64px;
  --size-owner-list-min: 460px;
  --size-owner-empty: var(--size-data-col);
  --size-work-tags: 220px;
  --size-preview-note: 34ch;
  --size-match-preview-h: 160px;
  --size-match-search: 420px;
  --size-match-results-h: var(--size-inspector-preview-short);
  --size-match-empty: var(--size-work-tags);
  --size-data-cell-min: 150px;
  --size-cover-choice: 14px;
  --size-media-inspector-w: 1100px;
  --size-media-inspector-chrome: var(--size-work-tags);

  /* Archive Sort: compact search, readable help, fluid cards and contained media. */
  --size-sort-search: 220px;
  --size-sort-help: 70ch;
  --size-sort-card-min: 240px;
  --size-sort-preview-max: 420px;
  --tracks-sort-grid: repeat(auto-fill, minmax(min(var(--size-full), var(--size-sort-card-min)), 1fr));
  --opacity-sort-count: 0.6; /* subordinate count beside each filter label */
  --opacity-sort-context: var(--opacity-hover); /* keep muted card text legible */
  --opacity-sort-quiet: var(--opacity-reversed); /* secondary navigation */

  /* CMS media stage bounds and artwork tone swatch. */
  --size-crop-card-viewport: 92vw;
  --size-crop-media-viewport: 84vw;
  --size-preview-dialog-media: 680px;
  --size-preview-dialog-h: 60vh;
  --size-media-dialog-h: 76vh;
  --size-cms-card-preview-max: 360px; /* contain tall CMS chooser previews without cropping */
  --size-cms-chooser-preview-h: 240px; /* keep chooser file identity and action visible beside natural-shape media */
  --size-tone-swatch: var(--space-5);
  --radius-tone-swatch: var(--space-row-inset);
  --offset-tone-swatch: calc(-1 * var(--size-hairline));
  --outline-crop-shade: 100vmax solid var(--scrim);
  --outline-input: var(--size-hairline) solid var(--text);
  --offset-input-focus: calc(-1 * var(--size-hairline));
  --shadow-source-selected: inset var(--space-row-inset) 0 0 var(--text);
  --shadow-match-selected: inset 0 0 0 var(--size-rule-strong) var(--text);
  --shadow-table-selected: inset var(--size-rule-strong) 0 0 var(--selected-bg);
  --background-media-label: linear-gradient(var(--colour-clear), var(--scrim));
  --background-media-overlay: color-mix(in srgb, var(--surface) 82%, var(--colour-clear));
  --background-window-spacer: repeating-linear-gradient(135deg, var(--surface), var(--surface) var(--space-4), var(--surface-subtle) var(--space-4), var(--surface-subtle) var(--space-8));
  --background-preview-pending: linear-gradient(90deg, var(--colour-clear) 0%, var(--surface-subtle) 50%, var(--colour-clear) 100%) var(--surface-sunken);
  --background-preview-size: 200% 100%;
  --position-preview-start: 120% 0;
  --position-preview-end: -20% 0;

  /* Fixed composition tracks: keep current widths and breakpoints unchanged. */
  --tracks-archive-filters: minmax(240px, 1.5fr) 72px repeat(5, minmax(112px, 1fr)) auto auto;
  --tracks-archive-filters-dense: minmax(180px, 1fr) 112px 104px auto auto;
  --tracks-source-filters: minmax(220px, 2fr) repeat(3, minmax(120px, 1fr)) auto auto;
  --tracks-source-more: repeat(auto-fit, minmax(160px, 1fr));
  --tracks-source-row: 62px 104px minmax(200px, 1.6fr) 124px 100px minmax(140px, 1fr) 108px;
  --tracks-source-row-mobile: 62px minmax(0, 1fr);
  --tracks-source-facts: 100px minmax(0, 1fr);
  --tracks-source-navigation: minmax(76px, auto) minmax(0, 1fr) minmax(76px, auto);
  --tracks-source-attachment: 96px 150px minmax(0, 1fr) auto;
  --tracks-source-decision: minmax(0, 1fr) 144px;
  --tracks-credit-fields: minmax(0, 1.5fr) minmax(0, 1fr) minmax(88px, 0.7fr) auto;
  --tracks-owner-filters: minmax(180px, 1fr) repeat(2, minmax(130px, 180px)) auto auto;
  --tracks-data-controls: minmax(180px, 1fr) minmax(130px, 220px) auto;
  --tracks-owner-row: minmax(300px, 1fr) minmax(100px, auto);
  --tracks-owner-row-mobile: minmax(0, 1fr) minmax(80px, max-content);
  --tracks-media-details: minmax(64px, auto) minmax(0, 1fr);
  --tracks-media-tiles: repeat(auto-fill, minmax(132px, 1fr));
  --tracks-works-tools: minmax(0, 1fr) repeat(3, minmax(0, 180px));
  --tracks-cms-site-row: minmax(112px, 0.5fr) minmax(112px, 0.6fr) minmax(220px, 1.5fr);
  --tracks-cms-site-row-mobile: minmax(88px, 0.7fr) minmax(72px, 0.6fr) minmax(0, 1.6fr);
  /* Archive owner review: a spacious media surface with a bounded editor. */
  --work-review-w: 1408px;
  --tracks-work-popup: minmax(0, 1fr) var(--panel-w);
}

@media (prefers-reduced-motion: reduce) {
  :root { --motion: 1ms linear; }
}

/* ============================================================================
   console-ui / base.css — reset, fonts, typography, focus, scrollbars.
   Requires tokens.css. Sets no colour or size that is not a token.

   Font files: both servers expose Berkeley Mono at /fonts/. If an app cannot
   serve that path, it declares its own @font-face block and skips this file's
   — the path is the only thing that may legitimately differ.
   ========================================================================= */

@font-face {
  font-family: "Berkeley Mono";
  src: url("/fonts/BerkeleyMono-Regular.woff2") format("woff2");
  font-style: normal; font-weight: 400; font-display: swap;
}
@font-face {
  font-family: "Berkeley Mono";
  src: url("/fonts/BerkeleyMono-Bold.woff2") format("woff2");
  font-style: normal; font-weight: 700; font-display: swap;
}
@font-face {
  font-family: "Berkeley Mono";
  src: url("/fonts/BerkeleyMono-Oblique.woff2") format("woff2");
  font-style: italic; font-weight: 400; font-display: swap;
}
@font-face {
  font-family: "Berkeley Mono";
  src: url("/fonts/BerkeleyMono-Bold-Oblique.woff2") format("woff2");
  font-style: italic; font-weight: 700; font-display: swap;
}

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

html, body {
  margin: 0;
  min-height: var(--size-full);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--type-body);
  line-height: var(--leading-body);
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
}

/* Large type tightens; small type stays open. Without this every size inherits
   one leading value and nothing shares a baseline. */
h1, h2, h3, h4 {
  margin: 0;
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  letter-spacing: var(--track-tight);
}
h1 { font-size: var(--type-title); }
h2 { font-size: var(--type-subtitle); letter-spacing: 0; }
h3, h4 { font-size: var(--type-body); letter-spacing: 0; }
p { margin: 0; }

a { color: inherit; text-underline-offset: var(--underline-offset); }

/* The one uppercase treatment in the system. Anything else stays sentence case,
   because uppercase mono at 10-11px is slow to read in quantity. */
.ui-label {
  display: block;
  color: var(--text-muted);
  font-size: var(--type-label);
  line-height: var(--leading-snug);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.ui-caption { color: var(--text-muted); font-size: var(--type-caption); line-height: var(--leading-snug); }
.ui-hint    { color: var(--text-muted); font-size: var(--type-label); line-height: var(--leading-body); }
.ui-strong  { font-weight: var(--weight-bold); }

/* Numbers that are compared down a column line up. */
.ui-numeric { font-variant-numeric: tabular-nums; }

button, input, select, textarea {
  min-width: 0;
  border-radius: var(--radius-ui);
  color: inherit;
  font: inherit;
}

/* One focus treatment for every interactive element in both tools. It is drawn
   inside the control's own box so a focused row never shifts its neighbours. */
:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
:focus:not(:focus-visible) { outline: none; }

.ui-skip-link {
  position: absolute;
  left: var(--space-4);
  top: var(--space-4);
  z-index: var(--z-skip-link);
  padding: var(--space-2) var(--space-4);
  border: var(--border-ui-strong);
  background: var(--surface-raised);
  transform: var(--transform-skip-hidden);
}
.ui-skip-link:focus { transform: none; }

.ui-visually-hidden {
  position: absolute;
  width: var(--size-hairline); height: var(--size-hairline);
  margin: calc(-1 * var(--size-hairline)); padding: 0;
  overflow: hidden;
  clip-path: var(--clip-hidden);
  white-space: nowrap;
}

::-webkit-scrollbar { width: var(--space-4); height: var(--space-4); background: var(--scrollbar-bg); }
::-webkit-scrollbar-track { background: var(--scrollbar-bg); border-left: var(--border-ui); }
::-webkit-scrollbar-thumb { border: var(--size-hairline) solid var(--scrollbar-bg); background: var(--scrollbar-thumb); }
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
::placeholder { color: var(--text-faint); }
::selection { background: var(--selected-bg); color: var(--selected-text); }

/* ============================================================================
   console-ui / controls.css — the controls both tools share.
   Requires tokens.css + base.css.

   Naming: every class is prefixed `ui-`. Archive keeps `owner-*` for its own
   compositions; CMS keeps its page classes. Only the control itself is shared,
   never the layout around it.
   ========================================================================= */

/* --- Button --------------------------------------------------------------
   Three weights: default (bordered), primary (filled), quiet (text only).
   Three sizes map 1:1 onto the control-height tokens. Buttons carry no margin;
   the container that holds them owns the gap. */
.ui-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  min-width: var(--control-h);
  margin: 0;
  padding: 0 var(--space-4);
  border: var(--border-ui);
  border-radius: var(--radius-ui);
  background: var(--surface-raised);
  color: var(--text);
  font-size: var(--type-label);
  line-height: var(--leading-snug);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--motion), border-color var(--motion), color var(--motion);
}
.ui-btn:hover:not(:disabled):not([aria-disabled="true"]) { border-color: var(--line-strong); background: var(--surface); }
.ui-btn:active:not(:disabled) { background: var(--surface-subtle); }

.ui-btn--primary { border-color: var(--text); background: var(--text); color: var(--text-inverse); font-weight: var(--weight-bold); }
.ui-btn--primary:hover:not(:disabled) { background: var(--text); border-color: var(--text); opacity: var(--opacity-hover); }

.ui-btn--quiet { border-color: var(--colour-clear); background: none; color: var(--text-muted); padding: 0 var(--space-2); }
.ui-btn--quiet:hover:not(:disabled) { border-color: var(--colour-clear); background: var(--surface-subtle); color: var(--text); }

.ui-btn--danger { color: var(--error); }
.ui-btn--danger:hover:not(:disabled) { border-color: var(--error); background: var(--error-wash); }

.ui-btn--compact { min-height: var(--control-h-compact); font-size: var(--type-caption); padding: 0 var(--space-3); }
.ui-btn--touch   { min-height: var(--control-h-touch); padding: 0 var(--space-10); font-size: var(--type-body); }
/* Sparse phone actions need a full finger target; dense desktop rows retain 36px. */
@media (max-width: 640px) {
  .ui-btn--phone { min-height: calc(var(--control-h-touch) + var(--space-4)); }
}

/* Disabled reads as unavailable, not as faded decoration: the border stays so
   the control keeps its shape in a row of enabled siblings. */
.ui-btn:disabled,
.ui-btn[aria-disabled="true"] {
  color: var(--text-faint);
  background: var(--surface);
  border-color: var(--line);
  cursor: not-allowed;
}
.ui-btn--primary:disabled { background: var(--surface-subtle); border-color: var(--line); color: var(--text-faint); }

/* Pressed / on. Used by view toggles and the four front-end flags. */
.ui-btn[aria-pressed="true"],
.ui-btn.is-active {
  border-color: var(--selected-bg);
  background: var(--selected-bg);
  color: var(--selected-text);
}

/* A selected control stays legible throughout pointer activation. */
.ui-btn[aria-pressed="true"]:hover:not(:disabled):not([aria-disabled="true"]),
.ui-btn.is-active:hover:not(:disabled):not([aria-disabled="true"]),
.ui-btn[aria-pressed="true"]:active:not(:disabled),
.ui-btn.is-active:active:not(:disabled) {
  border-color: var(--selected-bg);
  background: var(--selected-bg);
  color: var(--selected-text);
}

.ui-btn-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* --- Field ---------------------------------------------------------------
   Label above, control below, help or error beneath. The label is always
   present in the DOM; a field with no visible label uses .ui-visually-hidden,
   never a placeholder standing in for one. */
.ui-field { display: grid; gap: var(--space-2); min-width: 0; }
.ui-field > .ui-label { margin: 0; }
.ui-field-row { display: grid; gap: var(--space-4); grid-auto-flow: column; grid-auto-columns: 1fr; align-items: end; }

.ui-input,
.ui-select,
.ui-textarea {
  width: var(--size-full);
  min-height: var(--control-h-touch);
  padding: 0 var(--space-3);
  border: var(--border-ui);
  border-radius: var(--radius-ui);
  background: var(--surface-raised);
  color: var(--text);
  font-size: var(--type-body);
  transition: border-color var(--motion), background-color var(--motion);
}
.ui-input:hover, .ui-select:hover, .ui-textarea:hover { border-color: var(--line-strong); }
.ui-input:focus, .ui-select:focus, .ui-textarea:focus { border-color: var(--text); }

/* Dense repeated rows only — a media row's twelve-column field grid. */
.ui-input--compact, .ui-select--compact { min-height: var(--control-h); font-size: var(--type-label); }

.ui-textarea {
  min-height: var(--size-textarea-min);
  padding: var(--space-4) var(--space-5);
  font-size: var(--type-subtitle);
  line-height: var(--leading-body);
  resize: vertical;
}

/* Unknown is not the same as empty. A value the archive has never learned is
   marked; a value deliberately left blank is not. */
.ui-input[data-unknown="true"] { border-style: dashed; color: var(--text-faint); }

.ui-input[aria-invalid="true"],
.ui-textarea[aria-invalid="true"] { border-color: var(--error); background: var(--error-wash); }
.ui-field-error { color: var(--error); font-size: var(--type-label); }
.ui-field-help  { color: var(--text-muted); font-size: var(--type-label); }

.ui-input:disabled, .ui-select:disabled, .ui-textarea:disabled {
  background: var(--surface);
  color: var(--text-faint);
  border-color: var(--line);
  cursor: not-allowed;
}

/* A changed field is marked on its label, not with a coloured bar down the
   control. The save bar is the one place that counts what is unsaved. */
.ui-field.is-dirty > .ui-label::after { content: " · changed"; color: var(--text-muted); letter-spacing: 0; }

/* --- Checkbox ------------------------------------------------------------ */
/* Native checkboxes also appear in legacy CMS compositions that predate the
   .ui-check wrapper. Keep their platform-drawn mark on the shared black token
   so an unwrapped control cannot fall back to browser blue. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--selected-bg);
}

.ui-check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  color: var(--text);
  font-size: var(--type-label);
  cursor: pointer;
}
.ui-check input {
  width: var(--size-check); height: var(--size-check);
  margin: 0;
  accent-color: var(--selected-bg);
  cursor: pointer;
}
.ui-check input:disabled { cursor: not-allowed; }
.ui-check:has(input:disabled) { color: var(--text-faint); cursor: not-allowed; }
.ui-check-group { display: flex; align-items: center; gap: var(--space-6); flex-wrap: wrap; }

/* --- Tag ----------------------------------------------------------------- */
.ui-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: var(--border-ui);
  border-radius: var(--radius-ui);
  background: var(--surface-raised);
  color: var(--text-muted);
  font-size: var(--type-caption);
  line-height: var(--leading-snug);
  white-space: nowrap;
}
.ui-tag--on { border-color: var(--selected-bg); background: var(--selected-bg); color: var(--selected-text); }
.ui-tag button { border: 0; background: none; padding: 0; color: inherit; cursor: pointer; line-height: var(--leading-control); }
.ui-tag-list { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* --- Status chip ---------------------------------------------------------
   Text plus a 1px rule in the state colour. No filled pills, no icons: at this
   size a filled badge is louder than the record it describes. */
.ui-status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  width: max-content;
  padding: var(--space-1) var(--space-2);
  border: var(--size-hairline) solid currentColor;
  border-radius: var(--radius-ui);
  font-size: var(--type-caption);
  line-height: var(--leading-snug);
  color: var(--text-muted);
}
.ui-status--ok      { color: var(--success); }
.ui-status--error   { color: var(--error); }
.ui-status--warning { border-color: var(--warning); background: var(--warning); color: var(--warning-ink); }
.ui-status--busy    { color: var(--text-muted); }
.ui-status--busy::before { content: "…"; }
/* Unavailable is its own state: the record exists, the file does not. */
.ui-status--unavailable { color: var(--text-faint); border-style: dashed; }

/* --- Filter bar ----------------------------------------------------------
   Common filters sit in the bar. Anything rarer goes behind "More filters" and
   is never permanently expanded into a wall of controls. */
.ui-filter-bar {
  display: flex;
  align-items: end;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-4) 0;
  border-bottom: var(--border-ui);
}
.ui-filter-bar .ui-field { flex: 0 1 var(--size-filter-field); }
.ui-filter-bar .ui-field--wide { flex: 1 1 var(--size-filter-field-wide); }
.ui-filter-bar .ui-filter-end { margin-left: auto; display: flex; align-items: end; gap: var(--space-2); }

/* --- Selection bar -------------------------------------------------------
   Appears only when something is selected, in the flow above the collection so
   it never covers a row the user is choosing from. */
.ui-selection-bar {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border: var(--border-ui-strong);
  border-radius: var(--radius-ui);
  background: var(--surface-raised);
  font-size: var(--type-label);
}
.ui-selection-count { font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; }

/* --- Table --------------------------------------------------------------- */
.ui-table { width: var(--size-full); border-collapse: collapse; font-size: var(--type-label); }
.ui-table th {
  position: sticky; top: 0; z-index: var(--z-sticky);
  padding: var(--space-2) var(--space-3);
  border-bottom: var(--border-ui-strong);
  background: var(--surface);
  color: var(--text-muted);
  font-weight: var(--weight-regular);
  letter-spacing: var(--track-label);
  text-align: left;
  text-transform: uppercase;
}
.ui-table td { padding: var(--space-3); border-bottom: var(--border-ui); vertical-align: top; }
.ui-table tbody tr:hover { background: var(--surface-subtle); }
.ui-table tbody tr.is-selected { background: var(--surface-subtle); box-shadow: var(--shadow-table-selected); }

/* --- Panel / card -------------------------------------------------------- */
.ui-panel {
  border: var(--border-ui);
  border-radius: var(--radius-ui);
  background: var(--surface-raised);
}
.ui-panel-head {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-ui);
}
.ui-panel-head h2, .ui-panel-head h3 { margin-right: auto; }
.ui-panel-body { display: grid; gap: var(--space-4); padding: var(--space-4); }

/* --- Disclosure ---------------------------------------------------------- */
.ui-disclosure > summary {
  display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--control-h);
  color: var(--text-muted);
  font-size: var(--type-label);
  cursor: pointer;
  list-style: none;
}
.ui-disclosure > summary::-webkit-details-marker { display: none; }
.ui-disclosure > summary::before { content: "+"; width: var(--size-character); }
.ui-disclosure[open] > summary::before { content: "\2212"; }
.ui-disclosure[open] > summary { color: var(--text); }

/* --- Dialog --------------------------------------------------------------
   One dialog element at two widths. Wide is for work detail and the asset
   chooser; narrow for a single question. */
.ui-dialog {
  width: min(var(--dialog-w-narrow), calc(var(--viewport-w) - var(--space-16)));
  max-height: calc(var(--viewport-h) - var(--space-16));
  padding: 0;
  border: var(--border-ui-strong);
  border-radius: var(--radius-ui);
  background: var(--surface-raised);
  color: var(--text);
  overflow: auto;
}
.ui-dialog--wide { width: min(var(--dialog-w-wide), calc(var(--viewport-w) - var(--space-16))); }
.ui-dialog::backdrop { background: var(--scrim); }
.ui-dialog-head {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-4);
  border-bottom: var(--border-ui);
  background: var(--surface-raised);
}
.ui-dialog-head h2 { margin-right: auto; font-size: var(--type-heading); letter-spacing: var(--track-tight); }
.ui-dialog-body { padding: var(--space-4); }

/* --- Media ---------------------------------------------------------------
   The rule the archive turns on: a still is shown at its own shape. Tiles size
   themselves from the asset's aspect ratio; full views letterbox against black
   rather than cropping to remove the bars. */
.ui-media {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--media-bg);
  color: var(--media-text);
}
.ui-media img, .ui-media video { width: var(--size-full); height: var(--size-full); object-fit: contain; }
.ui-media--true { aspect-ratio: var(--ar, var(--ratio-preview-fallback)); }
.ui-media--full { aspect-ratio: auto; max-height: var(--size-media-dialog-h); }
.ui-media--thumb { width: var(--size-thumb); height: var(--size-thumb); flex: 0 0 auto; }
/* Nothing to show is a labelled well, not a black rectangle. */
.ui-media--empty {
  background: var(--surface-subtle);
  color: var(--text-muted);
  font-size: var(--type-caption);
  text-align: center;
}

/* --- Save bar ------------------------------------------------------------
   Fixed, always visible while a form is open, and it says what saving does.
   Uploading a file is not saving; saving is not deploying. */
.ui-save-bar {
  position: fixed; inset: auto 0 0 0; z-index: var(--z-savebar);
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-10);
  border-top: var(--border-ui-strong);
  background: var(--surface-raised);
  font-size: var(--type-label);
}
.ui-save-bar .ui-save-note { color: var(--text-muted); }
.ui-save-bar[data-state="dirty"]  { border-top-color: var(--text); }
.ui-save-bar[data-state="dirty"] .ui-save-note { color: var(--text); }
.ui-save-bar[data-state="saved"]  .ui-save-note { color: var(--success); }
.ui-save-bar[data-state="failed"] .ui-save-note { color: var(--error); }

/* --- Empty state --------------------------------------------------------- */
.ui-empty {
  padding: var(--space-24) var(--space-10);
  border: var(--size-hairline) dashed var(--line);
  border-radius: var(--radius-ui);
  color: var(--text-muted);
  text-align: center;
}
