/* ─────────────────────────────────────────────────────────────────────────────
   THE SHARED ADMIN COMPONENT LAYER.

   WHY THIS FILE EXISTS. Screens were hand-styled one at a time, so the platform
   had as many designs as it had pages: raw tables next to premium cards, inline
   `style=` attributes carrying layout decisions, and the same idea — a summary
   figure, a status, an empty state — re-invented in every view with slightly
   different spacing. The owner's words for the result were "HTML4/classic
   forms, raw tables, developer diagnostics".

   The rule this encodes: A SCREEN COMPOSES COMPONENTS. It does not style
   itself. Anything a second page might need lives here as a class, so it is
   fixed once for every page rather than once per page.

   IT DEFINES NO COLOURS OF ITS OWN. Every value below resolves to a token
   already declared in admin.css (--bg2, --text, --accent, --radius, --shadow…),
   so the existing themes — light, dark, blue — and the design packs keep
   working, and a theme change reaches these components for free. Adding a
   second palette here is what would fork the design system, which is the exact
   thing this file exists to prevent.

   LOGICAL PROPERTIES THROUGHOUT (padding-inline, margin-inline-start, inset-
   inline) so Arabic and Kurdish mirror correctly without a single RTL override.
   ───────────────────────────────────────────────────────────────────────────── */

/* ── Page header ─────────────────────────────────────────────────────────── */

.ui-head {
  display: flex; flex-wrap: wrap; gap: 1rem;
  align-items: flex-start; justify-content: space-between;
  margin-bottom: 1.25rem;
}
.ui-head-title { display: flex; align-items: center; gap: .625rem; }
.ui-head-title h1 { font-size: 1.375rem; font-weight: 700; line-height: 1.25; }
.ui-head-icon { font-size: 1.5rem; line-height: 1; }
.ui-head-sub { font-size: .8125rem; color: var(--text3); margin-top: .25rem; max-width: 68ch; line-height: 1.5; }
.ui-head-actions { display: flex; flex-wrap: wrap; gap: .5rem; }

/* ── Section: a titled band of related panels ────────────────────────────── */

.ui-section { margin-bottom: 1.5rem; }
.ui-section-head {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  margin-bottom: .625rem; padding-inline-start: .125rem;
}
/* The headline keeps title and note together so an action can never land
   between them when the row wraps on a phone. */
.ui-section-headline { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; min-inline-size: 0; flex: 1; }
.ui-section-actions { display: flex; align-items: center; gap: .4rem; flex: 0 0 auto; }
.ui-section-head h2 { font-size: .8125rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text2); }
.ui-section-head .ui-section-note { font-size: .75rem; color: var(--text3); }

/* ── A COLLAPSIBLE SECTION ────────────────────────────────────────────────
   The summary IS the section heading, so it must look like one and behave
   like a control: a 44px row, a clear affordance, and a visible focus ring
   for the keyboard. The marker is replaced by our own chevron so it reads
   the same in RTL, where a native triangle points the wrong way. */
.ui-section--collapsible > .ui-section-summary {
  cursor: pointer;
  list-style: none;
  min-block-size: 44px;
  align-items: center;
  border-radius: var(--radius);
  padding: .5rem .625rem;
  margin-inline-start: 0;
  transition: background .15s ease;
}
.ui-section--collapsible > .ui-section-summary::-webkit-details-marker { display: none; }
.ui-section--collapsible > .ui-section-summary:hover { background: var(--bg3); }
.ui-section--collapsible > .ui-section-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* The count keeps the section informative while it is SHUT — "how many are
   in there" is the question a closed list has to keep answering. */
.ui-section-count {
  flex: 0 0 auto;
  font-size: .75rem; font-weight: 700;
  color: var(--text2); background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 999px; padding: .1rem .5rem;
  font-variant-numeric: tabular-nums;
}
.ui-section-chev {
  flex: 0 0 auto;
  color: var(--text3); font-size: .8rem; line-height: 1;
  transition: transform .18s ease;
}
.ui-section--collapsible[open] > .ui-section-summary .ui-section-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .ui-section--collapsible > .ui-section-summary,
  .ui-section-chev { transition: none; }
}

/* ── ESTATE SECTIONS ──────────────────────────────────────────────────────
   A titled band with a COUNT and a sentence of meaning, used where a list
   holds several different KINDS of thing and reading one as another is the
   expensive mistake — the tenant estate being the case it was built for.
   Logical properties throughout, so Arabic and Kurdish need no mirrored copy. */
.ui-estate-section { margin-block-end: 2.25rem; }
.ui-estate-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem .75rem;
  margin-block-end: .9rem; padding-block-end: .5rem;
  border-block-end: 1px solid var(--line);
}
/* The estate head is a summary now, so it is also a control: it needs the
   44px row, the affordance and the focus ring, and the native marker has to
   go because its triangle points the wrong way in RTL. It reuses
   .ui-section-count and .ui-section-chev rather than growing a second pair,
   so a group in the tenant estate and a collapsible section elsewhere read
   as the same mechanism. */
details.ui-estate-section > .ui-estate-head {
  cursor: pointer;
  list-style: none;
  min-block-size: 44px;
  align-content: center;
  transition: background .15s ease;
}
details.ui-estate-section > .ui-estate-head::-webkit-details-marker { display: none; }
details.ui-estate-section > .ui-estate-head:hover { background: var(--bg3); }
details.ui-estate-section > .ui-estate-head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
details.ui-estate-section[open] > .ui-estate-head .ui-section-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  details.ui-estate-section > .ui-estate-head { transition: none; }
}
.ui-estate-title {
  margin: 0; font-size: .8125rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text2);
}
.ui-estate-sub { flex-basis: 100%; margin: 0; font-size: .75rem; color: var(--text3); }

/* The capital is chrome, not one of the cards beside it. */
.ui-entity.is-capital { border-inline-start: 3px solid var(--info); }
.ui-entity.is-capital .ui-entity-foot { align-items: center; gap: .75rem; flex-wrap: wrap; }

/* ── FACT LIST ────────────────────────────────────────────────────────────
   Values the ENVIRONMENT decides, stated before the things an operator can
   change, so a setting is never mistaken for a mystery. */
.ui-facts { display: grid; gap: 1rem; margin: 0; }
.ui-facts dt { font-weight: 600; font-size: .8125rem; color: var(--text2); }
.ui-facts dd { margin-inline-start: 0; }

/* ── TYPED-TO-CONFIRM RELEASE ─────────────────────────────────────────────
   A click is not a decision. Used wherever removing something has to be
   deliberate, and paired with a refusal line where it is not allowed at all. */
.ui-locked { margin-block: .5rem 0; font-size: .8125rem; color: var(--text3); }
.ui-release { margin-block-start: .5rem; }
.ui-release summary { cursor: pointer; font-size: .8125rem; }
.ui-release form { display: grid; gap: .45rem; margin-block-start: .5rem; max-inline-size: 22rem; }
.ui-row-act { margin-block-start: .4rem; }

/* ── BRAND PREVIEW ────────────────────────────────────────────────────────
   A mark, a name and a call to action, shown as a visitor meets them, so a
   colour is judged by how it looks rather than by the hex somebody typed.
   The two colours arrive as custom properties set on the element. */
.ui-brand-preview-bar { display: flex; align-items: center; gap: .85rem; flex-wrap: wrap; }
.ui-brand-mark {
  inline-size: 44px; block-size: 44px; border-radius: var(--radius-lg); flex: none;
  display: grid; place-items: center; font-weight: 700; font-size: 1.2rem;
  color: var(--on-brand); background: var(--brand-primary);
}
.ui-brand-names { display: grid; gap: .1rem; min-inline-size: 0; }
.ui-brand-names strong { font-size: 1.1rem; }
.ui-brand-names small { color: var(--text3); }
.ui-brand-chip {
  margin-inline-start: auto; padding: .45rem .9rem; border-radius: 999px;
  background: var(--brand-accent); color: var(--on-brand); font-weight: 600; font-size: .875rem;
}

/* ── FIELD GRID ───────────────────────────────────────────────────────────
   A section's inputs, each labelled by what the value IS — a colour gets a
   swatch beside its hex, an uploaded mark gets a preview. */
.ui-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem 1.25rem; }
.ui-fields .ui-field--wide { grid-column: 1 / -1; }
.ui-swatch { display: flex; gap: .5rem; align-items: center; }
.ui-swatch input[type="color"] { inline-size: 48px; block-size: 40px; padding: 2px; flex: none; }
.ui-asset { display: flex; gap: .75rem; align-items: center; }
.ui-asset img { block-size: 40px; inline-size: auto; max-inline-size: 140px; border-radius: 8px; flex: none; }
.ui-asset input { min-inline-size: 0; }

/* ── STICKY SAVE ──────────────────────────────────────────────────────────
   Reachable on a phone without covering the last field, and clear of the home
   indicator. 44px targets, per the operator ethos. */
.ui-save-bar {
  position: sticky; inset-block-end: 0; z-index: 5;
  display: flex; justify-content: flex-end; gap: .5rem;
  margin-block-start: 1.5rem; padding-block: .85rem;
  padding-block-end: calc(.85rem + env(safe-area-inset-bottom));
  background: var(--bg1); border-block-start: 1px solid var(--line);
}
.ui-save-bar .btn { min-block-size: 44px; }
@media (max-width: 560px) {
  .ui-brand-chip { margin-inline-start: 0; }
  .ui-save-bar { justify-content: stretch; }
  .ui-save-bar .btn { flex: 1; }
}

/* ── Grid: the one layout primitive. `--min` sets the breakpoint. ─────────── */

.ui-grid { display: grid; gap: .875rem; grid-template-columns: repeat(auto-fit, minmax(var(--min, 240px), 1fr)); }
.ui-grid-tight { --min: 160px; }
.ui-grid-wide  { --min: 320px; }

/* ── KPI: one number that matters, and where it goes ─────────────────────── */

/* The KPI row. Eleven admin screens (dashboard, analytics, demo control,
   payment attempts, billing, tenancy terms, platform estate, platform
   finance …) already asked for this class and got NOTHING — the cards fell
   into a single column on desktop because the grid rule was never written.
   Declared once here rather than fixed per view. */
.ui-kpis { display: grid; gap: .875rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); margin-bottom: 1.5rem; }
/* KPI CARDS PAIR UP ON A PHONE rather than stacking one per row — four short
   cards stacked is four screens of scrolling to read four numbers, and the
   numbers are what somebody came for. Same rule as .ui-grid-tight below. */
@media (max-width: 560px) { .ui-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .625rem; } }

.ui-kpi {
  display: flex; flex-direction: column; gap: .375rem;
  background: var(--bg2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: .875rem 1rem;
  box-shadow: var(--shadow); min-height: 92px;
  position: relative; overflow: hidden;
}
/* A KPI that links is a button-sized target and says so on hover. */
a.ui-kpi { color: inherit; transition: box-shadow .15s, transform .15s, border-color .15s; }
a.ui-kpi:hover { box-shadow: var(--shadow-md); border-color: var(--accent); opacity: 1; }
a.ui-kpi::after {
  content: '→'; position: absolute; inset-inline-end: .75rem; top: .75rem;
  font-size: .875rem; color: var(--text3); opacity: 0; transition: opacity .15s;
}
a.ui-kpi:hover::after { opacity: 1; }
[dir="rtl"] a.ui-kpi::after { content: '←'; }

.ui-kpi-label { font-size: .75rem; color: var(--text3); display: flex; align-items: center; gap: .375rem; }
.ui-kpi-value { font-size: 1.75rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.ui-kpi-value.is-muted { font-size: 1rem; font-weight: 600; color: var(--text3); }
/* THE FOOT IS A FOOTNOTE, NOT THE CARD.
   On Platform finance each card carried three lines of explanation under a
   two-word value, so at 390px the reader met a paragraph where they came for a
   number — "Contracted monthly / Not measured / The sum of every live
   contract's monthly figure, per currency." The full sentence stays available
   (it is the element's title, and it is unclamped from tablet up); on a phone
   it is capped so the value stays the loudest thing in the card. */
.ui-kpi-foot { font-size: .75rem; color: var(--text2); line-height: 1.4; }
@media (max-width: 560px) {
  /* Three lines, not two. At two, the background-work card on the platform
     dashboard read "31 waiting · 0 failed · oldest…" — and the age is the
     fact that decides whether a queue is healthy or stuck, so the clamp was
     cutting off the only part that mattered. Three still leaves the value
     the loudest thing in the card. */
  .ui-kpi-foot {
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
    overflow: hidden;
  }
}

/* Tone: a thin edge, never a coloured tile — a wall of colour reads as alarm. */
.ui-kpi.tone-good     { border-inline-start: 3px solid var(--success); }
.ui-kpi.tone-warning  { border-inline-start: 3px solid var(--warning); }
.ui-kpi.tone-critical { border-inline-start: 3px solid var(--danger); }
.ui-kpi.tone-quiet    { border-inline-start: 3px solid var(--border); }

/* ── Panel: a titled card holding a list, table or chart ─────────────────── */

.ui-panel {
  background: var(--bg2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow); overflow: hidden;
}
.ui-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .75rem 1rem; border-bottom: 1px solid var(--border);
}
.ui-panel-head h3 { font-size: .9375rem; font-weight: 650; display: flex; align-items: center; gap: .5rem; }
/* THE DRILL-DOWN IN A CARD HEADER IS A REAL CONTROL.
   It rendered as bare 12px text with no padding — a 26px target on the
   dashboard, under the 44px floor, and nothing about it said it could be
   tapped. The tap area is grown with padding and pulled back with a matching
   negative margin, so the target is comfortable while the header's spacing is
   unchanged; the chevron says it goes somewhere. */
.ui-panel-head a {
  /* The panel's "Open" link is accent-coloured text on the panel surface, so
     it reads through the ink token like every other accent-as-text role. */
  color: var(--accent-on-surface, var(--accent));
  font-size: .75rem; font-weight: 600;
  display: inline-flex; align-items: center; gap: .25rem;
  min-height: 44px; padding-inline: .5rem;
  margin-block: -.6rem; margin-inline-end: -.5rem;
  border-radius: var(--radius);
}
.ui-panel-head a::after { content: '›'; font-size: 1rem; line-height: 1; }
.ui-panel-head a:hover { background: var(--bg3); }
.ui-panel-head a:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ui-panel-body { padding: .875rem 1rem; }
.ui-panel-body.is-flush { padding: 0; }

/* ── Metric rows: label / value pairs inside a panel ─────────────────────── */

.ui-metrics { display: flex; flex-direction: column; }
.ui-metric {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .5rem 0; border-bottom: 1px solid var(--border); font-size: .875rem;
  min-height: 44px;
}
.ui-metric:last-child { border-bottom: 0; }
.ui-metric-label { color: var(--text2); display: flex; align-items: center; gap: .5rem; }
.ui-metric-value { font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ui-metric-value.is-unknown { font-weight: 400; color: var(--text3); font-style: italic; }
a.ui-metric { color: inherit; }
a.ui-metric:hover { color: var(--accent); opacity: 1; }

/* ── Status dot + status text ────────────────────────────────────────────── */

.ui-dot { width: .5rem; height: .5rem; border-radius: 999px; flex: 0 0 auto; background: var(--text3); }
.ui-dot.is-good     { background: var(--success); }
.ui-dot.is-warning  { background: var(--warning); }
.ui-dot.is-critical { background: var(--danger); }
.ui-dot.is-unknown  { background: var(--text3); }

/* ── Distribution bar: proportions without a charting library ────────────── */

.ui-bar { display: flex; height: .5rem; border-radius: 999px; overflow: hidden; background: var(--bg3); }
.ui-bar > span { display: block; height: 100%; }
.ui-bar-key { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: .625rem; font-size: .75rem; color: var(--text2); }
.ui-bar-key > span { display: flex; align-items: center; gap: .375rem; }
.ui-bar-swatch { width: .625rem; height: .625rem; border-radius: 3px; flex: 0 0 auto; }

/* ── Attention list: only genuine, actionable items ──────────────────────── */

.ui-attention { display: flex; flex-direction: column; gap: .5rem; }
.ui-attention-item {
  display: flex; align-items: flex-start; gap: .625rem;
  padding: .625rem .75rem; border-radius: var(--radius);
  background: var(--bg3); font-size: .875rem; line-height: 1.45; min-height: 44px;
}
.ui-attention-item .ui-dot { margin-top: .45rem; }
.ui-attention-item.is-critical { background: color-mix(in srgb, var(--danger) 8%, var(--bg2)); }
.ui-attention-item.is-warning  { background: color-mix(in srgb, var(--warning) 10%, var(--bg2)); }

/* ── Empty state: says why it is empty, never just blank ─────────────────── */

.ui-empty { text-align: center; padding: 1.75rem 1rem; color: var(--text3); }
.ui-empty-icon { font-size: 1.75rem; display: block; margin-bottom: .5rem; opacity: .8; }
.ui-empty-title { font-size: .9375rem; font-weight: 600; color: var(--text2); margin-bottom: .25rem; }
.ui-empty-body { font-size: .8125rem; max-width: 46ch; margin: 0 auto; line-height: 1.5; }

/* ── Table wrapper: wide content scrolls itself, never the page ──────────── */

.ui-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* On paper there is nothing to scroll, and a scroll container can clip the
   overflowing part right off the sheet. Several print pages carry wide tables
   and are still ordinary browser pages a person may look at on a phone, so
   they get the wrapper AND this. */
@media print { .ui-table-wrap { overflow: visible; } }

/* ── Phones ──────────────────────────────────────────────────────────────── */

@media (max-width: 480px) {
  .ui-head-title h1 { font-size: 1.125rem; }
  .ui-kpi { min-height: 78px; padding: .75rem .875rem; }
  .ui-kpi-value { font-size: 1.375rem; }
  .ui-grid { --min: 140px; gap: .625rem; }
  .ui-head-actions { width: 100%; }
  .ui-head-actions .btn { flex: 1 1 auto; min-height: 44px; }
}

/* ── Filter bar: one shape for every list screen ─────────────────────────── */

.ui-filters {
  display: flex; flex-wrap: wrap; gap: .625rem; align-items: flex-end;
  background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: .75rem .875rem; margin-bottom: 1rem; box-shadow: var(--shadow);
}
.ui-filters .form-control, .ui-filters .form-select { min-height: 44px; }
.ui-filters .ui-f { flex: 1 1 160px; min-width: 0; }
.ui-filters .ui-f-wide { flex: 2 1 240px; }

/* THE <label class="field"> SHAPE. Two screens (ReVeRa submissions, tenant
   requests) wrote their filters as a label wrapping a caption and a bare
   control. Unstyled, the caption sat BESIDE an unstyled input and the owner saw
   a squeezed box on his phone (Oct 2026). Caption above, control full width,
   the same height and border as every other filter. */
.ui-filters label.field { display: flex; flex-direction: column; gap: .3rem; flex: 1 1 200px; min-width: 0; margin: 0; }
.ui-filters label.field > span { font-size: .8125rem; font-weight: 600; color: var(--text2); }
.ui-filters label.field input, .ui-filters label.field select {
  inline-size: 100%; min-width: 0; min-height: 44px; padding: .5rem .875rem; background: var(--bg2); color: var(--text);
  border: 1.5px solid var(--border); border-radius: 10px; font-size: .9375rem; outline: none;
}
.ui-filters label.field input:focus, .ui-filters label.field select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(59,130,246,.15); }
.ui-filters > .btn { min-height: 44px; }

/* A TICK BOX IS A FILTER TOO, and until Sep 2026 the design system had no
   opinion about one — so screens hand-rolled it with inline styles and a 15px
   target nobody could hit on a phone. Laid out like the other fields, aligned
   to the control rather than the label, and 44px tall so a finger lands on it. */
.ui-filters .ui-f-check {
  flex: 0 1 auto;
  display: flex;
  align-items: center;
  gap: .5rem;
  min-block-size: 44px;
  padding-inline: .25rem;
  font-size: .9rem;
  color: var(--text2);
  cursor: pointer;
}
.ui-filters .ui-f-check input[type="checkbox"] {
  inline-size: 18px;
  block-size: 18px;
  accent-color: var(--accent);
  flex: none;
}
.ui-filters .ui-f-actions { display: flex; gap: .5rem; flex: 0 0 auto; }
.ui-filters .ui-f-count { margin-inline-start: auto; align-self: center; font-size: .8125rem; color: var(--text3); white-space: nowrap; }

/* ── Badge: a status, never a colour tile ────────────────────────────────── */

.ui-badge {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .15rem .55rem; border-radius: 999px;
  font-size: .75rem; font-weight: 650; line-height: 1.4; white-space: nowrap;
  background: var(--bg3); color: var(--text2);
}
.ui-badge.is-good     { background: color-mix(in srgb, var(--success) 14%, var(--bg2)); color: var(--success-on-surface, var(--success)); }
.ui-badge.is-warning  { background: color-mix(in srgb, var(--warning) 16%, var(--bg2)); color: var(--warning-on-surface, var(--warning)); }
.ui-badge.is-critical { background: color-mix(in srgb, var(--danger) 12%, var(--bg2)); color: var(--danger); }
.ui-badge.is-accent   { background: color-mix(in srgb, var(--accent) 14%, var(--bg2)); color: var(--accent-on-surface, var(--accent)); }
.ui-badge.is-unknown  { background: var(--bg3); color: var(--text3); }

/* ── Entity card: one business / one record, with a footer of actions ────── */

.ui-entity { display: flex; flex-direction: column; overflow: hidden; }
.ui-entity-main { display: block; color: inherit; flex: 1; padding: 1rem; transition: background .15s; }
.ui-entity-main:hover { background: var(--bg3); opacity: 1; }
.ui-entity-row { display: flex; align-items: center; gap: .75rem; }
.ui-entity-mark {
  width: 48px; height: 48px; border-radius: var(--radius-lg); flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.25rem; font-weight: 800; color: var(--on-brand); background-size: cover; background-position: center;
}
/* The title WRAPS, at every width. It used to be one nowrap line with
   overflow:hidden + ellipsis, so on a desktop card the business name ate the
   row and the classification badge beside it was cut to "Se" / "Sector de"
   (owner's screenshot, Oct 2026). A badge is a whole word or it is noise:
   the name wraps between words, and the badges drop onto the next line
   whole when there is no room beside it. */
.ui-entity-title { font-weight: 700; display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .375rem; min-width: 0;
  white-space: normal; overflow: visible; text-overflow: clip; }
.ui-entity-title > span:first-child { overflow-wrap: break-word; min-width: 0; }
.ui-entity-title > .ui-badge, .ui-entity-row > .ui-badge { flex: 0 0 auto; }
.ui-entity-sub { font-size: .75rem; color: var(--text3); font-variant-numeric: tabular-nums; }
.ui-entity-body { min-width: 0; flex: 1; }
.ui-entity-stats { display: flex; justify-content: space-between; gap: .5rem; font-size: .75rem; color: var(--text3); border-top: 1px solid var(--border); padding-top: .625rem; margin-top: .75rem; }
.ui-entity-foot { display: flex; flex-direction: column; border-top: 1px solid var(--border); }
.ui-entity-foot > * { border: 0; border-radius: 0; min-height: 44px; }
.ui-entity-foot > * + * { border-top: 1px solid var(--border); }
.ui-entity-foot form { margin: 0; display: contents; }

/* ── Details: a disclosure that does not look like a browser default ─────── */

.ui-details { margin-top: .375rem; }
.ui-details > summary {
  cursor: pointer; font-size: .8125rem; color: var(--accent-on-surface, var(--accent)); list-style: none;
  display: inline-flex; align-items: center; gap: .375rem; min-height: 32px;
}
.ui-details > summary::-webkit-details-marker { display: none; }
.ui-details > summary::before { content: '›'; display: inline-block; transition: transform .15s; }
.ui-details[open] > summary::before { transform: rotate(90deg); }
[dir="rtl"] .ui-details > summary::before { content: '‹'; }
[dir="rtl"] .ui-details[open] > summary::before { transform: rotate(-90deg); }
.ui-details-body { font-size: .8125rem; color: var(--text2); margin-top: .375rem; padding-inline-start: 1rem; line-height: 1.6; }
.ui-details-body dl { display: grid; grid-template-columns: max-content 1fr; gap: .125rem .75rem; margin: 0; }
.ui-details-body dt { color: var(--text3); }
.ui-details-body dd { margin: 0; overflow-wrap: anywhere; }

/* ── Alert band: one important sentence, toned ───────────────────────────── */

.ui-alert { display: flex; gap: .75rem; align-items: flex-start; padding: .875rem 1rem; border-radius: var(--radius-lg); border: 1px solid var(--border); background: var(--bg2); margin-bottom: 1rem; box-shadow: var(--shadow); }
.ui-alert.is-critical { border-inline-start: 4px solid var(--danger); }
.ui-alert.is-warning  { border-inline-start: 4px solid var(--warning); }
.ui-alert.is-good     { border-inline-start: 4px solid var(--success); }
.ui-alert.is-info     { border-inline-start: 4px solid var(--accent); }
.ui-alert-title { font-weight: 700; margin-bottom: .25rem; }
.ui-alert-body { font-size: .875rem; color: var(--text2); line-height: 1.5; }

/* ── Danger zone: destructive actions live in one clearly bounded place ──── */

.ui-danger { border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--border)); border-radius: var(--radius-lg); background: var(--bg2); padding: 1rem; margin-bottom: 1rem; }
.ui-danger-title { font-weight: 700; color: var(--danger); display: flex; align-items: center; gap: .5rem; margin-bottom: .25rem; }
.ui-danger-body { font-size: .875rem; color: var(--text2); max-width: 70ch; line-height: 1.5; margin-bottom: .75rem; }

/* ── Muted helper text, monospace values ─────────────────────────────────── */

.ui-muted { color: var(--text3); font-size: .8125rem; }
.ui-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .875rem; }
.ui-nowrap { white-space: nowrap; }

/* ── Module price beside its availability (x-ui.module-price) ────────────
   A small muted label, the figure, and the period — one phrase that may wrap
   as a whole but never breaks inside the amount (bdi + nowrap), never clips,
   and never widens the tile or cell it sits in. 12px floor: .8125rem. */
.ui-mprice { display: inline-flex; flex-wrap: wrap; align-items: baseline; justify-content: center; gap: 0 .3em;
             max-inline-size: 100%; min-inline-size: 0; font-size: .8125rem; line-height: 1.35; color: var(--text3); }
.ui-mprice-lbl { font-weight: 600; }
.ui-mprice-val { font-weight: 700; color: var(--text); white-space: nowrap; font-variant-numeric: tabular-nums; }
.ui-mprice-per { white-space: nowrap; }
/* A state word with its price under it, as one item in a table cell (the
   phone layout makes a cell a flex row of label + value; two loose items
   there squeezed the price to 12px). */
.ui-comm { display: inline-flex; flex-direction: column; align-items: flex-start; gap: .25rem; min-inline-size: 0; max-inline-size: 100%; }
.ui-comm .ui-mprice { justify-content: flex-start; }

/* ── Forms: a field is a label above its control, in a responsive grid ───── */

.ui-form-grid { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fit, minmax(var(--min, 220px), 1fr)); }
.ui-field { display: flex; flex-direction: column; gap: .3rem; min-width: 0; }
.ui-field > span, .ui-field > label { font-size: .8125rem; font-weight: 600; color: var(--text2); }
.ui-field .form-control, .ui-field .form-select, .ui-field input, .ui-field select, .ui-field textarea {
  min-height: 44px; width: 100%; padding: .5rem .75rem; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg2); color: var(--text); font: inherit;
}
.ui-field input:focus, .ui-field select:focus, .ui-field textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
.ui-form-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .875rem; align-items: center; }
.ui-inline-form { display: flex; gap: .4rem; flex-wrap: wrap; align-items: center; }
.ui-inline-form select, .ui-inline-form input { min-height: 40px; padding: .35rem .6rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); color: var(--text); font: inherit; }

/* ── Split: a list beside a reading pane ─────────────────────────────────── */

.ui-split { display: grid; grid-template-columns: minmax(240px, 320px) 1fr; gap: 1rem; align-items: start; }
@media (max-width: 860px) { .ui-split { grid-template-columns: 1fr; } }
.ui-list { display: flex; flex-direction: column; gap: .25rem; padding: .5rem; }
.ui-list-item { display: block; padding: .625rem .75rem; border-radius: var(--radius); color: inherit; min-height: 44px; }
.ui-list-item:hover { background: var(--bg3); opacity: 1; }
.ui-list-item.is-active { background: color-mix(in srgb, var(--accent) 14%, var(--bg2)); }
.ui-list-title { font-weight: 600; font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-list-meta { font-size: .75rem; color: var(--text3); display: flex; gap: .375rem; align-items: center; flex-wrap: wrap; }

.ui-thread { max-height: 60vh; overflow-y: auto; }
.ui-thread-head { border-bottom: 1px solid var(--border); padding-bottom: .75rem; margin-bottom: 1rem; }
.ui-thread-title { font-weight: 700; }
.ui-msg { padding: .625rem 0; border-bottom: 1px solid var(--border); }
.ui-msg-head { display: flex; justify-content: space-between; gap: .75rem; font-size: .8125rem; }
.ui-msg-head strong { font-weight: 650; }
.ui-msg-head time { color: var(--text3); white-space: nowrap; }
.ui-msg-body { white-space: pre-wrap; font-size: .9rem; line-height: 1.5; overflow-wrap: anywhere; }
.ui-pane { min-height: 520px; }
.ui-center { justify-content: center; }

/* ── legend line above a classified table ───────────────────────────── */
.ui-legend { display: flex; flex-wrap: wrap; gap: .35rem .5rem; align-items: center; padding: .6rem .9rem; font-size: .8rem; color: var(--text3); border-bottom: 1px solid var(--border); }
.ui-entity-sector { display: flex; flex-wrap: wrap; gap: .35rem; align-items: center; margin: .4rem 0 .1rem; font-size: .82rem; }
.btn.is-disabled { cursor: not-allowed; opacity: .6; }
.ui-pager { margin-top: 1rem; }

/* THE `hidden` ATTRIBUTE HAS TO WIN.

   `hidden` is a plain attribute with a display:none rule of the lowest possible
   specificity, so ANY class that sets display beats it. The notifications bulk
   bar is written `class="ui-filters" hidden` — correctly — and `.ui-filters`
   sets display:flex, so it showed on every page load: "0 selected", "Select all
   5", and six buttons, shouting before anybody had chosen anything. Two more
   elements in the estate carry the same combination.

   One rule, once, rather than remembering it at every call site. */
[hidden] { display: none !important; }

/* ── Notifications: the ring, the row, the actions — mobile first ──────── */
.nt-list { display: flex; flex-direction: column; }
.nt-item { display: flex; gap: .7rem; align-items: flex-start; padding: .9rem .9rem; border-bottom: 1px solid var(--border); min-width: 0; }
/* UNREAD IS NOT AN ERROR. This tinted every unread row with the DANGER colour,
   so an inbox of ordinary news read as a screen of problems and the rows that
   were genuinely critical had nothing left to distinguish them. Unread is
   attention, which is what the accent is for; severity is carried by the dot,
   from the notification's own meaning. */
.nt-item.unread { background: color-mix(in srgb, var(--accent) 4%, transparent); }
.nt-pickcell { flex: none; align-self: flex-start; }
.nt-check { display: inline-flex; align-items: center; gap: .5rem; min-height: 44px; font-weight: 700; cursor: pointer; }
.nt-check input[type=checkbox] { width: 20px; height: 20px; flex: none; }
.nt-pref { width: 100%; border-bottom: 1px solid var(--border); padding: .35rem 0; font-weight: 500; }
.nt-prefs { display: flex; flex-direction: column; gap: .35rem; }
.nt-ring { flex: none; width: 48px; height: 48px; border-radius: 50%; padding: 3px; background: var(--border); position: relative; }
.nt-ring .nt-ic { width: 100%; height: 100%; border-radius: 50%; background: var(--bg2); display: flex; align-items: center; justify-content: center; font-size: 1.25rem; }
/* THE RING WAS A SPINNING RAINBOW. A conic gradient of danger, warning and
   success turning once every 3.2 seconds, on EVERY unread row — decoration
   wearing the colours that are supposed to carry meaning, so the palette said
   nothing and the page never settled. It honoured prefers-reduced-motion, which
   made it legal rather than good. A solid ring in the notification's own tone
   says the same thing and stays still. */
.nt-item.unread .nt-ring { background: var(--accent); }
.nt-item.unread.is-critical .nt-ring { background: var(--danger); }
.nt-item.unread.is-warning .nt-ring { background: var(--warning); }
.nt-item.unread.is-good .nt-ring { background: var(--success); }
.nt-ring .nt-dot { position: absolute; top: -2px; inset-inline-end: -2px; width: 12px; height: 12px; border-radius: 50%; background: var(--accent); border: 2.5px solid var(--bg2); display: none; }
.nt-item.unread .nt-dot { display: block; }
.nt-item.unread.is-critical .nt-dot { background: var(--danger); }
.nt-item.unread.is-warning .nt-dot { background: var(--warning); }
.nt-item.unread.is-good .nt-dot { background: var(--success); }
.nt-item.archived .nt-ring { background: transparent; border: 2px dashed var(--border); padding: 1px; }

.nt-bulk-actions { display: inline-flex; flex-wrap: wrap; gap: .4rem; align-items: center; }

/* A DAY HEADING, so a list of forty is four short lists. */
.nt-day { padding: .7rem .9rem .35rem; font-size: 0.75rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--text3); background: var(--bg); position: sticky; top: 0; z-index: 1; }
.nt-main { flex: 1 1 auto; min-width: 0; }
.nt-head { display: flex; gap: .4rem .5rem; align-items: baseline; flex-wrap: wrap; min-width: 0; }
.nt-title { font-weight: 700; overflow-wrap: anywhere; }
.nt-link { color: inherit; text-decoration: none; }
.nt-link:hover { text-decoration: underline; }
.nt-item.unread .nt-title { font-weight: 800; }
.nt-body { font-size: .875rem; color: var(--text2); margin-top: .15rem; overflow-wrap: anywhere; }
.nt-when { font-size: 0.75rem; color: var(--text3); margin-top: .3rem; }
.nt-type { font-size: 0.75rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--text3); border: 1px solid var(--border); border-radius: 999px; padding: .12rem .5rem; white-space: nowrap; }
.nt-type.is-critical { border-color: var(--danger); color: var(--danger); }
.nt-type.is-warning { border-color: var(--warning); color: var(--warning); }
.nt-nodest { display: inline-flex; align-items: center; min-height: 44px; padding: 0 .75rem; border-radius: 10px; border: 1px dashed var(--border); color: var(--text3); font-size: .82rem; }
.nt-actions { margin-top: .6rem; display: flex; flex-wrap: wrap; gap: .4rem; }
.nt-actions > form { margin: 0; }
.nt-actions .btn, .nt-actions button { min-height: 44px; }
.nt-tabs { display: flex; gap: .5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.nt-tab { display: inline-flex; align-items: center; gap: .4rem; min-height: 44px; padding: .4rem 1rem; border-radius: 999px; border: 1.5px solid var(--border); background: var(--bg2); color: var(--text2); font-weight: 700; text-decoration: none; font-size: .88rem; }
.nt-tab.on { border-color: var(--danger); color: var(--danger); background: color-mix(in srgb, var(--danger) 8%, var(--bg2)); }
.nt-count { font-size: 0.75rem; font-weight: 800; border-radius: 999px; padding: .1rem .5rem; background: var(--bg3); color: var(--text3); }
.nt-tab.on .nt-count { background: var(--danger); color: var(--on-brand, #fff); }
.nt-filters { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: 1rem; }
.nt-chip { min-height: 40px; padding: .3rem .8rem; border-radius: 999px; border: 1px solid var(--border); background: var(--bg2); color: var(--text2); font-size: .8rem; font-weight: 700; cursor: pointer; }
.nt-chip.on { border-color: var(--warning); color: var(--warning); background: color-mix(in srgb, var(--warning) 10%, var(--bg2)); }
.nt-bulkbar { margin-bottom: 1rem; }

/* THE LIST COMES FIRST ON A PHONE.
   Measured at 390px on the notifications page: the workspace banner, the
   title, three tabs, the filter panel, the bulk bar and the category chips
   pushed the FIRST notification roughly 1,500px down the page — an opening
   screen with no notification on it. Below 560px the bulk bar folds away
   while nothing is selected (the page sets data-idle) and the category chips
   become one swipeable row instead of three stacked ones. Neither rule fires
   on a desktop, where the vertical room exists and the controls are wanted
   in view. */
@media (max-width: 560px) {
    .nt-bulkbar[data-idle="1"] { display: none; }
    .nt-filters {
        flex-wrap: nowrap;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding-bottom: .25rem;
    }
    .nt-filters::-webkit-scrollbar { display: none; }
    .nt-filters .nt-chip { flex: 0 0 auto; }
}
.ui-f-grow { flex: 1 1 200px; min-width: 0; }
.ui-f-label { display: block; font-size: .75rem; color: var(--text3); margin-bottom: .2rem; }
@media (max-width: 560px) {
  .nt-item { padding: .8rem .7rem; gap: .55rem; }
  .nt-ring { width: 42px; height: 42px; }
  .nt-actions > .btn, .nt-actions > form, .nt-actions > .nt-nodest, .nt-actions > button { flex: 1 1 46%; min-width: 0; }
  .nt-actions > form > .btn, .nt-actions > form > button { width: 100%; }
  .nt-actions .btn { font-size: .82rem; padding-inline: .5rem; white-space: normal; }
}

/* ── forms & definition lists on owner screens ─────────────────────────── */
.ui-check { display: flex; align-items: center; gap: .6rem; min-height: 44px; cursor: pointer; }
.ui-check input[type=checkbox] { width: 20px; height: 20px; }
.ui-dl { display: grid; grid-template-columns: minmax(120px, 36%) 1fr; gap: .45rem 1rem; margin: 0; }
.ui-dl dt { color: var(--text3); font-weight: 600; }
.ui-dl dd { margin: 0; overflow-wrap: anywhere; }
.ui-num { text-align: end; }
.ui-code { background: var(--bg2); border: 1px solid var(--border); border-radius: 10px; padding: .7rem .9rem; font-size: .82rem; overflow-x: auto; margin: 0; }
@media (max-width: 560px) { .ui-dl { grid-template-columns: 1fr; gap: .1rem; } .ui-dl dd { margin-bottom: .5rem; } }

/* TOTALS read right-aligned and sit at the end of what they total, on every
   money screen rather than on one. Narrow because a total is two words and a
   number, not a paragraph. */
.ui-dl-totals { margin-inline-start: auto; max-inline-size: 22rem; margin-block-start: 1rem; }
.ui-dl-totals dd { text-align: end; font-variant-numeric: tabular-nums; }

/* A ROW-LEVEL ACTION THAT TAKES ONE PIECE OF INPUT — a reason, a quantity —
   without a popup. Wraps on a phone so the field keeps its full width. */
.ui-inline-form { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.ui-inline-form .form-control { flex: 1 1 10rem; min-inline-size: 0; }

/* ── CHARTS (x-ui.chart) ──
   Inline SVG, no library. Data colours are the theme tokens, passed as inline
   fill/stroke by the component because a series index is data, not style;
   everything else is a class. Text is always ink, never the series colour. */
.ui-chart { display: flex; flex-direction: column; gap: .5rem; min-inline-size: 0; }
.ui-chart-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; flex-wrap: wrap; }
.ui-chart-title { margin: 0; font-size: .875rem; font-weight: 600; color: var(--text); }
.ui-chart-svg { inline-size: 100%; block-size: auto; aspect-ratio: 640 / 180; display: block; overflow: visible; direction: ltr; }
.ui-chart-grid { stroke: var(--border); stroke-width: 1; }
/* AXIS LABELS ARE STILL TEXT SOMEBODY HAS TO READ.
   10px ticks are conventional on a dense desktop chart and unreadable in one
   hand on a phone — the dashboard's date axis ("08-17", "08-21") and the
   analytics volume axis were the smallest type on either screen. A phone
   shows fewer ticks anyway, so it can afford the platform's own floor; the
   desktop keeps a tighter 11px where the density is the point. */
.ui-chart-tick { font-size: 12px; fill: var(--text3); font-family: inherit; }
/* On a phone the chart is drawn ~330px wide from a 640-unit viewBox, so SVG
   type is halved on the way to the screen: 24 units lands at ~12px, the
   platform's floor. Half the date ticks step aside (`is-minor`) so the larger
   labels never collide. */
@media (max-width: 560px) {
  .ui-chart-tick { font-size: 24px; }
  .ui-chart-tick.is-minor, .ui-chart-tick.is-y0 { display: none; }
  .ui-chart-svg .ui-chart-label { font-size: 22px; }
}
.ui-chart-label { font-size: 12px; font-weight: 600; fill: var(--text2); font-family: inherit; }
.ui-chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ui-chart-dot { stroke: var(--bg2); stroke-width: 2; }
.ui-chart-bar { stroke: var(--bg2); stroke-width: 1; }
.ui-chart-hit { fill: transparent; }
/* Series colours in FIXED order — a series keeps its colour whatever is filtered
   out. Blue, amber, grey, ink: measured (tests/Unit/ChartPaletteTest) to stay
   apart in every theme for full-colour AND colour-blind readers. The status
   tokens success/danger are NOT series colours — red vs green merges under
   deuteranopia, and a status colour must keep meaning "good/bad". */
.ui-chart-swatch.ui-s0 { background: var(--accent); }  .ui-chart-bar.ui-s0, .ui-chart-dot.ui-s0 { fill: var(--accent); }  .ui-chart-line.ui-s0 { stroke: var(--accent); }
.ui-chart-swatch.ui-s1 { background: var(--warning); } .ui-chart-bar.ui-s1, .ui-chart-dot.ui-s1 { fill: var(--warning); } .ui-chart-line.ui-s1 { stroke: var(--warning); }
.ui-chart-swatch.ui-s2 { background: var(--text2); }   .ui-chart-bar.ui-s2, .ui-chart-dot.ui-s2 { fill: var(--text2); }   .ui-chart-line.ui-s2 { stroke: var(--text2); }
.ui-chart-swatch.ui-s3 { background: var(--text); }    .ui-chart-bar.ui-s3, .ui-chart-dot.ui-s3 { fill: var(--text); }    .ui-chart-line.ui-s3 { stroke: var(--text); }
.ui-chart-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .25rem 1rem; font-size: .75rem; color: var(--text2); }
.ui-chart-legend li { display: inline-flex; align-items: center; gap: .375rem; }
.ui-chart-swatch, .ui-bar-swatch { inline-size: .625rem; block-size: .625rem; border-radius: 2px; display: inline-block; }
.ui-chart-table .data-table td.ui-num, .ui-chart-table .data-table th.ui-num { text-align: end; font-variant-numeric: tabular-nums; }
.ui-chart-grid-2 { display: grid; gap: .875rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr)); }
.ui-defs { margin: 0; display: grid; gap: .375rem .75rem; grid-template-columns: max-content 1fr; font-size: .8125rem; }
.ui-defs dt { color: var(--text2); font-weight: 600; }
.ui-defs dd { margin: 0; color: var(--text3); }
@media (max-width: 560px) { .ui-defs { grid-template-columns: 1fr; } .ui-defs dd { margin-block-end: .25rem; } .ui-chart-svg { aspect-ratio: 640 / 240; } }

/* ── TRANSLATION VERTICAL: a job's progress, and pair chips ──
   Steps read top to bottom; a done step carries its date. Logical properties
   only, so Arabic and Kurdish get the rail on the correct side. */
.tr-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .125rem; }
.tr-step { display: grid; grid-template-columns: 1.25rem 1fr; gap: .625rem; align-items: start; min-height: 44px; padding-block: .375rem; position: relative; }
.tr-step::before { content: ""; position: absolute; inset-inline-start: .55rem; inset-block: 1.35rem -.2rem; border-inline-start: 2px solid var(--border); }
.tr-step:last-child::before { display: none; }
.tr-step-dot { width: 1.25rem; height: 1.25rem; border-radius: 999px; border: 2px solid var(--border); background: var(--bg2); display: grid; place-items: center; font-size: 0.75rem; line-height: 1; color: var(--on-brand, #fff); }
.tr-step.is-done .tr-step-dot { background: var(--success); border-color: var(--success); }
.tr-step.is-current .tr-step-dot { background: var(--accent); border-color: var(--accent); }
.tr-step-label { font-weight: 600; font-size: .875rem; }
.tr-step.is-todo .tr-step-label { color: var(--text3); font-weight: 500; }
.tr-step-when { font-size: .75rem; color: var(--text3); }
.tr-pair { display: inline-flex; align-items: center; gap: .3rem; padding: .15rem .55rem; border-radius: 999px; background: var(--bg3); font-size: .8rem; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.tr-pair.is-certified { background: color-mix(in srgb, var(--success) 14%, var(--bg2)); color: var(--success); }
.tr-pairs { display: flex; flex-wrap: wrap; gap: .3rem; }
.tr-pairgrid { display: grid; gap: .5rem; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.tr-pairgrid .ui-check { border: 1px solid var(--border); border-radius: var(--radius); padding-inline: .6rem; background: var(--bg2); }
.tr-pairgrid .ui-check .tr-cert { margin-inline-start: auto; font-size: .75rem; color: var(--text3); display: inline-flex; align-items: center; gap: .3rem; }
.tr-actions { display: flex; flex-wrap: wrap; gap: .5rem; align-items: stretch; }
.tr-actions > form { margin: 0; display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.tr-actions .btn, .tr-actions button { min-height: 44px; }
.tr-fieldset { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: .75rem 1rem 1rem; margin: 0 0 .875rem; min-inline-size: 0; }
.tr-fieldset > legend { font-weight: 700; font-size: .875rem; color: var(--text2); padding-inline: .35rem; }
.tr-hint { font-size: .75rem; color: var(--text3); }
.tr-stack { display: flex; flex-direction: column; gap: .875rem; }
.tr-inline { display: inline-flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
@media (max-width: 560px) { .tr-actions > form, .tr-actions > .btn { flex: 1 1 100%; } .tr-actions > form > .btn, .tr-actions > form > button { flex: 1 1 auto; } }

/* ── RESTAURANT FLOOR PLAN (pos-tables): the chrome AROUND the stage ──
   Logical properties only, so the toolbar, legend and hints mirror for Arabic
   and Kurdish. The stage itself is pinned `direction: ltr` on purpose: a table's
   x/y is a place on a physical floor, and a floor does not flip with the
   language — mirroring it would put the terrace on the wrong side of the room. */
.pt-toolbar-form { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin: 0; }
.pt-upload { cursor: pointer; }
.pt-search { padding: .5rem .7rem; flex: 1; min-inline-size: 200px; max-inline-size: 340px; }
.pt-hint { color: var(--text3); font-size: .82rem; margin-inline-start: auto; }
.pt-stage { direction: ltr; }
.pt-stage-empty { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--text3); text-align: center; padding: 2rem; }
.pt-stage-empty .pt-glyph { font-size: 3rem; }
.pt-stage-empty h3 { margin: .5rem 0; font-weight: 700; color: var(--text); }
.pt-stage-empty p { margin: 0; max-inline-size: 380px; font-size: .9rem; }
.pt-legend-sep { border-inline-start: 1px solid var(--border); padding-inline-start: 1rem; margin-inline-start: .5rem; color: var(--text3); font-size: .78rem; }
.pt-legend-count { color: var(--text3); margin-inline-start: auto; font-size: .82rem; }
.pt-dot { display: inline-block; inline-size: 14px; block-size: 14px; border-radius: 50%; margin-inline-end: .35rem; border: 2px solid currentColor; vertical-align: middle; }
.pt-dot-free { color: var(--text3); background: var(--bg); }
.pt-dot-occupied { color: var(--warning); background: var(--bg2); }
.pt-dot-ready { color: var(--success); background: var(--bg2); }
.pt-dot-regular { color: var(--seat-regular); background: var(--seat-regular); }
.pt-dot-family { color: var(--seat-family); background: var(--seat-family); }
.pt-dot-bar { color: var(--seat-bar); background: var(--seat-bar); }
.pt-dot-booth { color: var(--seat-booth); background: var(--seat-booth); }
.pt-dot-outdoor { color: var(--seat-outdoor); background: var(--seat-outdoor); }
.pt-dot-private { color: var(--seat-private); background: var(--seat-private); }
.pt-none { padding: 1.2rem; margin-block-start: 1rem; text-align: center; color: var(--text3); }
.pt-none a { color: var(--accent); font-weight: 700; }
.pt-head-actions { display: flex; gap: .5rem; flex-wrap: wrap; }
.pt-note { font-size: 0.75rem; color: var(--text3); line-height: 1.5; margin-block-end: .5rem; }
.pt-cat-empty { grid-column: 1 / -1; text-align: center; color: var(--text3); padding: 1.5rem; }
.pt-receipt-meta { font-size: .85rem; color: var(--text2); }
.pt-receipt-empty { text-align: center; color: var(--text3); padding: 1.5rem 0; }

/* A value the operator must paste elsewhere: read-only field plus a copy
   button that stays on one line on a desktop and stacks on a phone, where a
   40-character webhook URL and a button do not fit side by side. */
.ui-copy { display: block; margin-block-end: .75rem; }
.ui-copy > label { display: block; font-size: .8rem; font-weight: 600; color: var(--text2); margin-block-end: .3rem; }
.ui-copy-row { display: flex; gap: .5rem; align-items: stretch; }
.ui-copy-row .form-control { flex: 1 1 auto; min-inline-size: 0; }
.ui-copy-btn { flex: 0 0 auto; white-space: nowrap; min-block-size: 44px; }
.ui-copy-btn.is-copied { border-color: var(--success); color: var(--success); }
@media (max-width: 560px) { .ui-copy-row { flex-direction: column; } }

/* Tenancy billing providers (/admin/integrations/billing). Lives here rather
   than on the page: a migrated screen composes the system, it does not carry a
   stylesheet of its own. */
.prov { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.prov-id { display: flex; align-items: center; gap: .75rem; }
.prov-mark { font-size: 1.75rem; line-height: 1; }
.prov-name { font-size: 1.05rem; font-weight: 700; color: var(--text); }
.prov-state { display: flex; gap: .4rem; flex-wrap: wrap; }
.prov-do { margin-block: .85rem 0; padding-block-start: .85rem; border-block-start: 1px solid var(--border);
           color: var(--text2); font-size: .88rem; line-height: 1.6; }
.cur-list { display: flex; gap: .35rem; flex-wrap: wrap; margin-block-end: .75rem; }

/* ── THE BULK-ACTION BAR ────────────────────────────────────────────────────
   Two list screens (customers, users) each carried their own hand-styled
   version of this — an accent-coloured strip with white controls on it — as
   thirty-odd inline style attributes. Declared once here, they compose it.
   Logical properties throughout, so it reverses under RTL by itself. */
.ui-bulkbar { display: none; align-items: center; gap: 1rem; flex-wrap: wrap;
              background: var(--accent); border-radius: var(--radius);
              padding: .75rem 1.25rem; margin-block-end: 1rem; }
.ui-bulkbar.is-on { display: flex; }
.ui-bulkbar-count { color: var(--on-brand); font-weight: 700; font-size: .9375rem; }
.ui-bulkbar-note { color: color-mix(in srgb, var(--on-brand) 72%, transparent); font-size: .875rem; }
.ui-bulkbar-all { color: var(--on-brand); text-decoration: underline; font-size: .8125rem; white-space: nowrap; }
.ui-bulkbar form { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; margin: 0; }
.ui-bulkbar .form-select, .ui-bulkbar .btn-secondary {
    background: color-mix(in srgb, var(--on-brand) 16%, transparent); color: var(--on-brand);
    border-color: color-mix(in srgb, var(--on-brand) 30%, transparent); }
/* The dropdown's own list is painted by the OS on a white sheet, so its
   options must not inherit the accent-ink of the closed control. */
.ui-bulkbar .form-select option, .ui-bulkbar .form-select optgroup { color: var(--text); background: var(--bg2); }
.ui-bulkbar-sep { display: flex; align-items: center;
                  border-inline-start: 1px solid color-mix(in srgb, var(--on-brand) 25%, transparent);
                  padding-inline-start: .875rem; }
.ui-bulkbar-danger { background: var(--danger); color: var(--on-brand); border: none;
                     padding: .5rem 1rem; border-radius: var(--radius); font-weight: 700; cursor: pointer; }
.ui-bulkbar-clear { background: color-mix(in srgb, var(--on-brand) 10%, transparent); color: var(--on-brand);
                    border: 1px solid color-mix(in srgb, var(--on-brand) 20%, transparent);
                    padding: .375rem .875rem; border-radius: var(--radius); cursor: pointer;
                    margin-inline-start: auto; font-size: .875rem; }

/* The "select every row on this page" strip above a list. The text sits INSIDE
   the label so the whole thing is one 44px tap target — it used to be a 22px
   checkbox beside words that did nothing when tapped. */
.ui-selectall { display: flex; align-items: center; gap: .75rem;
                padding: .6rem .9rem; border-block-end: 1px solid var(--border); }
.ui-selectall > label { display: flex; align-items: center; gap: .75rem;
                        cursor: pointer; min-height: 44px; }
.ui-selectall span { font-size: .78rem; color: var(--text3); font-weight: 600; }
.ui-rowcheck { accent-color: var(--accent); flex-shrink: 0; inline-size: 15px; block-size: 15px; }
.ui-pagerbar { padding: 1rem 1.25rem; border-block-start: 1px solid var(--border); }
.ui-status-cell { min-inline-size: 64px; text-align: end; }
.ui-mark-initials { font-weight: 800; font-size: .95rem; color: var(--accent); }

/* ── THE USERS SCREEN ───────────────────────────────────────────────────────
   Declared here rather than inline. This screen carried 36 style attributes
   for what is really eight recurring ideas: a quiet note under a name, a
   small table cell, a stacked menu of destructive actions, a full-width
   button in that menu. Naming them is what lets the next screen reuse them
   instead of typing the values again. */
.usr-name      { font-weight: 500; }
.usr-note      { font-size: 0.75rem; color: var(--text3); }
.usr-note.is-spaced { margin-block-start: .2rem; }
.usr-you       { font-size: .75rem; color: var(--accent); }
.usr-cell      { font-size: .875rem; }
.usr-cell.is-quiet { color: var(--text3); }
.usr-cell-time { font-size: .8125rem; color: var(--text3); }
.usr-check-col { inline-size: 34px; }
.usr-actions   { display: flex; flex-wrap: wrap; gap: .375rem; max-inline-size: 100%; }
.usr-more      { max-inline-size: 100%; }
.usr-more > summary { min-height: 44px; display: inline-flex; align-items: center; cursor: pointer; }
.usr-more-menu { display: flex; flex-direction: column; gap: .3rem; padding: .45rem;
                 border: 1px solid var(--border); border-radius: 10px; background: var(--bg2);
                 margin-block-start: .3rem; min-inline-size: min(220px, 100%); max-inline-size: 100%; }
.usr-more-menu .btn { inline-size: 100%; }
.usr-reset     { margin-block-end: 1rem; }
.usr-reset-title { font-weight: 700; margin-block-end: .4rem; }
.usr-reset-note  { font-size: .82rem; margin-block-end: .5rem; }
.usr-reset-link  { font-size: .8rem; }
.usr-warn        { margin-block-start: .8rem; }
.usr-warn ul     { margin: .35rem 0 0; padding-inline-start: 1.1rem; font-size: .86rem; }
/* A checkbox in a table row: 18px is the drawn box, and admin.css gives the
   44px hit area to the label that wraps it. */
.ui-rowcheck-lg  { inline-size: 18px; block-size: 18px; accent-color: var(--accent); }
/* An empty table body says why it is empty, in the table's own grid. */
.ui-table-empty  { text-align: center; padding: 2rem; color: var(--text3); }

/* ══════════════════════════════════════════════════════════════════════════
   THE PHONE IS A SUPPORTED WIDTH, NOT A DEGRADED ONE
   ─────────────────────────────────────────────────────────────────────────
   Owner acceptance, Sep 2026, tested on an iPhone against the real Platform
   console: "MOBILE RESPONSIVE ACCEPTANCE: FAIL. The page content is exceeding
   the mobile viewport… Nothing in the Platform owner console should require me
   to move the page sideways or have content disappear beyond the phone frame."

   Three rules had caused most of it and are gone (see admin.css): a
   page-level `overflow-x: hidden` that clipped rather than fixed, a
   `white-space: nowrap` on every table in a card, and a card body turned into
   a horizontal scroll container. What follows is the replacement — in the
   SHARED components, so the defect cannot come back one page at a time.

   His rule, kept verbatim as the acceptance test: at supported mobile widths
   there is NO horizontal page overflow, and no data or control may be hidden
   to achieve it.
   ══════════════════════════════════════════════════════════════════════════ */

/* THE FLOATING LAYER'S FLOOR. Four separate floating controls (the chrome
   pill, the demo chip, the matter timer, the collapse peek) each hard-coded
   their own bottom offset and none knew about the iPhone home bar, so they sat
   in the gesture area and over the page's own actions. One token, safe-area
   aware, and every floating control measures from it. */
:root { --float-floor: calc(.85rem + env(safe-area-inset-bottom, 0px));
        --float-side: calc(.85rem + env(safe-area-inset-right, 0px)); }

@media (max-width: 560px) {
  /* ── LABEL / VALUE PAIRS STACK. A 40%-label + 60%-value split is a desktop
        shape: at 390px the value half is ~200px, which a long email or a
        visitor id cannot live in. Stacked, each gets the full width. */
  .ui-defs, .ui-dl { grid-template-columns: 1fr; }
  .ui-details-body dl { grid-template-columns: 1fr; }
  .ui-details-body dt { margin-block-start: .5rem; }

  /* ── FILTER BARS WRAP DELIBERATELY, one control per line, each a full-width
        44px target rather than four squeezed onto one row. */
  .ui-filters { gap: .5rem; }
  .ui-filters .ui-f, .ui-filters .ui-f-wide, .ui-f-grow { flex: 1 1 100%; min-inline-size: 0; }
  .ui-filters .form-control, .ui-filters .form-select { inline-size: 100%; min-inline-size: 0; }
  .ui-filters .ui-f-actions { flex: 1 1 100%; display: flex; gap: .5rem; }
  .ui-filters .ui-f-actions .btn { flex: 1 1 auto; min-height: 44px; }
  .ui-filters label.field { flex: 1 1 100%; }
  .ui-filters > .btn { flex: 1 1 auto; }
  /* The count was pushed to the far edge by `margin-inline-start: auto`, which
     on one wrapped line put it hard against the frame. */
  .ui-filters .ui-f-count { margin-inline-start: 0; flex: 1 1 100%; }

  /* ── ACTION GROUPS STACK AND STAY REACHABLE. Every action keeps its own
        line rather than being compressed until its label is unreadable. */
  /* THE HEADER'S ACTIONS SHARE A ROW rather than stacking full-width. Two
     buttons at 100% each is a third of a phone screen spent before any
     content — the reader came for the page, not for its toolbar. They still
     wrap when there are more than two, and each keeps a 44px target. */
  .ui-head-actions { inline-size: 100%; flex-wrap: wrap; }
  .ui-head-actions .btn { flex: 1 1 calc(50% - .25rem); min-height: 44px; min-inline-size: 0; }
  /* A lone action does take the full width — there is nothing to share with. */
  .ui-head-actions .btn:only-child { flex-basis: 100%; }
  /* A `display: contents` WRAPPER GENERATES NO BOX, so a flex rule on it is
     silently ignored and the CONTROL inside keeps its intrinsic width. That is
     what clipped "Enter <tenant>" 69px past the edge of a 375px screen: the
     <form> around the button is display:contents, the button stayed
     `flex: 0 1 auto` at 206px, and the measurement — not the CSS — is what
     found it. The contract therefore sits on the control itself. */
  /* GRID, NOT WRAPPED FLEX — because one child here is a `display: contents`
     <form>, which generates no box. A flex rule on it is silently ignored, and
     the button inside becomes an anonymous flex item that would not wrap onto
     its own line however the container was told to wrap. Measured, twice: the
     button sat 69px past a 375px edge, and "fixing" the flex basis merely moved
     it to 330px past. A single-column grid gives every item its own row with no
     wrap logic to get wrong, anonymous items included. */
  .ui-entity-foot { display: grid; grid-template-columns: 1fr; gap: .5rem; }
  .ui-entity-foot > *, .ui-entity-foot form { inline-size: auto; min-inline-size: 0; }
  .ui-entity-foot .btn, .ui-entity-foot button, .ui-entity-foot > a {
    inline-size: 100%; min-inline-size: 0; min-height: 44px;
    justify-content: center; box-sizing: border-box; }
  .ui-form-actions { flex-wrap: wrap; }
  .ui-form-actions .btn, .ui-form-actions button {
    flex: 1 1 100%; inline-size: 100%; min-inline-size: 0;
    min-height: 44px; justify-content: center; box-sizing: border-box; }
  /* Same reasoning for the head actions and the bulk bar: reach the control,
     not whatever wrapper happens to be around it. */
  .ui-head-actions .btn, .ui-head-actions button, .ui-head-actions > a,
  .ui-bulkbar .btn, .ui-bulkbar button {
    inline-size: 100%; min-inline-size: 0; justify-content: center; box-sizing: border-box; }

  /* EXCEPT A LONE QUIET ACTION, which does not outrank the page.
     Stretching to full width is right for a page's MAIN action and wrong when
     the only action is a secondary one: on Platform finance "Export standing"
     became a full-width slab between the title and the figures, so the first
     thing a phone showed was a download button rather than what the platform
     is owed. The overflow this 100% rule was written for came from a
     `display: contents` form wrapper around a button; a plain secondary
     anchor has no such wrapper and is safe at its intrinsic width. */
  .ui-head-actions > .btn-secondary:only-child {
    flex: 0 0 auto; inline-size: auto;
  }

  /* ── THE BULK-ACTION BAR REFLOWS instead of compressing. `margin-inline-start:
        auto` on Clear pinned it to the frame edge on a wrapped line. */
  .ui-bulkbar { padding: .75rem .9rem; gap: .6rem; }
  .ui-bulkbar form, .ui-bulkbar .ui-bulkbar-sep { flex: 1 1 100%; }
  .ui-bulkbar .form-select, .ui-bulkbar .btn, .ui-bulkbar-danger { flex: 1 1 100%; min-height: 44px; }
  .ui-bulkbar-clear { margin-inline-start: 0; flex: 1 1 100%; min-height: 44px; }
  .ui-bulkbar-sep { border-inline-start: 0; padding-inline-start: 0; }

  /* ── CARDS AND PANELS OWN THE WIDTH THEY ARE GIVEN. No fixed or minimum
        width may exceed the frame, and the padding shrinks rather than the
        text. `box-sizing` is stated because a card with padding plus
        `width: 100%` is wider than its parent without it. */
  .ui-panel, .ui-kpi, .ui-metric, .ui-alert, .ui-danger, .ui-empty {
    inline-size: 100%; max-inline-size: 100%; min-inline-size: 0; box-sizing: border-box; }
  .ui-panel-body { padding-inline: .85rem; }
  .ui-grid { --min: 100%; }
  /* …EXCEPT a row of KPI cards. Four one-per-row cards is four screens of
     scrolling to read four numbers, and the numbers are the part somebody
     came for. They are short by construction — a label, a figure, a caption —
     so they pair up and the summary stays above the fold. */
  .ui-grid-tight { --min: 45%; }

  /* ── LONG IDENTIFIERS WRAP SAFELY. An email, a uuid, a visitor id or a
        hash has no spaces to break at, so it needs `anywhere`. This is the
        wrap half of the owner's rule; the copy-field component is the
        shortened-with-a-copy-action half. */
  .ui-mono, .ui-code, .ui-list-meta, .ui-msg-body, .ui-metric-value,
  .ui-entity-sub, .ui-defs dd, .ui-dl dd { overflow-wrap: anywhere; }
  /* A value that was allowed to refuse to wrap is the single most common
     cause of a row wider than the screen. */
  .ui-metric-value, .ui-f-count { white-space: normal; }
  /* A title must never be what widens the row — but on a phone the answer
     to that is WRAPPING, not an ellipsis. Measured at 390px: "Compiled
     views" was cut to 65px of a 100px word inside a card with the whole
     screen still to spare, and the account name in the sidebar footer read
     "Platform Ow…". An ellipsis is the right call when the row is genuinely
     out of room; here the row had room and the truncation was pure loss.
     Wrapping satisfies the original constraint (a wrapped title cannot
     widen a row) and shows the whole name. */
  .ui-entity-title, .ui-list-title {
    min-inline-size: 0;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    /* break-word, NOT anywhere. "anywhere" lets the box shrink to a single
       character, and a flex parent will take that offer: it rendered
       "Application cache" as a 26px column 94px tall — a word broken
       vertically, which is the thing this whole rule exists to prevent.
       break-word keeps min-content at the longest word, so the row still
       cannot be widened and the title still cannot be shredded. */
    overflow-wrap: break-word;
    /* In a flex row the title must CLAIM the free space. Without this it is
       sized at min-content and any sibling — a checkbox, a hint, a badge —
       squeezes it: the backup screen's "Application cache" row laid out as
       [checkbox][title][hint] and gave the title 26px of a 333px row. */
    flex: 1 1 auto;
  }
  /* A row built from a checkbox, a title and a hint does not fit on one line
     of a phone. Let it wrap instead of shrinking its own text to a column. */
  .ui-list-item { flex-wrap: wrap; }

  /* ── FLOATING CONTROLS RESPECT THE SAFE AREA and never cover an action. */
  .chrome-show-pill, .demo-mini-chip { inset-block-end: var(--float-floor) !important; }

  /* ── THE STATUS COLUMN stops reserving width it does not need. */
  .ui-status-cell { min-inline-size: 0; }
}

/* Below 420px the label/value split inside a stacked table row is itself too
   narrow to be worth having: the label goes ABOVE the value. */
@media (max-width: 420px) {
  table.responsive td, table.data-table td { display: block; }
  table.responsive td::before, table.data-table td::before {
    display: block; flex: none; margin-block-end: .1rem; }
}

/* MODALS AND DRAWERS fit the frame, on every width. A dialog with a fixed
   340px width is wider than the content area of a small phone once the page
   padding is counted, and the part that overflowed carried the buttons. */
/* The dial pad keeps its own 340px design width where there is room for it;
   the point of the rule is the second half, which caps it to the frame. */
.dialpad-modal { --modal-w: 340px; }
.dialpad-modal, .ui-modal, .ui-drawer {
  inline-size: min(var(--modal-w, 420px), calc(100vw - 1.5rem));
  max-inline-size: calc(100vw - 1.5rem);
  box-sizing: border-box;
  max-block-size: calc(100vh - 2rem);
  overflow-y: auto;
  padding-block-end: max(1rem, env(safe-area-inset-bottom, 0px));
}

/* ── STATUS LIST (x-ui.status-list / x-ui.status-item) ──────────────────────
   ONE FACT IS ONE LINE.

   A data table with one row per fact becomes one CARD per fact below 420px.
   Platform Health — twenty-three checks, each with a status, a finding, an
   evidence link and a timestamp — measured 8,617px at 390px: ten iPhone
   screens of swiping to learn whether the platform was up. The information
   was correct and the page was unusable, which is the whole point the owner
   made on 13 Sep 2026.

   So the row shrinks. A dot carries the state, the name carries the subject,
   a pill carries the word, and everything a reader wants only sometimes —
   evidence, recommendation, timestamp, the raw environment key — costs zero
   height until it is opened. The same component serves desktop, where the
   extra width goes to the summary line rather than to more columns. */

.ui-slist {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: 14px;
  background: var(--bg2); overflow: hidden;
}
.ui-slitem + .ui-slitem { border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }

/* ON A PHONE THE BADGE GETS ITS OWN LINE.
   Side by side, a status pill ("Needs attention") takes better than half of a
   390px row, so the one-line summary beside it was clamped after three words:
   "Not running as…", "Running a commit later…", "The platform is open to…".
   Every one of those cuts exactly where the meaning starts, which is worse
   than no summary — the reader has to open each row to learn anything.
   So below 700px the row becomes a two-line grid: dot + title + chevron on
   top, summary across the full width, badge under it. The touch target, the
   dot and the chevron are unchanged; only the wrapping differs. */
.ui-slitem-row,
.ui-slitem-d > summary {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "dot main chev"
    ".   sum  sum";
  align-items: center; gap: .3rem .7rem;
  padding: .8rem .9rem; min-block-size: 44px;   /* the touch-target floor */
  color: inherit; text-decoration: none;
  cursor: pointer; list-style: none;
}
.ui-slitem-row > .ui-slitem-dot,
.ui-slitem-d > summary > .ui-slitem-dot { grid-area: dot; }
.ui-slitem-row > .ui-slitem-main,
.ui-slitem-d > summary > .ui-slitem-main { grid-area: main; }
.ui-slitem-row > .ui-badge,
.ui-slitem-d > summary > .ui-badge { grid-area: sum; justify-self: start; }
.ui-slitem-row > .ui-slitem-chev,
.ui-slitem-d > summary > .ui-slitem-chev { grid-area: chev; }
.ui-slitem-d > summary::-webkit-details-marker { display: none; }
.ui-slitem-row:hover, .ui-slitem-d > summary:hover { background: var(--bg3); }
.ui-slitem-d > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* THE DOT IS THE STATE, and it is never the only carrier of it — the pill
   spells the same word, so colour vision is never the requirement. */
.ui-slitem-dot {
  inline-size: 9px; block-size: 9px; border-radius: 50%;
  flex: 0 0 auto; background: var(--text3);
}
.ui-slitem.is-good     .ui-slitem-dot { background: var(--success); }
.ui-slitem.is-warning  .ui-slitem-dot { background: var(--warning); }
.ui-slitem.is-critical .ui-slitem-dot { background: var(--danger); }
.ui-slitem.is-accent   .ui-slitem-dot { background: var(--accent); }

.ui-slitem-main { display: flex; flex-direction: column; gap: .1rem; min-inline-size: 0; flex: 1; }
.ui-slitem-title { font-weight: 600; font-size: .92rem; line-height: 1.35; unicode-bidi: isolate; }
.ui-slitem-sum {
  font-size: .8rem; color: var(--text2); line-height: 1.4; unicode-bidi: isolate;
  /* One line, and the full text is inside the disclosure — a summary that
     grows to four lines is the thing this component exists to prevent. */
  /* Two lines now that the summary owns the full row width on a phone; the
     complete sentence is still inside the disclosure. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.ui-slitem-chev {
  flex: 0 0 auto; color: var(--text3); font-size: 1.1rem;
  transition: transform .18s ease;
}
.ui-slitem-d[open] > summary .ui-slitem-chev { transform: rotate(90deg); }

.ui-slitem-body {
  padding: .2rem .9rem 1rem 1.65rem;
  display: flex; flex-direction: column; gap: .7rem;
  font-size: .85rem; color: var(--text2);
}
.ui-slitem-meta { margin: 0; font-size: .78rem; color: var(--text3); }
.ui-slitem-dl { margin: 0; display: grid; grid-template-columns: 1fr; gap: .15rem .9rem; }
.ui-slitem-dl dt { font-weight: 650; color: var(--text2); font-size: .78rem; }
.ui-slitem-dl dd { margin: 0 0 .5rem; color: var(--text); overflow-wrap: anywhere; }
.ui-slitem-dl.is-tech dd code { font-size: .78rem; }

/* THE MACHINE'S VOCABULARY LIVES UNDERNEATH THE HUMAN EXPLANATION, never as
   the label. An environment variable is an answer to "where does this come
   from", which is a question a reader asks second. */
.ui-slitem-tech > summary {
  cursor: pointer; font-size: .78rem; font-weight: 650; color: var(--text3);
  padding: .35rem 0; min-block-size: 32px; display: flex; align-items: center;
}
.ui-slitem-tech[open] > summary { color: var(--text2); }

/* From tablet up the summary gets the extra width instead of the page
   growing more columns. */
@media (min-width: 700px) {
  /* From tablet up there is room for the pill beside the text again. */
  .ui-slitem-row, .ui-slitem-d > summary {
    display: flex; align-items: center; padding-inline: 1.1rem;
  }
  .ui-slitem-sum { -webkit-line-clamp: 2; }
  .ui-slitem-dl { grid-template-columns: minmax(8rem, 12rem) 1fr; }
  .ui-slitem-dl dt { padding-block-start: .05rem; }
  .ui-slitem-dl dd { margin-block-end: .25rem; }
}

@media (prefers-reduced-motion: reduce) {
  .ui-slitem-chev { transition: none; }
}

/* A STATE PILL NEVER GETS CLIPPED TO MAKE ROOM FOR A TITLE. The title and the
   summary already clamp; the pill is the one thing on the row that must be
   read whole, so it keeps its width and the text beside it gives way. */
.ui-slitem .ui-badge { flex: 0 0 auto; }
.ui-slitem-full { margin: 0; color: var(--text); line-height: 1.5; }

/* ── SECTOR STATUS CARD (Demo control) ──────────────────────────────────────
   The sector showroom was a nine-column table — Sector, Business, Level,
   Provisioned, Visitors, Pack, Health, Doors, Reset — which below 420px became
   nine stacked rows for every sector on the platform. A card carries the same
   facts in the order somebody asks for them: what is this, whose business is
   it, is it well, when was it last refreshed, and what can I do with it. */

.ui-sector { border-inline-start: 3px solid var(--border); }
.ui-sector.is-good     { border-inline-start-color: var(--success); }
.ui-sector.is-warning  { border-inline-start-color: var(--warning); }
.ui-sector.is-critical { border-inline-start-color: var(--danger); }

.ui-sector-head { display: flex; align-items: flex-start; gap: .65rem; margin-block-end: .6rem; }
.ui-sector-icon { font-size: 1.35rem; line-height: 1.2; flex: 0 0 auto; }
.ui-sector-name { display: flex; flex-direction: column; gap: .1rem; min-inline-size: 0; flex: 1; }
.ui-sector-name strong { font-size: .95rem; line-height: 1.3; }
.ui-sector-name .ui-muted { font-size: .8rem; }
.ui-sector-head .ui-badge { flex: 0 0 auto; }

/* AN INCOMPLETE SECTOR SAYS SO ON ITS OWN CARD, not in a column somebody has
   to go looking for. */
.ui-sector-warn {
  margin: 0 0 .6rem; padding: .45rem .6rem; border-radius: 8px;
  font-size: .8rem; line-height: 1.45;
  background: color-mix(in srgb, var(--warning) 12%, var(--bg2));
  color: var(--text);
}

.ui-sector-facts { margin: 0 0 .6rem; display: flex; flex-direction: column; gap: .2rem; }
.ui-sector-facts > div { display: flex; justify-content: space-between; gap: .75rem; font-size: .8rem; }
.ui-sector-facts dt { color: var(--text3); }
.ui-sector-facts dd { margin: 0; color: var(--text); text-align: end; overflow-wrap: anywhere; }

.ui-sector-foot { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.ui-sector-foot .btn { min-block-size: 40px; }

/* THE RESET STAYS SHUT. A destructive action that is one tap away on a card
   somebody is scrolling past is a destructive action waiting to happen; it
   still demands the typed phrase underneath. */
.ui-sector-reset { inline-size: 100%; }
.ui-sector-reset > summary {
  cursor: pointer; font-size: .8rem; font-weight: 650; color: var(--danger);
  padding: .4rem 0; min-block-size: 40px; display: flex; align-items: center;
}
.ui-sector-reset .ui-inline-form { padding-block-start: .35rem; }

/* ── THE FILTER BAR STARTS SHUT ON A PHONE ──────────────────────────────────
   Nine full-width selects opened the users screen on its own controls: the
   list somebody came to read began two screens down. The fields collapse
   behind one row below 700px and behave exactly as before above it, so no
   page had to change to get this. */
.ui-filters-d > summary {
  display: none;              /* wide screens: no disclosure at all */
}
.ui-filters-fields { display: contents; }

@media (max-width: 700px) {
  .ui-filters-d > summary {
    display: flex; align-items: center; gap: .5rem;
    cursor: pointer; list-style: none;
    padding: .6rem .1rem; min-block-size: 44px;
    font-weight: 650; font-size: .9rem; color: var(--text2);
  }
  .ui-filters-d > summary::-webkit-details-marker { display: none; }
  .ui-filters-d > summary::before {
    content: '▸'; color: var(--text3); transition: transform .18s ease;
  }
  .ui-filters-d[open] > summary::before { transform: rotate(90deg); }
  .ui-filters-fields {
    display: flex; flex-direction: column; gap: .5rem;
    padding-block-end: .4rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ui-filters-d > summary::before { transition: none; }
}

/* THE WAY OUT OF AN EMPTY STATE. An empty panel that explains itself and then
   leaves the reader to find the door is half a design; this is where the door
   goes. Stacked and full-width on a phone so the action is a thumb-sized
   target rather than a link lost in a paragraph. */
.ui-empty-act {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: .5rem; margin-block-start: .9rem;
}
.ui-empty-act p { margin: 0 0 .4rem; flex: 1 1 100%; color: var(--text3); font-size: .82rem; line-height: 1.5; }
.ui-empty-act .btn { min-block-size: 44px; }
@media (max-width: 560px) { .ui-empty-act .btn { flex: 1 1 100%; } }

/* ── The journey spine: where a multi-step piece of work has got to ───────

   THE OWNER'S SUN ZIP, READ VERTICALLY, AND NOW IN ONE PLACE.

   Welded red above (steps finished), the sun at the step being worked, open
   green teeth below (steps ahead) — the same three values as `.sun-zip`, the
   loader and the courier road, so the brand reads as one thing everywhere.

   It was defined page-locally inside the ReVeRa engagement workspace, in a
   pushed style block — the owner's own visual language living in one template
   where nothing else could reach it, so the next screen that needed a journey
   would have copied it rather than used it. Logical properties throughout, so
   the rail mirrors itself under RTL without a second rule. */
/* SCREEN-READER ONLY. Progress must never rely on colour alone, so every step
   carries its state as a word as well as a hue — and that word is for the
   reader who cannot see the hue, not for the one who can. */
.u-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
             overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }

.ui-journey { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 1rem 1.1rem; margin-bottom: 1.5rem; box-shadow: var(--shadow); }
.ui-journey-head { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; margin-bottom: .8rem; }
.ui-journey-head h2 { font-size: .8125rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text2); margin: 0; }
.ui-journey-count { font-size: .75rem; color: var(--text3); }

.ui-journey-steps { list-style: none; margin: 0; padding: 0; }
.ui-jstep { position: relative; padding-inline-start: 2.6rem; padding-bottom: 1.1rem; min-width: 0; }
.ui-jstep:last-child { padding-bottom: .1rem; }
/* The rail between one step and the next: welded behind you, open ahead. */
.ui-jstep::before { content: ''; position: absolute; inset-inline-start: 13px; top: 28px; bottom: 2px; width: 4px; border-radius: 2px; }
.ui-jstep:last-child::before { display: none; }
.ui-jstep.is-done::before { background: var(--step-done); }
.ui-jstep.is-now::before,
.ui-jstep.is-waiting::before { background-image: repeating-linear-gradient(180deg, var(--step-waiting-ink) 0 7px, transparent 7px 13px); }
.ui-jstep.is-blocked::before { background: var(--step-blocked); }

.ui-jstep-mark { position: absolute; inset-inline-start: 2px; top: 0; width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-weight: 900; font-size: .8rem; }
.ui-jstep.is-done .ui-jstep-mark { background: var(--step-done); color: var(--on-brand, #fff); }
/* THE CURRENT STEP WEARS THE BRAND ACCENT, not a generic amber.
   It breathes — slowly, once every 3.2 seconds, in step with the loader — and
   stops entirely for anybody who has asked for less motion. Meaningful, not
   decorative: it is the one thing on the page saying "you are here". The glow
   is mixed FROM the accent so a tenant who changes their brand colour gets a
   halo that follows it instead of a leftover amber. */
.ui-jstep.is-now .ui-jstep-mark { background: var(--step-now); color: var(--on-brand, #fff);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--step-now) 22%, transparent);
  animation: ui-jsun 3.2s ease-in-out infinite; }
/* WAITING: a soft rose that reads as "not yet", never as "wrong". The fill is
   ten per cent opacity so a column of nine pending steps stays calm, and the
   ink is desaturated far below the destructive red two rules down — a customer
   must never have to work out which red they are looking at. */
.ui-jstep.is-waiting .ui-jstep-mark { background: var(--step-waiting-bg); border: 2px dashed var(--step-waiting-ink); color: var(--step-waiting-ink); }

/* BLOCKED: this one really is wrong, so it is the only saturated red here, and
   it never appears without a sentence saying what happened and what to do —
   the component refuses to render it without one. */
.ui-jstep.is-blocked .ui-jstep-mark { background: var(--step-blocked); color: var(--on-brand, #fff); border: 0; }
.ui-jstep.is-blocked .ui-jstep-label { color: var(--step-blocked); font-weight: 800; }
.ui-jstep-fix { display: inline-flex; align-items: center; gap: .3rem; margin-top: .35rem; min-height: 44px;
                font-size: .8125rem; font-weight: 700; color: var(--step-blocked); text-decoration: none; }
.ui-jstep-fix:hover { text-decoration: underline; }

/* THE CURRENT STEP CARRIES LIVE STATUS, so it gets room for a line of it. */
.ui-jstep-live { display: flex; align-items: center; gap: .4rem; margin-top: .25rem; font-size: .78rem; color: var(--text2); }
.ui-jstep-spin { width: 12px; height: 12px; border-radius: 50%; flex: none;
                 border: 2px solid color-mix(in srgb, var(--step-now) 30%, transparent);
                 border-top-color: var(--step-now); animation: ui-jspin .9s linear infinite; }
@keyframes ui-jspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ui-jstep-spin { animation: none; border-top-color: var(--step-now); } }

/* A STEP IS A LINK ONLY WHERE GOING THERE IS SAFE. The owner's rule: do not
   let somebody jump into a downstream state that has not been created yet. */
.ui-jstep-go { color: inherit; text-decoration: none; display: block; min-height: 44px; }
.ui-jstep-go:hover .ui-jstep-label { text-decoration: underline; }
@keyframes ui-jsun { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.14); } }
@media (prefers-reduced-motion: reduce) { .ui-jstep.is-now .ui-jstep-mark { animation: none; } }

.ui-jstep-body { display: flex; flex-direction: column; min-width: 0; }
.ui-jstep-label { font-weight: 800; font-size: .9375rem; line-height: 26px; }
.ui-jstep.is-done .ui-jstep-label { color: var(--text3); font-weight: 600; }
/* WAITING IS NOT FAILURE and is never coloured as though it were: the
   unexamined is a first-class state in ReVeRa's doctrine and on this platform,
   and a step that has not arrived yet is the same thing. */
.ui-jstep.is-waiting .ui-jstep-label { color: var(--text3); font-weight: 600; }
.ui-jstep-detail { font-size: .78rem; color: var(--text3); margin-top: .1rem; }
.ui-journey-next { margin: .8rem 0 0; font-size: .8125rem; color: var(--text2); }

/* ── ReVeRa workspace ──────────────────────────────────────────────────────
   Evidentra's recruiter surface. These lived in engagement show.blade.php's
   own style block, where the shortlist and the intake screens could not
   reach them and drifted apart. One place now — the owner's "no second
   framework" rule applied to a page that had quietly grown its own. */
.rv-drop { border: 2px dashed var(--border); border-radius: var(--radius-lg); padding: 2.5rem 1rem; text-align: center;
           color: var(--text3); cursor: pointer; transition: border-color .15s, color .15s; }
.rv-drop.is-hot { border-color: var(--accent); color: var(--accent); }
.rv-drop strong { display: block; font-size: 1.05rem; color: var(--text2); }
.rv-drop-status { margin-top: .5rem; font-size: .85rem; }

.rv-progress { height: 10px; background: var(--bg3); border-radius: 999px; overflow: hidden; margin-top: .4rem; }
.rv-progress > span { display: block; height: 100%; width: 0; background: var(--accent); transition: width .3s; }

.rv-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 1rem; margin: .9rem 0 0; }
.rv-tile { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem; }
.rv-tile-label { font-size: 0.75rem; color: var(--text3); text-transform: uppercase; letter-spacing: .03em; }
.rv-tile-value { font-size: 1.6rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.rv-tile-value.tone-good  { color: var(--success-on-surface, var(--success)); }
.rv-tile-value.tone-info  { color: var(--info); }
.rv-tile-value.tone-quiet { color: var(--text3); }

.rv-ai { border-radius: var(--radius); padding: .7rem 1rem; font-size: .85rem; margin-bottom: 1rem;
         display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; }
.rv-ai.is-on  { background: color-mix(in srgb, var(--success) 8%, var(--bg2)); border: 1px solid color-mix(in srgb, var(--success) 25%, transparent); }
.rv-ai.is-off { background: var(--bg2); border: 1px solid var(--border); color: var(--text3); }
.rv-ai-icon { font-size: 1.2rem; }
.rv-ai-text { flex: 1; min-width: 0; }

.rv-crit { display: flex; align-items: center; gap: .6rem; padding: .45rem 0; border-bottom: 1px solid var(--border); font-size: .9rem; }
.rv-crit-name { flex: 1; min-width: 0; }
.rv-crit-meta { color: var(--text3); font-size: .78rem; }
.rv-crit-must { background: color-mix(in srgb, var(--danger) 14%, var(--bg2)); color: var(--danger);
                border-radius: 999px; padding: .1rem .6rem; font-size: 0.75rem; font-weight: 800; }
.rv-crit-job { font-size: .8rem; font-weight: 800; color: var(--text2); margin: .75rem 0 .15rem; }

/* THE READING CONSOLE. Deliberately dark in both themes: it is a machine
   window showing the CV text actually being read, not a surface of the app. */
.rv-console { display: grid; grid-template-columns: 1fr; gap: .8rem; }
.rv-console-screen { background: var(--rv-screen-bg); border: 1px solid var(--rv-screen-edge); border-radius: var(--radius);
                     padding: .85rem 1rem; box-shadow: inset 0 0 40px rgba(96,165,250,.06); }
.rv-console-lamps { display: flex; align-items: center; gap: .4rem; margin-bottom: .5rem; }
.rv-console-lamps i { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.rv-console-lamps i:nth-child(1) { background: var(--danger); }
.rv-console-lamps i:nth-child(2) { background: var(--warning); }
.rv-console-lamps i:nth-child(3) { background: var(--success); }
.rv-console-name { margin-inline-start: .5rem; font-family: ui-monospace, monospace; font-size: 0.75rem; color: var(--text2);
                   overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-console-out { margin: 0; min-height: 6.5rem; max-height: 10rem; overflow: auto; color: var(--rv-screen-ink);
                  font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: .78rem; line-height: 1.6;
                  white-space: pre-wrap; word-break: break-word; }
.rv-console-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .5rem; flex-wrap: wrap; }
.rv-console-head h2 { font-size: 1rem; font-weight: 800; margin: 0; flex: 1; min-width: 200px; }
.rv-console-track { flex: 2; min-width: 180px; height: 10px; background: var(--bg2); border-radius: 999px; overflow: hidden; }
.rv-console-track > span { display: block; height: 100%; width: 0; border-radius: 999px;
                           background: linear-gradient(90deg, var(--info), color-mix(in srgb, var(--info) 45%, #fff));
                           transition: width .35s ease; }
.rv-console-pct { font-variant-numeric: tabular-nums; font-weight: 800; font-size: .9rem; min-width: 3.2rem; text-align: end; }
.rv-console-status { font-size: .82rem; color: var(--text2); margin-bottom: .5rem; }
.rv-verdicts { display: flex; flex-direction: column; gap: .35rem; font-size: .85rem; }

/* THE JOB BOARD — one card per job in the engagement. */
.rv-jobs { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: .9rem; }
.rv-job { border: 1px solid var(--border); border-radius: var(--radius); padding: .85rem 1rem; background: var(--bg2); }
.rv-job-title { font-weight: 800; margin-bottom: .3rem; }
.rv-job-fit { font-size: 1.5rem; font-weight: 900; line-height: 1.2; font-variant-numeric: tabular-nums; }
.rv-job-fit small { font-size: .8rem; font-weight: 600; color: var(--text3); }
.rv-job-medals { font-size: .85rem; margin: .25rem 0 .4rem; display: flex; gap: .45rem; flex-wrap: wrap; }
.rv-job-top { font-size: .76rem; color: var(--text3); }
.rv-job-note { font-size: 0.75rem; color: var(--text3); margin-top: .3rem; }
.rv-job-warn { font-size: .8rem; color: var(--warning); }

.rv-runby { display: flex; gap: .85rem; align-items: flex-start; }
.rv-runby-face { width: 52px; height: 52px; border-radius: 50%; object-fit: cover; border: 2px solid var(--border); flex: 0 0 auto; }
.rv-runby-initials { width: 52px; height: 52px; border-radius: 50%; background: var(--bg3); color: var(--text2); flex: 0 0 auto;
                     display: flex; align-items: center; justify-content: center; font-weight: 800; }
.rv-runby-name { font-size: .78rem; color: var(--text3); margin-top: -.2rem; }

.rv-toolbar { display: flex; gap: .5rem; flex-wrap: wrap; }
.rv-toolbar .btn, .rv-toolbar .form-control, .rv-toolbar .form-select { min-height: 44px; }
.rv-inline-form { display: flex; gap: .4rem; align-items: center; }
.rv-open-count { max-width: 96px; }
.rv-foot { display: flex; gap: 1rem; flex-wrap: wrap; align-items: end; justify-content: space-between; }
.rv-foot form { display: flex; gap: .5rem; align-items: end; }
.rv-foot .btn { min-height: 44px; }
.rv-danger-label { color: var(--danger); }
.rv-pile-empty { text-align: center; color: var(--text3); padding: 1.5rem; }
.rv-cv-edit { position: relative; }
.rv-cv-edit > summary { list-style: none; cursor: pointer; min-height: 44px; display: inline-flex; align-items: center; }
.rv-cv-edit > summary::-webkit-details-marker { display: none; }
.rv-cv-edit form { display: flex; flex-direction: column; gap: .4rem; margin-top: .5rem; padding: .6rem;
                   border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }
.rv-cv-del { margin: 0; margin-inline-start: auto; }
.rv-cv-del .btn { background: color-mix(in srgb, var(--danger) 10%, transparent); color: var(--danger);
                  border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent); }

/* Small shared helpers the ReVeRa surface needed and every page can use. */
.is-quiet { color: var(--text3); }
.ui-self-center { align-self: center; }
.ui-jstep-done { font-size: 0.75rem; color: var(--rv-mark); font-weight: 800; margin-inline-start: .4rem; }

.rv-h { font-size: 1rem; font-weight: 800; margin: 0 0 .2rem; }
.rv-h3 { font-size: .85rem; font-weight: 700; margin-top: 1.1rem; }
.rv-sub { font-size: .8rem; color: var(--text3); margin: .15rem 0 .9rem; line-height: 1.5; }
.rv-note { font-size: .85rem; color: var(--text2); }
.rv-done-note { font-size: .85rem; color: var(--success); font-weight: 700; }
.rv-block { padding: 1.25rem; margin-bottom: 1.25rem; }
.rv-block-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .6rem; }
.rv-head-act { margin-inline-start: auto; min-height: 44px; display: inline-flex; align-items: center; }
.rv-console-card { padding: 1.1rem 1.3rem; margin-bottom: 1.25rem; }
.rv-crit-fold > summary { font-size: .85rem; font-weight: 700; }
.rv-crit-body { padding: .75rem 0 0; }
.rv-row-act { margin: .4rem 0 .2rem; }
.rv-addform { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: .6rem; align-items: end; }
.rv-addform .btn, .rv-addform .form-control, .rv-addform .form-select { min-height: 44px; }
.rv-check { display: flex; gap: .5rem; align-items: center; min-height: 44px; font-size: .85rem; }
.rv-check input { width: 18px; height: 18px; }
.rv-release { border-color: var(--success); color: var(--success); }

/* ── ReVeRa index: the brand face, the door in, the engagement cards ──────
   Lifted out of engagements/index.blade.php so the dashboard and the intake
   screens can wear the same brand instead of each growing their own. */
.rv-hero { position: relative; border-radius: var(--radius-lg); overflow: hidden; background: var(--bg2);
           border: 1px solid var(--border); margin-bottom: 1.25rem; box-shadow: var(--shadow); }
.rv-hero .rv-top { height: 7px; background: var(--rv-mark); }
.rv-hero .rv-mid { padding: 1.9rem 1.25rem 1.6rem; text-align: center;
                   background: radial-gradient(55% 120% at 50% 45%, rgba(245,158,11,.18), transparent 70%); }
.rv-hero .rv-name { font-size: 2.3rem; font-weight: 900; letter-spacing: .04em; line-height: 1; margin: 0;
                    color: var(--text); text-shadow: 0 0 18px rgba(245,158,11,.85), 0 0 42px rgba(245,158,11,.4); }
.rv-hero .rv-tag { font-size: .95rem; color: var(--text2); margin-top: .45rem; font-weight: 600; }
.rv-hero .rv-bottom { height: 7px; background-color: color-mix(in srgb, var(--step-waiting) 16%, transparent);
                      background-image: repeating-linear-gradient(90deg, var(--step-waiting) 0 7px, transparent 7px 13px); }

.rv-new { margin-bottom: 1.5rem; }
.rv-new > summary { list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: .6rem;
                    min-height: 58px; border-radius: var(--radius-lg); font-size: 1.08rem; font-weight: 800;
                    background: var(--accent); color: var(--accent-ink, #fff); box-shadow: var(--shadow-md); transition: transform .12s; }
.rv-new > summary::-webkit-details-marker { display: none; }
.rv-new > summary:hover { transform: translateY(-1px); }
.rv-new[open] > summary { border-end-start-radius: 0; border-end-end-radius: 0; }
.rv-new-body { padding: 1.25rem; border-start-start-radius: 0; border-start-end-radius: 0; }
.rv-new-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: .75rem; align-items: end; }
.rv-new-form .btn, .rv-new-form .form-control, .rv-new-form .form-select { min-height: 44px; }
@media (prefers-reduced-motion: reduce) { .rv-new > summary { transition: none; } .rv-new > summary:hover { transform: none; } }

.rv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 1rem; margin-bottom: 1rem; }
.rv-card { border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; background: var(--bg2);
           display: flex; flex-direction: column; box-shadow: var(--shadow); }
.rv-card .rv-strip { height: 5px; }
.rv-strip-intake { background-color: color-mix(in srgb, var(--step-waiting) 16%, transparent);
                   background-image: repeating-linear-gradient(90deg, var(--step-waiting) 0 7px, transparent 7px 13px); }
.rv-strip-scoring { background: linear-gradient(90deg, var(--warning), #fbbf24); }
.rv-strip-ready { background: var(--rv-mark); }
.rv-strip-archived { background: var(--border); }
.rv-card .rv-body { padding: 1rem 1.1rem; flex: 1; }
.rv-card .rv-client { font-size: 1.05rem; font-weight: 800; }
.rv-card .rv-role { font-size: .82rem; color: var(--text3); margin-top: .1rem; }
.rv-card .rv-meta { display: flex; gap: .45rem; flex-wrap: wrap; margin-top: .7rem; font-size: .76rem; }
.rv-card .rv-chip { background: var(--bg3); border-radius: 999px; padding: .2rem .6rem; font-weight: 700; color: var(--text2); }
.rv-card .rv-open { display: flex; align-items: center; justify-content: center; min-height: 46px; font-weight: 800;
                    border-top: 1px solid var(--border); color: var(--accent); }

/* Settings & the public intake door — the plumbing, behind disclosures. */
.rv-fold { margin-top: 1.25rem; }
.rv-fold > summary { cursor: pointer; font-weight: 800; font-size: .95rem; min-height: 44px; display: flex; align-items: center; }
.rv-sec { border-top: 1px solid var(--border); margin: 1rem 0 .9rem; padding-top: .9rem; font-weight: 700; font-size: .85rem; }
.rv-check-block { display: flex; gap: .6rem; align-items: flex-start; min-height: 44px; cursor: pointer; max-width: 64ch; }
.rv-check-block + .rv-check-block { margin-top: .7rem; }
.rv-check-block input[type="checkbox"] { width: 22px; height: 22px; flex: none; margin-top: .2rem; }
.rv-check-block small { color: var(--text3); }
.rv-help { font-size: .76rem; color: var(--text3); margin-top: .25rem; }
.rv-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--min, 240px), 1fr)); gap: .8rem; }
.rv-cols-wide { --min: 260px; }
.rv-cols-narrow { --min: 200px; }
.rv-num { max-width: 140px; }
.rv-colour { max-width: 110px; height: 46px; padding: 4px; }
.rv-intake-summary { display: flex; align-items: center; gap: .7rem; padding: 1rem 1.1rem; cursor: pointer; list-style: none; min-height: 44px; }
.rv-intake-summary::-webkit-details-marker { display: none; }
.rv-intake-summary > span:nth-child(2) { flex: 1; min-width: 0; }
.rv-links { list-style: none; padding: 0; margin: 0 0 .9rem; font-size: .86rem; }
.rv-links li { padding: .3rem 0; }
.rv-actions-row { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }
.rv-actions-row .btn { min-height: 44px; }
.rv-intake-body { border-top: 1px solid var(--border); }
.rv-intake-body .flash { margin-bottom: .9rem; }
.rv-price-list { font-variant-numeric: tabular-nums; }

/* The compact brand strip — the hero's small sibling, on every ReVeRa page. */
.rv-band { position: relative; border-radius: var(--radius-lg); overflow: hidden; background: var(--bg2);
           border: 1px solid var(--border); margin-bottom: 1rem; box-shadow: var(--shadow); }
.rv-band-top { height: 5px; background: var(--rv-mark); }
.rv-band-mid { padding: .8rem 1.1rem; display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
               background: radial-gradient(55% 130% at 50% 45%, rgba(245,158,11,.15), transparent 70%); }
.rv-band-name { font-weight: 900; font-size: 1.1rem; letter-spacing: .03em; text-shadow: 0 0 14px rgba(245,158,11,.8); }
.rv-band-sub { font-size: .82rem; color: var(--text2); }
.rv-band-acts { margin-inline-start: auto; display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.rv-band-bottom { height: 5px; background-color: color-mix(in srgb, var(--step-waiting) 16%, transparent);
                  background-image: repeating-linear-gradient(90deg, var(--step-waiting) 0 7px, transparent 7px 13px); }

/* The candidate card's internals. Only the medal colour and the bar width
   stay inline — those are DATA (this person's score), not styling. */
.rv-cv-top { display: flex; align-items: flex-start; gap: .6rem; }
.rv-cv-lead { flex: none; padding-top: .2rem; }
.rv-cv-who { flex: 1; min-width: 0; }
.rv-cv-name { font-weight: 800; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-cv-name a { color: inherit; text-decoration: none; }
.rv-cv-id { font-size: 0.75rem; color: var(--text3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-cv-medal { font-size: 1.5rem; line-height: 1; }
.rv-cv-score { display: flex; align-items: baseline; gap: .5rem; margin-top: .55rem; flex-wrap: wrap; }
.rv-cv-num { font-size: 1.45rem; font-weight: 900; }
.rv-cv-of { font-size: 0.75rem; color: var(--text3); }
.rv-cv-state { font-size: .8rem; color: var(--text3); }
.rv-cv-state.is-bad { color: var(--danger); }
.rv-cv-advice { font-size: 0.75rem; color: var(--text3); flex-basis: 100%; }
.rv-cv-context { font-size: 0.75rem; color: var(--text3); margin-top: .4rem; }
.rv-cv-chips { display: flex; gap: .35rem; flex-wrap: wrap; margin-top: .5rem; }
.rv-cv-chip { font-size: 0.75rem; font-weight: 700; border-radius: 999px; padding: .15rem .55rem; white-space: nowrap; }
.rv-cv-extra { margin-top: .5rem; }

/* The JD reader uses the same console as the CV reader, in green — the two
   panels are one component with two subjects, not two panels. */
.rv-console-screen.is-jd { box-shadow: inset 0 0 40px rgba(74,222,128,.06); }
.rv-console-out.is-jd { color: var(--rv-screen-jd); min-height: 7.5rem; max-height: 11rem; }
.rv-console-track.is-jd > span { background: linear-gradient(90deg, var(--success), color-mix(in srgb, var(--success) 45%, #fff)); }

.rv-jd { padding: 1.1rem 1.3rem; margin-bottom: 1rem; }
.rv-jd-head { display: flex; align-items: flex-start; gap: .8rem; flex-wrap: wrap; margin-bottom: .85rem; }
.rv-jd-head-text { flex: 1; min-width: 240px; }
.rv-jd-head h3 { margin: 0 0 .2rem; font-size: 1rem; font-weight: 800; }
.rv-jd-head p { margin: 0; font-size: .85rem; color: var(--text2); line-height: 1.55; }
.rv-jd-drop { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .35rem;
              border: 2px dashed var(--border); border-radius: var(--radius-lg); padding: 1.6rem 1rem; cursor: pointer;
              background: var(--bg2); text-align: center; transition: border-color .15s, background .15s; }
.rv-jd-drop.is-hot { border-color: var(--success); background: var(--bg3); }
.rv-jd-drop-icon { font-size: 1.8rem; line-height: 1; }
.rv-jd-drop-title { font-weight: 700; font-size: .92rem; }
.rv-jd-drop-types { font-size: .76rem; color: var(--text3); }
.rv-jd-run { margin-top: 1rem; }
.rv-jd-list { margin-top: .9rem; font-size: .85rem; }
.rv-jd-doc { display: flex; align-items: center; gap: .6rem; padding: .45rem 0; border-top: 1px solid var(--border); }
.rv-jd-doc-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-jd-doc-state { color: var(--text3); font-size: .78rem; white-space: nowrap; }
.rv-jd-doc-why { font-size: .76rem; color: var(--danger); padding: 0 0 .4rem 1.7rem; }
.rv-jd-none { color: var(--text3); padding: .5rem 0; }
@media (prefers-reduced-motion: reduce) { .rv-jd-drop, .rv-drop { transition: none; } }

/* The JD review panel — one framed card per document, one framed row per
   proposal. The row's confidence is a MODIFIER, not a colour written inline:
   is-vocab = the reader recognised it (amber, the sun); is-required = a
   must-have (the welded red edge); is-unheaded = a line with nothing to
   anchor it (dashed, and unticked by default). */
.jd-docs { display: flex; flex-direction: column; gap: 1rem; }
.jd-doc { border: 2px solid var(--border); border-radius: var(--radius-lg); background: var(--bg2); overflow: hidden; box-shadow: var(--shadow); }
.jd-doc-head { display: flex; align-items: center; gap: .6rem; padding: .7rem 1rem; border-bottom: 1px solid var(--border);
               background: var(--bg3); flex-wrap: wrap; }
.jd-doc-head strong { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jd-doc-icon { font-size: 1.1rem; }
.jd-doc-err { font-size: .75rem; color: var(--danger); }
.jd-doc-words { font-size: 0.75rem; color: var(--text3); }
.jd-doc-count { font-size: 0.75rem; font-weight: 800; background: var(--bg2); border: 1px solid var(--border);
                border-radius: 999px; padding: .15rem .6rem; }
.jd-doc-empty { padding: .9rem 1rem; font-size: .82rem; color: var(--text3); }
.jd-doc-rows { display: flex; flex-direction: column; gap: .55rem; padding: .8rem 1rem; }

.jd-prop { border: 1.5px solid var(--border); border-radius: 11px; padding: .6rem .8rem; background: var(--bg); }
.jd-prop.is-unheaded { border-style: dashed; }
.jd-prop.is-vocab { background: linear-gradient(90deg, rgba(245,158,11,.14), transparent 60%); }
.jd-prop.is-required { border-color: rgba(185,28,28,.45); border-inline-start: 5px solid var(--rv-mark); }
.jd-prop.is-taken { opacity: .5; }
.jd-prop-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.jd-tick { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px;
           flex: none; margin: -.25rem 0 -.25rem -.35rem; cursor: pointer; }
.jd-tick input { width: 22px; height: 22px; }
.jd-prop-value { flex: 1; min-width: 150px; font-weight: 600; }
.jd-prop.is-vocab .jd-prop-value { font-size: .98rem; font-weight: 700; }
.jd-tag { font-size: 0.75rem; font-weight: 800; border-radius: 999px; padding: .15rem .55rem; white-space: nowrap; }
.jd-tag-vocab { background: rgba(245,158,11,.2); color: var(--warning-ink-2); }
.jd-tag-unheaded { background: var(--bg3); color: var(--text3); }
.jd-tag-must { background: rgba(185,28,28,.12); color: var(--rv-mark); }
.jd-already { font-size: 0.75rem; color: var(--text3); }
.jd-weight { padding: .3rem .45rem; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); font: inherit; font-size: .82rem; }
.jd-star { min-width: 44px; min-height: 44px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg);
           cursor: pointer; font-size: 1rem; }
.jd-star.is-on { border-color: var(--warning); }
.jd-quote { font-size: .76rem; color: var(--text3); margin: .35rem 0 0 .1rem; font-style: italic; }
.jd-quote span { white-space: nowrap; }

.jd-pick { border-top: 1px dashed var(--border); }
.jd-pick > summary { cursor: pointer; padding: .6rem 1rem; font-size: .82rem; font-weight: 700;
                     min-height: 44px; display: flex; align-items: center; gap: .5rem; }
.jd-pick-n { font-weight: 500; color: var(--text3); }
.jd-pick-body { padding: 0 1rem .9rem; }
.jd-pick-hint { font-size: .76rem; color: var(--text3); margin: .2rem 0 .6rem; }
.jd-pick-lines { display: flex; flex-direction: column; gap: .2rem; max-height: 340px; overflow: auto;
                 border: 1px solid var(--border); border-radius: var(--radius); padding: .4rem; }
.jd-pick-line { display: flex; align-items: flex-start; gap: .55rem; padding: .35rem .4rem; border-radius: 8px;
                min-height: 44px; cursor: pointer; }
.jd-pick-line input { width: 18px; height: 18px; margin-top: .15rem; flex: none; }
.jd-pick-line span { font-size: .82rem; line-height: 1.35; }
.jd-bar { display: flex; gap: .6rem; margin-top: 1rem; flex-wrap: wrap; align-items: center; }
.jd-bar .btn { min-height: 44px; }
.jd-bar .btn-primary { min-height: 50px; font-weight: 800; font-size: .95rem; }
.rv-funnel-fill { background: var(--accent); border-radius: 999px; }
.rv-roles { margin-top: .6rem; }
.rv-roles a { color: inherit; }
.rv-jstep-go { margin-top: .5rem; display: inline-flex; align-items: center; gap: .35rem; min-height: 44px; }

/* ── THE SITE-EDITOR TEMPLATE CHOOSER ─────────────────────────────────────
   Seven cards, each a look the business can try. The card that is in use is
   edged in the success colour rather than filled — a wall of colour on a
   chooser reads as an alarm, and the point here is calm comparison. */
.ed-tpls { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: .875rem; margin-top: 1rem; }
.ed-tpl { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg2);
          padding: .9rem 1rem; display: flex; flex-direction: column; gap: .4rem; }
.ed-tpl.is-on { border-inline-start: 3px solid var(--success); }
.ed-tpl-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.ed-tpl-icon { font-size: 1.1rem; color: var(--text3); }
.ed-tpl-name { font-size: .9375rem; }
.ed-tpl-blurb { font-size: .8125rem; color: var(--text2); line-height: 1.5; flex: 1; margin: 0; }
.ed-tpl-acts { display: flex; gap: .4rem; flex-wrap: wrap; margin-top: .25rem; }
.ed-tpl-acts .btn { min-height: 44px; }
.ed-tpl-revert { margin-top: 1rem; }
.ed-tpl-revert .btn { min-height: 44px; }

/* ── SPACING UTILITIES — A DOZEN, NOT A FRAMEWORK ─────────────────────────
   A census of the admin views found the same handful of one-declaration
   inline styles written out hundreds of times: `margin:0` on a field inside a
   row, `margin-bottom:0` on the last control in a group, a small top margin
   before a note. Ninety-one of them on the Site Editor alone.

   These are NOT a utility framework and must not grow into one — no colour
   utilities, no display utilities, no numeric scale. They are the four
   spacing moves this platform's own rhythm actually makes, named, so the same
   declaration is not retyped into every view and so a page can be read
   without its layout being buried in attributes.

   Anything beyond these belongs in a component. */
.u-flush   { margin: 0 !important; }
.u-flush-b { margin-bottom: 0 !important; }
.u-gap-xs  { margin-top: .4rem; }
.u-gap-sm  { margin-top: .6rem; }
.u-gap     { margin-top: .85rem; }
.u-gap-lg  { margin-top: 1.25rem; }

/* A field showing a value the operator cannot change here. Muted, not
   disabled-looking: it is information, not a control somebody broke. */
.form-control.is-readonly { background: var(--bg3); color: var(--text3); }

/* The Site Editor's corner-style chooser. Its surface used to be a literal
   #fff written inline, which is the same dark-mode fault as the shared
   partial's missing --surface token, one page further down. */
.ed-corner { cursor: pointer; border: 2px solid var(--border); border-radius: var(--radius);
             padding: .85rem; background: var(--bg2); display: flex; flex-direction: column;
             align-items: center; gap: .45rem; transition: border-color .15s, background .15s; text-align: center; }
.ed-corner.on { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .ed-corner { transition: none; } }

/* ── SITE-EDITOR LAYOUT PIECES ────────────────────────────────────────────
   The shapes this editor actually repeats, named once. Lifted out of the
   template after a census found the same six declarations written out in
   full dozens of times — which is how a 2,400-line view becomes unreadable
   and how one of them silently drifts from the others. */
.ed-row { display: flex; gap: .85rem; align-items: flex-start; flex-wrap: wrap; }
.ed-row > .mod-field { margin-bottom: 0; }
.ed-side { display: grid; grid-template-columns: 140px 1fr; gap: 1rem; align-items: start; }
@media (max-width: 560px) { .ed-side { grid-template-columns: 1fr; } }
.ed-h3 { font-weight: 700; margin: 1.25rem 0 .6rem; }
.ed-narrow { width: auto; }
.ed-fixed { margin-bottom: 0; flex: 0 0 auto; }
.ed-swatch { width: 80px; height: 42px; padding: 4px; cursor: pointer; }
.ed-thumb { position: relative; margin-bottom: .5rem; border-radius: var(--radius); overflow: hidden; aspect-ratio: 4/3; }
.ed-live { background: var(--success); color: var(--accent-ink, #fff); font-size: 0.75rem; padding: .2rem .55rem;
           border-radius: 999px; margin-inline-start: .5rem; font-weight: 700; letter-spacing: .05em; }
.u-gap-1 { margin-top: 1rem; }
.u-gap-b { margin-bottom: .85rem; }
.ed-side-wide { display: grid; grid-template-columns: 240px 1fr; gap: 1rem; align-items: start; }
@media (max-width: 640px) { .ed-side-wide { grid-template-columns: 1fr; } }
.ed-grow { flex: 1; min-width: 240px; }
.ed-grow-sm { flex: 1; min-width: 200px; }
.ed-label { font-size: .75rem; font-weight: 600; color: var(--text2); display: block; margin-bottom: .3rem; }
.ed-add { grid-column: 1 / -1; background: transparent; color: var(--text3); border: 1px dashed var(--border); }
.ed-tall { min-block-size: 120px; }
.ed-tall-sm { min-block-size: 100px; }
.ed-num-sm { width: 90px; }
.u-gap-half { margin-top: .5rem; }
.u-gap-b1 { margin-bottom: 1rem; }

/* ── THE FOUR ROLES THE CENSUS FOUND ──────────────────────────────────────
   A count of every inline style on the platform (14,691 declarations across
   743 views) showed the debt is not a few bad pages — the top forty views
   hold only 26% of it. It is four ROLES, written out by hand thousands of
   times:

     color:var(--text3)                     2,087×   a caption / hint / meta line
     display:flex + align-items + gap       1,541×   a row of things
     font-weight:700/800                    2,190×   emphasis
     .7rem + uppercase + 700 + --text3        672×   a small field label

   Naming the ROLE is the fix, not adding a utility for every property. These
   are the sentences this platform's screens actually say; anything that is
   not one of them belongs in a component, and this list must not grow into a
   utility framework. */

/* A caption, a hint, a meta line — the second, quieter sentence. */
.u-meta { font-size: .8125rem; color: var(--text3); line-height: 1.5; }
.u-meta-sm { font-size: .75rem; color: var(--text3); line-height: 1.45; }
.u-muted { color: var(--text3); }
.u-quiet { color: var(--text2); }

/* A small label over a value. Uppercase because it is a LABEL, not prose. */
.u-eyebrow { font-size: 0.75rem; font-weight: 700; text-transform: uppercase;
             letter-spacing: .04em; color: var(--text3); }

/* A row of things that sit on one line and wrap rather than overflow. */
.u-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.u-row-top { display: flex; align-items: flex-start; gap: .5rem; flex-wrap: wrap; }
.u-fill { flex: 1; }
.u-fill-0 { flex: 1; min-width: 0; }

/* A machine value shown to a person — an id, a hash, a hostname. Monospace
   and always left-to-right, because reordering a token misquotes it. */
.u-mono { font-family: ui-monospace, 'SF Mono', Menlo, monospace; }

/* The few alignment and sizing moves worth a name. */
.u-center { text-align: center; }
.u-end { text-align: end; }
.u-w { width: 100%; }
.u-tap { min-height: 44px; }
.u-mb { margin-bottom: 1rem; }
.u-mb-lg { margin-bottom: 1.25rem; }

/* ── A CLOSED DISCLOSURE MUST SHOW NOTHING BUT ITS SUMMARY ────────────────
   The browser hides a closed <details>'s body through a UA rule that ANY
   explicit `display` on that body overrides. So every disclosure whose panel
   is a flex column — which is most of them, because a panel is a stack of
   things — was rendering its whole content while closed.

   MEASURED, not suspected: /admin/users at 390px was 18,766px tall for 26
   users. All 26 rows' "More" menus were laid out in the flow with the
   disclosure shut, at 981px each. Twenty-two phone screens of scrolling to
   read twenty-six names, and the progressive disclosure the page was built
   around did nothing at all.

   This is the same defect as `[hidden]` losing to `display:flex`, which this
   platform already fixed once with a global rule. One line, and every
   disclosure on every screen starts collapsing for real. */
details:not([open]) > *:not(summary) { display: none !important; }

/* ─── THREE MORE ROLES, FOUND BY COUNTING THE DETAIL PAGES ────────────────
   The ten biggest record pages carried 1,150 inline declarations between
   them, 705 of them distinct — which reads as 705 decisions and is not. It
   is a handful of ROLES typed over and over with drift: the same small
   uppercase label at .66, .7, .74, .75, .76, .8, .86 and .88rem, on eight
   different screens, because each was tuned by eye in isolation. Naming the
   role is what stops the drift; a utility named after a pixel value would
   just be an inline style with extra steps, so the bare font-sizes were
   left alone rather than renamed. */
.u-body-sm { font-size: .875rem; color: var(--text2); line-height: 1.5; }
/* A cell that takes the whole row of a grid — a note, a total, a wide table. */
.u-span { grid-column: 1 / -1; }
/* A button that fills its column, centred: the phone shape of a primary action. */
.u-btn-block { width: 100%; justify-content: center; }

/* A FILTER ROW INSIDE A CARD HEADER.
   Written inline on the activity log as two fixed pixel widths plus a button
   — 405px of controls inside a 390px screen, so the page scrolled sideways.
   A shared shape instead: the fields share the row on a desktop and wrap to
   full width on a phone, and nothing carries a width the screen cannot hold. */
.ui-inline-filter { display: flex; gap: .5rem; flex-wrap: wrap; min-inline-size: 0; }
.ui-inline-filter .form-control { flex: 1 1 10rem; min-inline-size: 0; }
.ui-inline-filter .btn { flex: 0 0 auto; min-height: 40px; }
@media (max-width: 560px) {
  .ui-inline-filter { inline-size: 100%; }
  .ui-inline-filter .form-control { flex: 1 1 100%; }
  .ui-inline-filter .btn { inline-size: 100%; }
}

/* ─── A COMMAND WITH A COPY BUTTON ────────────────────────────────────────
   Written three times as inline styles, and in all three the button was
   absolutely positioned over the text: on the backup screen it covered
   "cd /var/ww…", so the operator could not read the command it was offering
   to copy. On an Arabic page it was worse — `right` put it at the visual
   START of an LTR command.

   One shape instead: the block reserves room for the button, the command is
   forced LTR and isolated so no bidi run reorders a flag, and the button sits
   at the block's end whichever way the page runs. */
.code-copy-wrap {
  position: relative;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .75rem;
  padding-inline-end: 4.5rem;          /* the button's own room */
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: max(.75rem, var(--type-floor, .75rem));
  color: var(--accent);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  direction: ltr;
  unicode-bidi: isolate;
  text-align: start;
}
.code-copy-btn {
  position: absolute;
  top: .375rem;
  inset-inline-end: .375rem;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: .3rem .6rem;
  min-height: 32px;
  font-size: 0.75rem;
  font-family: inherit;
  cursor: pointer;
  color: var(--text2);
}
.code-copy-btn:hover { border-color: var(--accent); color: var(--text); }
.code-copy-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ─── TOASTS ───────────────────────────────────────────────────────────────
   The platform's non-blocking feedback. "Saved", "Copied", "Email sent" used
   to be a coloured bar at the TOP of the page: it pushed the content down,
   stayed until the next navigation, and on a phone was often scrolled past
   before it was read — the old-admin look the owner named.

   Bottom-centre on a phone so it is near the thumb and clear of the header;
   bottom-end on a desktop where the corner is free. Never over the primary
   action: the host is a column that grows upward, and the page reserves its
   own bottom gutter (--floating-gutter in admin.css). */
.byar-toasts {
  position: fixed;
  z-index: 1400;
  bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
  inset-inline-end: 1rem;
  display: flex;
  flex-direction: column-reverse;
  gap: .5rem;
  max-inline-size: min(26rem, calc(100vw - 2rem));
  pointer-events: none;
}
@media (max-width: 560px) {
  .byar-toasts {
    inset-inline: 1rem;
    max-inline-size: none;
    align-items: stretch;
  }
}

.byar-toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: .625rem;
  padding: .75rem .875rem;
  border-radius: var(--radius);
  background: var(--bg2);
  color: var(--text);
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--text3);
  box-shadow: 0 10px 30px -10px rgba(15, 23, 42, .45);
  font-size: .875rem;
  line-height: 1.45;
  animation: byar-toast-in .22s ease both;
}
.byar-toast.is-going { animation: byar-toast-out .2s ease both; }

/* Colour is the state, and the word is always there too. */
.byar-toast.is-success { border-inline-start-color: var(--success); }
.byar-toast.is-error   { border-inline-start-color: var(--danger); }
.byar-toast.is-warning { border-inline-start-color: var(--warning); }
.byar-toast.is-info    { border-inline-start-color: var(--accent); }

.byar-toast-text { flex: 1; min-inline-size: 0; overflow-wrap: anywhere; }
.byar-toast-x {
  flex: 0 0 auto;
  background: none;
  border: 0;
  color: var(--text3);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: .25rem;
  min-inline-size: 32px;
  min-block-size: 32px;
  border-radius: 6px;
}
.byar-toast-x:hover { color: var(--text); background: var(--bg3); }
.byar-toast-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

@keyframes byar-toast-in  { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes byar-toast-out { to { opacity: 0; transform: translateY(6px); } }

@media (prefers-reduced-motion: reduce) {
  .byar-toast, .byar-toast.is-going { animation: none; }
}

/* ── TITLES STAY READABLE THROUGH THE TABLET BAND TOO ──────────────────────
   The rules above live in the 560px block, so between 561px and the desktop
   layout a list title went back to being ellipsised by a sibling: measured at
   768px, "Application cache" had 57px of the 113px it needs. The band matters
   because it is a tablet held in the hand, not a desk. Desktop is unchanged —
   measured clean at 1280px — so the floor stops there. */
@media (max-width: 900px) {
  .ui-entity-title, .ui-list-title {
    min-inline-size: 0;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    overflow-wrap: break-word;
    flex: 1 1 auto;
  }
  .ui-list-item { flex-wrap: wrap; }
}

/* ── A DESCRIPTION IS A SENTENCE, SO IT WRAPS ─────────────────────────────
   Capping the activity log's description at a fixed pixel width and
   ellipsising it hid the end of every explanation longer than the cap — on
   the screen whose entire job is saying what happened. A readable measure
   (about 70 characters) keeps the column from sprawling across a wide
   monitor without amputating the sentence. */
.ui-activity-description {
  max-inline-size: 70ch;
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.45;
}

/* ── QUICK ACTIONS ────────────────────────────────────────────────────────
   The fourth question a control plane answers — "what can I do now" — and the
   one the dashboard never answered: an owner had to know which sidebar entry
   held the thing he wanted. A tile is a target first: 44px minimum, the whole
   surface tappable, and it wraps to a single readable column on a phone rather
   than shrinking its own label. */
.ui-quick-actions {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: .625rem;
}
.ui-quick-action {
  display: flex; align-items: center; gap: .625rem;
  min-block-size: 56px;
  padding: .75rem .875rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg2);
  color: var(--text);
  text-decoration: none;
  box-shadow: var(--shadow-sm, 0 1px 2px rgba(15, 23, 42, .04));
  transition: background .15s ease, border-color .15s ease, transform .15s ease;
}
.ui-quick-action:hover {
  background: var(--bg3);
  border-color: var(--accent);
  opacity: 1;
}
.ui-quick-action:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The primary action is the one that CREATES something. Exactly one tile
   carries it, because two primaries is none. */
.ui-quick-action.is-primary {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg2));
  border-color: var(--accent);
  font-weight: 600;
}
.ui-quick-action-icon { font-size: 1.25rem; line-height: 1; flex: 0 0 auto; }
.ui-quick-action-label { font-size: .875rem; min-inline-size: 0; overflow-wrap: break-word; }
@media (prefers-reduced-motion: reduce) { .ui-quick-action { transition: none; } }
/* TWO ACROSS ON A PHONE. One per row turned ten starting points into a
   screen and a half of scrolling between "what needs me" and the estate;
   a 56px tile with a short label reads comfortably at half of 390px, and a
   long Kurdish label wraps inside its own tile rather than widening it. */
@media (max-width: 480px) {
  .ui-quick-actions { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; }
  .ui-quick-action { padding: .625rem .75rem; }
}

/* The tenant code is a reference an operator quotes, not the business's name.
   It sits under the name, so it is dimmed and sized down to stop it competing
   with the thing it identifies. */
.ui-entity-code { color: var(--text3); letter-spacing: .02em; }

/* ── DEPTH SAYS WHAT A SURFACE IS ─────────────────────────────────────────
   Everything was outlined and flat at the same elevation, so a card, a card
   you are pointing at, a drawer and a modal all sat on the same plane and the
   page read as a wireframe. The four steps in admin.css say four different
   things; this is where the shared primitives use them.

   Restraint is the point: nothing gains a shadow it did not have, the resting
   ones get a SOFTER and more correct one, and only the things that are
   genuinely above the page get a large one. */
.ui-panel,
.ui-kpi { box-shadow: var(--shadow-sm); }

/* A card that is a link is a target, so it lifts when pointed at — the cue
   that it can be pressed, at a weight that does not make the page jump. */
.ui-panel a.ui-entity-main:hover,
.ui-entity:hover,
.ui-quick-action:hover { box-shadow: var(--shadow-md); }

/* Over the page. */
.presence-panel { box-shadow: var(--shadow-lg); }

/* Over everything. */
.byar-dialog,
.ui-drawer { box-shadow: var(--shadow-xl); }

@media (prefers-reduced-motion: reduce) {
  .ui-entity, .ui-quick-action { transition: none; }
}

/* ACCENT USED AS TEXT ON A SURFACE reads through --accent-on-surface: the same
   colour in light mode, a lifted one in dark. Fills, borders and bars keep
   --accent, because a fill does not have to clear a contrast bar and text
   does. (--accent-ink is a different token meaning the ink ON an accent fill.)

   NARROW ON PURPOSE. A first attempt matched every link inside a panel, which
   is most of the platform — and it captured rows like .ui-slitem-row, whose
   whole design is `color: inherit` so the row takes the tone of the state it
   reports. Repainting those broke the status list on every screen that has
   one. A shared token is only shared where the role is actually the same. */
.ui-details > summary,
.ui-link { color: var(--accent-on-surface, var(--accent)); }

/* ── SEMANTIC INK, EVERYWHERE THE SEMANTIC COLOUR IS TEXT ─────────────────
   Badge labels, status words and figures read through the *-on-surface
   tokens, which are the same hues stepped to be readable as text. Anything
   that is a FILL — a bar, a dot, a border, a left edge — keeps the original
   token, because a fill has no contrast bar to clear and changing it would
   repaint the whole platform for no gain. */
.ui-badge.is-critical,
.ui-slitem.is-critical .ui-slitem-status,
.ui-status-critical { color: var(--danger-on-surface, var(--danger)); }

.ui-slitem.is-warning .ui-slitem-status,
.ui-status-warning { color: var(--warning-on-surface, var(--warning)); }

.ui-slitem.is-good .ui-slitem-status,
.ui-status-good { color: var(--success-on-surface, var(--success)); }

/* AN INLINE LINK IN PROSE IS ACCENT-COLOURED TEXT, so it reads through the
   same ink token as every other accent-as-text role. Scoped to the admin
   content, and excluding anything that is a button or already carries its own
   colour — a row whose whole design is `color: inherit` must keep inheriting,
   which is the mistake the first attempt at this made. */
.admin-content a:not(.btn):not([class*="ui-slitem"]):not([class*="ui-entity"]):not(.ui-quick-action):not([class*="nav-"]):not(.adm-band-exit):not([class*="mod-btn"]) {
  color: var(--accent-on-surface, var(--accent));
}

/* ── A FAULT CARD ─────────────────────────────────────────────────────────
   Severity, what happened and whether it is still happening on one row; the
   facts as a compact definition grid; the meaning and the action as
   sentences; the machine's own words behind a disclosure. Wraps to a single
   column on a phone rather than compressing any of it. */
.ui-fault { padding: .9rem 1rem; margin-block-end: .625rem; }
.ui-fault-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin-block-end: .625rem;
}
.ui-fault-title {
  font-size: .95rem; font-weight: 700; margin: 0;
  flex: 1 1 14rem; min-inline-size: 0; overflow-wrap: break-word;
}
.ui-fault-facts {
  display: grid; grid-template-columns: auto 1fr; gap: .2rem .75rem;
  margin: 0 0 .625rem; font-size: .8125rem;
}
.ui-fault-facts dt { color: var(--text3); }
.ui-fault-facts dd { margin: 0; color: var(--text2); font-variant-numeric: tabular-nums; }
.ui-fault-impact, .ui-fault-action { margin: 0 0 .5rem; font-size: .875rem; line-height: 1.5; }
.ui-fault-action { color: var(--text2); }
.ui-fault-raw {
  background: var(--surface-sunken, var(--bg3)); border-radius: var(--radius);
  padding: .625rem .75rem; overflow-x: auto; white-space: pre-wrap;
  overflow-wrap: anywhere; font-size: .75rem; line-height: 1.5;
}
@media (max-width: 480px) {
  .ui-fault-facts { grid-template-columns: 1fr; gap: 0 0; }
  .ui-fault-facts dt { margin-block-start: .35rem; }
}

/* ── PRODUCT RECONCILIATION TABLE ─────────────────────────────────────────
   Six facts per product, one row each. It lives HERE and not on the estate
   page because an owner-console screen carries no stylesheet of its own —
   that rule is what stops the console becoming a dozen private frameworks,
   and it is tested. Any screen needing this table gets it by class name. */
.ui-prodtable-sum { padding: .8rem 1rem; color: var(--text2); font-size: .89rem; line-height: 1.6; border-block-end: 1px solid var(--border); }
.ui-prodtable-wrap { inline-size: 100%; }
.ui-prodtable { inline-size: 100%; border-collapse: collapse; }
.ui-prodtable th, .ui-prodtable td { padding: .6rem .85rem; text-align: start; font-size: .89rem; }
.ui-prodtable thead th { font-size: .75rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text3); border-block-end: 1px solid var(--border); }
.ui-prodtable tbody tr + tr th, .ui-prodtable tbody tr + tr td { border-block-start: 1px solid var(--border); }
.ui-prodtable tbody th { font-weight: 700; }

/* At phone width a six-column table is unreadable however narrow it gets, so
   each row becomes a card and every cell carries its own label. */
@media (max-width: 760px) {
    .ui-prodtable, .ui-prodtable tbody, .ui-prodtable tr, .ui-prodtable th, .ui-prodtable td { display: block; }
    .ui-prodtable thead { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); }
    .ui-prodtable tbody tr {
        border: 1px solid var(--border); border-radius: 10px;
        margin: .6rem 1rem; padding: .3rem 0; background: var(--bg2);
    }
    .ui-prodtable tbody tr + tr th, .ui-prodtable tbody tr + tr td { border-block-start: 0; }
    .ui-prodtable tbody th { font-size: 1rem; padding-block-end: .2rem; }
    .ui-prodtable td { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
    .ui-prodtable td::before { content: attr(data-label); color: var(--text3); font-size: .75rem; text-transform: uppercase; letter-spacing: .05em; }
}

/* ── COMMAND CENTRE: ranked bars, chart doors, the estate table ──────────────
   Every number on the owner's command centre opens the rows behind it, so the
   shapes here are mostly about making that obvious: a whole row is the target
   (≥44px), it says so on hover and focus, and the bar beside the number is a
   comparison, never the only way to read the figure. */
.ui-rank { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .125rem; }
.ui-rank-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .25rem .75rem; align-items: center;
  min-block-size: 44px; padding: .375rem .5rem; border-radius: var(--radius); color: inherit;
}
a.ui-rank-row { text-decoration: none; }
a.ui-rank-row:hover { background: var(--bg3); opacity: 1; }
a.ui-rank-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ui-rank-label { display: flex; align-items: center; gap: .5rem; min-inline-size: 0; font-size: .875rem; color: var(--text); overflow-wrap: anywhere; }
.ui-rank-meta { font-size: .75rem; color: var(--text3); }
.ui-rank-value { font-weight: 650; font-variant-numeric: tabular-nums; font-size: .9375rem; color: var(--text); }
.ui-rank-bar { grid-column: 1 / -1; inline-size: 100%; block-size: 6px; display: block; }
[dir="rtl"] .ui-rank-bar { transform: scaleX(-1); }
.ui-rank-track { fill: var(--bg3); }
.ui-rank-fill { fill: var(--accent); }
.ui-rank-fill.is-good { fill: var(--success); }
.ui-rank-fill.is-warning { fill: var(--warning); }
.ui-rank-fill.is-critical { fill: var(--danger); }
.ui-rank-fill.is-quiet { fill: var(--text3); }

/* A chart mark that opens its rows. */
.ui-chart-link { cursor: pointer; }
.ui-chart-link:hover .ui-chart-bar, .ui-chart-link:focus-visible .ui-chart-bar { opacity: .8; }
.ui-chart-link:focus-visible { outline: 2px solid var(--accent); }

/* The estate table: the business is the link, its code and sector sit under it. */
.ui-cc-name { font-weight: 650; color: var(--text); overflow-wrap: anywhere; }
a.ui-cc-name:hover { color: var(--accent-on-surface, var(--accent)); opacity: 1; }
.ui-cc-sub { display: block; font-size: .75rem; color: var(--text3); margin-block-start: .125rem; }
.ui-cc-flags { display: flex; flex-wrap: wrap; gap: .25rem; }
.ui-cc-rule { font-size: .75rem; color: var(--text3); line-height: 1.5; margin: .5rem 0 0; }
.ui-cc-more { display: flex; flex-wrap: wrap; gap: .5rem; margin-block-start: .75rem; }
.ui-cc-more .btn { min-block-size: 44px; }
@media (max-width: 760px) {
  /* On a phone the row becomes a card; the business name is its title and
     needs no "Business" label beside it. */
  table.table td.ui-cc-first { display: block; padding-block-end: .5rem; border-block-end: 1px solid var(--border); margin-block-end: .25rem; }
  table.table td.ui-cc-first::before { content: none; }
}

/* THE NUMBER IS THE LOUDEST THING ON A KPI CARD — and it was not, anywhere.
   typography.css floors every span inside .admin-body at max(floor, 1em), and
   `.admin-body span` (0,1,1) out-ranks `.ui-kpi-value` (0,1,0): so every KPI
   value on every screen rendered at body size, in the link colour of the card
   around it. Measured on the command centre, Oct 2026: 16px blue where the
   design says 28px ink. Scoped by the card so it out-ranks the floor without
   touching it; a value that is a WORD ("Keeping up", a host name) gets a size
   that wraps cleanly in a narrow card instead of a headline that cannot. */
.ui-kpi .ui-kpi-value { font-size: 1.75rem; color: var(--text); overflow-wrap: anywhere; }
.ui-kpi .ui-kpi-value.is-word { font-size: 1.125rem; line-height: 1.3; }
.ui-kpi .ui-kpi-value.is-muted { font-size: 1rem; color: var(--text3); }
@media (max-width: 480px) {
  .ui-kpi .ui-kpi-value { font-size: 1.375rem; }
  .ui-kpi .ui-kpi-value.is-word, .ui-kpi .ui-kpi-value.is-muted { font-size: 1rem; }
}

/* The control plane's "right now" row: seven figures on one desktop line
   instead of six and an orphan. Two per row on a phone, as before. */
@media (min-width: 900px) {
  .ui-grid-now { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
}
