/* ==========================================================================
   Mauqe V2 — Component layer
   The complete UI vocabulary. Every interactive surface in the product is
   assembled from these classes; screens add layout, never new visual language.
   Depends on tokens.css + base.css.
   ========================================================================== */

/* ==========================================================================
   Icon
   ui.Icon() emits <svg class="icon"> with a 24x24 viewBox and no width or
   height, so this is the one declaration that gives every icon a size. It is
   the floor, not the answer: any container rule below (`.btn svg`,
   `.nav-item svg`, `.badge svg`, …) is more specific and wins. Never size an
   icon on the instance — add a rule for its container here instead.
   ========================================================================== */
.icon { inline-size: 1em; block-size: 1em; flex: 0 0 auto; }

/* An icon placed directly in a layout run — "{icon} some text" inside .row —
   belongs to the run, not to the text, so it keeps one size however small the
   text beside it is set. Direct children only: an icon inside a .btn or a
   .badge nested in the row is that component's business. .row is a flex
   container already, so the icon cannot be squashed by a long label. */
.row > svg, .row-top > svg { inline-size: 16px; block-size: 16px; }

/* ==========================================================================
   Button
   Variants: primary | secondary | ghost | quiet | danger | success
   Sizes:    .btn-xs | .btn-sm | (default) | .btn-lg
   Modifiers:.btn-icon (square) | .btn-block | .btn-pill
   ========================================================================== */
.btn {
  --btn-bg: var(--bg-surface);
  --btn-fg: var(--fg-default);
  --btn-bd: var(--border-strong);
  --btn-bg-h: var(--bg-hover);
  --btn-sh: var(--sh-1);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  height: var(--ctl-h);
  padding-inline: var(--s-4);
  border: var(--border-w) solid var(--btn-bd);
  border-radius: var(--r-control);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--t-md);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  box-shadow: var(--btn-sh);
  transition: background var(--dur-1) var(--ease-out),
              border-color var(--dur-1) var(--ease-out),
              color var(--dur-1) var(--ease-out),
              box-shadow var(--dur-1) var(--ease-out),
              transform var(--dur-1) var(--ease-out);
}
.btn:hover:not(:disabled):not([aria-disabled="true"]) { background: var(--btn-bg-h); }
.btn:active:not(:disabled) { transform: translateY(0.5px); box-shadow: none; }
.btn:focus-visible { outline: none; box-shadow: var(--sh-focus); }
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  box-shadow: none;
}
.btn svg { width: 1.15em; height: 1.15em; flex: 0 0 auto; }

.btn-primary {
  --btn-bg: var(--brand-solid);
  --btn-fg: var(--brand-on);
  --btn-bd: transparent;
  --btn-bg-h: var(--brand-solid-h);
  --btn-sh: var(--sh-2);
}
.btn-secondary {
  --btn-bg: var(--brand-bg);
  --btn-fg: var(--brand-fg);
  --btn-bd: transparent;
  --btn-bg-h: var(--brand-bg-2);
  --btn-sh: none;
}
.btn-ghost {
  --btn-bg: transparent;
  --btn-fg: var(--fg-muted);
  --btn-bd: transparent;
  --btn-bg-h: var(--bg-hover);
  --btn-sh: none;
}
.btn-ghost:hover:not(:disabled) { color: var(--fg-strong); }
.btn-quiet {
  --btn-bg: transparent;
  --btn-fg: var(--fg-muted);
  --btn-bd: var(--border);
  --btn-bg-h: var(--bg-hover);
  --btn-sh: none;
}
.btn-danger {
  --btn-bg: var(--bad-solid);
  --btn-fg: var(--fg-onbrand);
  --btn-bd: transparent;
  --btn-bg-h: var(--bad-700);
  --btn-sh: var(--sh-2);
}
.btn-danger-quiet {
  --btn-bg: transparent;
  --btn-fg: var(--bad-fg);
  --btn-bd: var(--bad-border);
  --btn-bg-h: var(--bad-bg);
  --btn-sh: none;
}
.btn-success {
  --btn-bg: var(--ok-solid);
  --btn-fg: var(--fg-onbrand);
  --btn-bd: transparent;
  --btn-bg-h: var(--ok-700);
  --btn-sh: var(--sh-2);
}

.btn-xs { height: var(--ctl-h-sm); padding-inline: var(--s-2); font-size: var(--t-xs); border-radius: var(--r-sm); gap: var(--s-1); }
.btn-sm { height: var(--ctl-h-sm); padding-inline: var(--s-3); font-size: var(--t-sm); border-radius: var(--r-sm); }
.btn-lg { height: 46px; padding-inline: var(--s-6); font-size: var(--t-lg); border-radius: var(--r-lg); }

.btn-icon { inline-size: var(--ctl-h); padding-inline: 0; }
.btn-icon.btn-sm, .btn-icon.btn-xs { inline-size: var(--ctl-h-sm); }
.btn-icon.btn-lg { inline-size: 46px; }
.btn-block { display: flex; width: 100%; }
.btn-pill  { border-radius: var(--r-full); }

/* Loading state — driven by the `aria-busy` attribute, not a class, so it
   stays accessible. Text stays in place; a spinner replaces the icon slot. */
.btn[aria-busy="true"] { pointer-events: none; opacity: 0.75; }
.btn[aria-busy="true"]::before {
  content: "";
  width: 1em; height: 1em;
  border: 2px solid currentColor;
  border-block-start-color: transparent;
  border-radius: var(--r-full);
  animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* A group of buttons welded into one control. */
.btn-group { display: inline-flex; }
.btn-group > .btn { border-radius: 0; margin-inline-start: -1px; box-shadow: none; }
.btn-group > .btn:first-child { border-start-start-radius: var(--r-control); border-end-start-radius: var(--r-control); margin-inline-start: 0; }
.btn-group > .btn:last-child  { border-start-end-radius: var(--r-control); border-end-end-radius: var(--r-control); }
.btn-group > .btn:hover, .btn-group > .btn:focus-visible { z-index: 1; }

/* ==========================================================================
   Form controls
   ========================================================================== */
.field { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
.field-row { display: flex; gap: var(--s-4); flex-wrap: wrap; }
.field-row > .field { flex: 1 1 200px; }

.label {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
  color: var(--fg-strong);
  line-height: var(--lh-snug);
}
.label .req { color: var(--bad-fg); font-weight: var(--fw-bold); }
.label .opt { color: var(--fg-subtle); font-weight: var(--fw-normal); font-size: var(--t-xs); }

/* Help text is content, not decoration: someone reads it to work out what to
   type. --fg-subtle measures 4.3:1 on white at this size, under the 4.5:1
   floor, so hints take the muted tone instead. */
.hint { font-size: var(--t-xs); color: var(--fg-muted); line-height: var(--lh-snug); }
.error-text {
  display: flex; align-items: flex-start; gap: var(--s-1);
  font-size: var(--t-xs); color: var(--bad-fg); line-height: var(--lh-snug);
}

.input, .select, .textarea {
  inline-size: 100%;
  min-height: var(--ctl-h);
  padding: var(--s-2) var(--s-3);
  background: var(--bg-surface);
  color: var(--fg-strong);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--r-control);
  font-size: var(--t-md);
  line-height: var(--lh-snug);
  box-shadow: var(--sh-1);
  transition: border-color var(--dur-1) var(--ease-out),
              box-shadow var(--dur-1) var(--ease-out),
              background var(--dur-1) var(--ease-out);
}
/* --fg-disabled measured 2.5:1 light / 2.8:1 dark against the input, which is
   below even the large-text floor — and this product uses placeholders to
   carry worked examples ("e.g. purchase requests"). --fg-subtle keeps the
   recessed look and roughly doubles the contrast. */
.input::placeholder, .textarea::placeholder { color: var(--fg-subtle); }
/* A textarea is a control and it is also a box that grows, and height is what
   --r-control is about: on a theme whose corners are `full` it takes the
   container corner, because a four-row field is 112px tall and a 112px stadium
   is not a rounded rectangle, it is an oval with text in it. */
.textarea { border-radius: var(--r-md); }
.input:hover:not(:disabled), .select:hover:not(:disabled), .textarea:hover:not(:disabled) {
  border-color: var(--fg-disabled);
}
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--brand-solid);
  box-shadow: var(--sh-focus);
}
.input:disabled, .select:disabled, .textarea:disabled {
  background: var(--bg-sunken);
  color: var(--fg-disabled);
  cursor: not-allowed;
  box-shadow: none;
}
.input[readonly] { background: var(--bg-subtle); }
.input[aria-invalid="true"], .select[aria-invalid="true"], .textarea[aria-invalid="true"] {
  border-color: var(--bad-solid);
}
.input[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px var(--bad-bg); }

.textarea { min-height: 88px; resize: vertical; line-height: var(--lh-base); }
.textarea-lg { min-height: 160px; }

.select {
  appearance: none;
  padding-inline-end: var(--s-8);
  /* Caret drawn as a background image so it mirrors correctly in RTL. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236d7b8f' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s-3) center;
  background-size: 16px;
  cursor: pointer;
}
[dir="rtl"] .select { background-position: left var(--s-3) center; }
[data-theme="dark"] .select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%239fadc0' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
}

.input-sm, .select-sm { min-height: var(--ctl-h-sm); padding-block: var(--s-1); font-size: var(--t-sm); }
.input-lg { min-height: 46px; font-size: var(--t-lg); }

/* Input with a leading icon or trailing affix (currency, unit, action). */
.input-wrap { position: relative; display: flex; align-items: center; inline-size: 100%; }
.input-wrap > .input { padding-inline-start: var(--s-8); }
.input-wrap > .i-lead {
  position: absolute;
  inset-inline-start: var(--s-3);
  color: var(--fg-subtle);
  pointer-events: none;
  display: flex;
}
.input-wrap > .i-lead svg { width: 16px; height: 16px; }
.input-wrap.has-affix > .input { padding-inline-end: var(--s-10); }
.input-wrap > .affix {
  position: absolute;
  inset-inline-end: var(--s-3);
  font-size: var(--t-sm);
  color: var(--fg-subtle);
}

/* Checkbox & radio — custom-drawn for consistent size and focus. */
.check, .radio {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--s-3);
  cursor: pointer;
  min-height: var(--tap-min);
  padding-block: var(--s-2);
  line-height: var(--lh-snug);
}
.check input, .radio input {
  appearance: none;
  flex: 0 0 auto;
  inline-size: 18px;
  block-size: 18px;
  margin: 1px 0 0;
  border: 1.5px solid var(--border-strong);
  background: var(--bg-surface);
  cursor: pointer;
  transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.check input { border-radius: var(--r-xs); }
.radio input { border-radius: var(--r-full); }
.check input:hover, .radio input:hover { border-color: var(--brand-solid); }
.check input:checked, .radio input:checked {
  background: var(--brand-solid);
  border-color: var(--brand-solid);
}
.check input:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-6.5'/%3E%3C/svg%3E");
  background-size: 100%;
}
.check input:indeterminate {
  background: var(--brand-solid);
  border-color: var(--brand-solid);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='3.5' y='7.2' width='9' height='1.8' rx='0.9' fill='white'/%3E%3C/svg%3E");
}
.radio input:checked {
  background-image: radial-gradient(circle, var(--brand-on) 0 3.2px, transparent 3.4px);
}
.check input:focus-visible, .radio input:focus-visible { outline: none; box-shadow: var(--sh-focus); }
.check input:disabled, .radio input:disabled { opacity: 0.45; cursor: not-allowed; }

/* Switch — for settings that take effect immediately. */
.switch { display: inline-flex; align-items: center; gap: var(--s-3); cursor: pointer; min-height: var(--tap-min); }
.switch input {
  appearance: none;
  position: relative;
  inline-size: 38px;
  block-size: 22px;
  flex: 0 0 auto;
  border-radius: var(--r-full);
  background: var(--border-strong);
  cursor: pointer;
  transition: background var(--dur-2) var(--ease-out);
}
.switch input::after {
  content: "";
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  inline-size: 16px;
  block-size: 16px;
  border-radius: var(--r-full);
  background: var(--fg-onbrand);
  box-shadow: var(--sh-2);
  transition: translate var(--dur-2) var(--ease-out);
}
.switch input:checked { background: var(--brand-solid); }
.switch input:checked::after { translate: calc(16px * var(--dir-flip)) 0; }
.switch input:focus-visible { outline: none; box-shadow: var(--sh-focus); }
.switch input:disabled { opacity: 0.45; cursor: not-allowed; }

/* Segmented control — mutually exclusive options, always all visible. */
.segmented {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--bg-sunken);
  border: var(--border-w) solid var(--border);
  border-radius: var(--r-control);
}
.segmented > button, .segmented > label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  height: calc(var(--ctl-h) - 8px);
  padding-inline: var(--s-3);
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
  color: var(--fg-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
/* Matches `.tab svg`: both are a small label with an icon in front of it. */
.segmented > button svg, .segmented > label svg { inline-size: 16px; block-size: 16px; }
.segmented > button:hover { color: var(--fg-strong); }
.segmented > button[aria-pressed="true"],
.segmented > button.is-active {
  background: var(--bg-surface);
  color: var(--fg-strong);
  box-shadow: var(--sh-1);
}

/* Fieldset used to group related settings. */
.fieldset {
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.fieldset > legend {
  padding-inline: var(--s-2);
  font-size: var(--t-sm);
  font-weight: var(--fw-semi);
  color: var(--fg-strong);
}

/* ==========================================================================
   Card / Panel
   ========================================================================== */
.card {
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.card-pad { padding: var(--pad-card); }
/* A card is as wide as the column it is in, and a header or a footer that
   cannot fit its contents on one line wraps onto a second. Without this a
   three-button footer in a narrow grid cell spills past the card's border and
   drags the whole page into a horizontal scroll — which is what a card
   "overflowing with content" looks like from the outside. `min-inline-size: 0`
   is what lets a long title inside actually shrink; a flex child's default
   `auto` minimum is the reason .truncate on its own does nothing here. */
.card-hd {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding: var(--s-4) var(--pad-card);
  border-block-end: 1px solid var(--border);
  min-height: 52px;
}
.card-hd > * { min-inline-size: 0; }
.card-hd h2, .card-hd h3 { font-size: var(--t-md); font-weight: var(--fw-semi); }
.card-bd { padding: var(--pad-card); min-inline-size: 0; }
.card-ft {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding: var(--s-3) var(--pad-card);
  border-block-start: 1px solid var(--border);
  background: var(--bg-subtle);
  border-end-start-radius: var(--r-lg);
  border-end-end-radius: var(--r-lg);
}

/* Interactive card — for pickable tiles (templates, field types, apps). */
.card-btn {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  text-align: start;
  padding: var(--s-4);
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--r-lg);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out),
              background var(--dur-2) var(--ease-out);
}
.card-btn:hover {
  border-color: var(--brand-300);
  box-shadow: var(--sh-3);
  transform: translateY(-2px);
}
.card-btn:focus-visible { outline: none; box-shadow: var(--sh-focus); border-color: var(--brand-solid); }
.card-btn[aria-pressed="true"], .card-btn.is-selected {
  border-color: var(--brand-solid);
  background: var(--brand-bg);
  box-shadow: 0 0 0 1px var(--brand-solid);
}

.panel {
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}

/* ==========================================================================
   Badge / Pill / Tag
   ========================================================================== */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 2px var(--s-2);
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  font-weight: var(--fw-medium);
  line-height: 1.5;
  white-space: nowrap;
  background: var(--bg-sunken);
  color: var(--fg-muted);
  border: 1px solid var(--border-subtle);
}
.badge-brand { background: var(--brand-bg); color: var(--brand-fg); border-color: transparent; }
.badge-ok    { background: var(--ok-bg);   color: var(--ok-fg);   border-color: var(--ok-border); }
.badge-warn  { background: var(--warn-bg); color: var(--warn-fg); border-color: var(--warn-border); }
.badge-bad   { background: var(--bad-bg);  color: var(--bad-fg);  border-color: var(--bad-border); }
.badge-info  { background: var(--info-bg); color: var(--info-fg); border-color: var(--info-border); }
.badge-pill  { border-radius: var(--r-full); padding-inline: var(--s-3); }
/* A badge sets --t-xs, so 1em would draw an 11px icon that reads as a smudge
   next to the label. 13px is the smallest size these paths stay legible at. */
.badge svg { inline-size: 13px; block-size: 13px; }

/* Status dot for live/inactive states. */
.dot { inline-size: 7px; block-size: 7px; border-radius: var(--r-full); background: var(--fg-disabled); flex: 0 0 auto; }
.dot-ok { background: var(--ok-solid); }
.dot-warn { background: var(--warn-solid); }
.dot-bad { background: var(--bad-solid); }
.dot-live { background: var(--ok-solid); box-shadow: 0 0 0 0 var(--ok-solid); animation: pulse 2s var(--ease-out) infinite; }
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok-solid) 55%, transparent); }
  70%  { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* Removable tag, used for multi-select values and filters. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 2px 2px 2px var(--s-2);
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  color: var(--fg-default);
}
.tag > button {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 18px; block-size: 18px;
  border-radius: var(--r-xs);
  color: var(--fg-subtle);
}
.tag > button:hover { background: var(--bad-bg); color: var(--bad-fg); }

/* Counter for unread/pending items. */
.count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 18px;
  block-size: 18px;
  padding-inline: 5px;
  border-radius: var(--r-full);
  background: var(--bad-solid);
  color: var(--fg-onbrand);
  font-size: var(--t-2xs);
  font-weight: var(--fw-semi);
  line-height: 1;
}
.count-quiet { background: var(--bg-active); color: var(--fg-muted); }

/* ==========================================================================
   Avatar
   ========================================================================== */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 32px;
  block-size: 32px;
  border-radius: var(--r-full);
  background: var(--brand-bg);
  color: var(--brand-fg);
  font-size: var(--t-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  flex: 0 0 auto;
  overflow: hidden;
  user-select: none;
}
.avatar img { inline-size: 100%; block-size: 100%; object-fit: cover; }
/* An avatar that holds an icon instead of initials — an app's own mark in the
   sidebar, say. Proportional rather than a pixel size, because .avatar comes in
   five sizes and one rule should serve all of them; 1em of the avatar's own
   font-size drew an 11px glyph inside a 26px tile. */
.avatar > svg { inline-size: 60%; block-size: 60%; }
/* The square tile an app, a template or a data source wears — the same mark as
   .avatar but cornered rather than round, because a thing you open is not a
   person. It belongs here and not in a page's own <style> block: three pages
   draw this markup, and while the rule lived in apps.html it styled one of
   them. On the other two the glyph fell through to `.icon { 1em }` and drew
   14px with no tile at all. */
.app-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 40px;
  block-size: 40px;
  border-radius: var(--r-md);
  background: var(--brand-bg);
  color: var(--brand-fg);
  flex: 0 0 auto;
}
.app-icon > svg { inline-size: 20px; block-size: 20px; }

.avatar-xs { inline-size: 22px; block-size: 22px; font-size: var(--t-2xs); }
.avatar-sm { inline-size: 26px; block-size: 26px; font-size: var(--t-2xs); }
.avatar-lg { inline-size: 44px; block-size: 44px; font-size: var(--t-md); }
.avatar-xl { inline-size: 64px; block-size: 64px; font-size: var(--t-xl); }

.avatar-stack { display: inline-flex; }
.avatar-stack > .avatar { border: 2px solid var(--bg-surface); margin-inline-start: -8px; }
.avatar-stack > .avatar:first-child { margin-inline-start: 0; }

/* ==========================================================================
   Table
   ========================================================================== */
.table-wrap {
  inline-size: 100%;
  overflow-x: auto;
  border: var(--border-w) solid var(--border);
  border-radius: var(--r-lg);
  background: var(--bg-surface);
}
.table { inline-size: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--t-sm); }

.table thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  background: var(--bg-subtle);
  padding: var(--s-3) var(--s-4);
  text-align: start;
  font-size: var(--t-xs);
  font-weight: var(--fw-semi);
  color: var(--fg-muted);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
  border-block-end: 1px solid var(--border);
}
[dir="rtl"] .table thead th { letter-spacing: 0; }

.table tbody td {
  padding: 0 var(--s-4);
  height: var(--row-h);
  border-block-end: 1px solid var(--border-subtle);
  color: var(--fg-default);
  vertical-align: middle;
}
.table tbody tr:last-child td { border-block-end: none; }
.table tbody tr { transition: background var(--dur-1) var(--ease-out); }
.table tbody tr:hover { background: var(--bg-subtle); }
.table tbody tr[aria-selected="true"] { background: var(--brand-bg); }
/* A row whose name is a link opens as a whole — see the delegated handler in
   app.js. The pointer is the only affordance that says so, and it is set from
   the link's presence rather than from a class, so no listing has to remember
   to opt in. :has is safe here: without it the row simply keeps the default
   cursor and the link still works. */
tbody tr:has(> td a[href]) { cursor: pointer; }

.table-zebra tbody tr:nth-child(even) { background: var(--bg-subtle); }
.table-zebra tbody tr:hover { background: var(--bg-hover); }

.table .num { text-align: end; font-variant-numeric: tabular-nums; }
.table .actions-cell { inline-size: 1%; white-space: nowrap; text-align: end; }
/* A row action is a .btn-sm because that is what a cell has room for, and 30px
   is a mouse size. On a finger-driven device every one of them — open, edit,
   copy, delete — has to clear the touch minimum, or a table is something you
   can only use sitting down. One rule for the context rather than a size on
   each instance; the same reasoning as the builder's block rail below. */
@media (pointer: coarse) {
  .table .actions-cell .btn { min-block-size: var(--tap-min); }
}
/* A bare icon in a cell — a tick for "required", say. Buttons and badges in a
   cell keep their own size, which is why this is scoped to a direct child. */
.table th > svg, .table td > svg { inline-size: 16px; block-size: 16px; }

/* Sortable header */
.th-sort {
  display: inline-flex; align-items: center; gap: var(--s-1);
  color: inherit; font: inherit; letter-spacing: inherit; text-transform: inherit;
  cursor: pointer;
}
.th-sort:hover { color: var(--fg-strong); }
.th-sort[aria-sort]:not([aria-sort="none"]) { color: var(--brand-fg); }

/* First column can be pinned when scrolling wide tables horizontally. */
.table .pin-start {
  position: sticky;
  inset-inline-start: 0;
  z-index: 1;
  background: var(--bg-surface);
}
.table thead .pin-start { z-index: 3; background: var(--bg-subtle); }
.table tbody tr:hover .pin-start { background: var(--bg-subtle); }

/* ==========================================================================
   Tabs
   ========================================================================== */
.tabs {
  display: flex;
  gap: var(--s-1);
  border-block-end: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-3);
  margin-block-end: -1px;
  border-block-end: 2px solid transparent;
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
  color: var(--fg-muted);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.tab:hover { color: var(--fg-strong); }
.tab[aria-selected="true"], .tab.is-active {
  color: var(--brand-fg);
  border-block-end-color: var(--brand-solid);
}
.tab svg { width: 16px; height: 16px; }

/* ==========================================================================
   Modal / Drawer / Sheet
   ========================================================================== */
.overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: var(--overlay);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-4);
  animation: fade-in var(--dur-2) var(--ease-out);
}
@keyframes fade-in { from { opacity: 0; } }

.modal {
  position: relative;
  inline-size: 100%;
  max-inline-size: 560px;
  max-block-size: min(86dvh, 900px);
  display: flex;
  flex-direction: column;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-5);
  animation: modal-in var(--dur-3) var(--ease-out);
  overflow: hidden;
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(12px) scale(0.985); }
}
.modal-sm { max-inline-size: 420px; }
.modal-lg { max-inline-size: 800px; }
.modal-xl { max-inline-size: 1080px; }
.modal-full { max-inline-size: min(1320px, 96vw); block-size: min(90dvh, 940px); max-block-size: none; }

.modal-hd {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-5) var(--s-5) var(--s-4);
  border-block-end: 1px solid var(--border);
  flex: 0 0 auto;
}
.modal-hd h2 { font-size: var(--t-lg); }
.modal-hd .hint { margin-block-start: 2px; }
.modal-bd { padding: var(--s-5); overflow-y: auto; flex: 1 1 auto; }
.modal-ft {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-block-start: 1px solid var(--border);
  background: var(--bg-subtle);
  flex: 0 0 auto;
}

/* On phones a modal becomes a bottom sheet — reachable with one thumb. */
@media (max-width: 620px) {
  .overlay { align-items: flex-end; padding: 0; }
  .modal {
    max-inline-size: none;
    border-radius: var(--r-2xl) var(--r-2xl) 0 0;
    max-block-size: 92dvh;
    animation: sheet-in var(--dur-3) var(--ease-out);
  }
  @keyframes sheet-in { from { transform: translateY(100%); } }
  .modal-ft > .btn { flex: 1; }
}

.drawer {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: var(--z-drawer);
  inline-size: min(460px, 92vw);
  display: flex;
  flex-direction: column;
  background: var(--bg-raised);
  border-inline-start: 1px solid var(--border);
  box-shadow: var(--sh-5);
  animation: drawer-in var(--dur-3) var(--ease-out);
}
@keyframes drawer-in { from { transform: translateX(calc(100% * var(--dir-flip))); } }

/* ==========================================================================
   Popover / Menu / Tooltip
   ========================================================================== */
.popover {
  position: absolute;
  z-index: var(--z-popover);
  min-inline-size: 200px;
  padding: var(--s-1);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-4);
  animation: pop-in var(--dur-2) var(--ease-out);
}
@keyframes pop-in { from { opacity: 0; transform: translateY(-4px) scale(0.98); } }

.menu { display: flex; flex-direction: column; gap: 1px; }
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  inline-size: 100%;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  color: var(--fg-default);
  text-align: start;
  cursor: pointer;
  white-space: nowrap;
}
.menu-item:hover, .menu-item:focus-visible { background: var(--bg-hover); color: var(--fg-strong); outline: none; }
.menu-item svg { width: 16px; height: 16px; color: var(--fg-subtle); flex: 0 0 auto; }
.menu-item:hover svg { color: var(--fg-muted); }
.menu-item.is-danger { color: var(--bad-fg); }
.menu-item.is-danger:hover { background: var(--bad-bg); }
.menu-item.is-danger svg { color: var(--bad-fg); }
.menu-item[aria-disabled="true"] { opacity: 0.45; pointer-events: none; }
.menu-sep { height: 1px; background: var(--border); margin: var(--s-1) 0; }
.menu-label {
  padding: var(--s-2) var(--s-3) var(--s-1);
  font-size: var(--t-2xs);
  font-weight: var(--fw-semi);
  /* 11px on a raised surface: --fg-subtle lands at 4.3:1 light / 4.4:1 dark,
     both under the floor. This label carries the person's own name and the
     notification heading, so it has to be legible. */
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

/* Tooltip — CSS-only, driven by a data attribute. Never carries essential
   information; it is always a restatement of a visible label. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  inset-block-end: calc(100% + 6px);
  inset-inline-start: 50%;
  translate: calc(-50% * var(--dir-flip)) 0;
  z-index: var(--z-popover);
  padding: var(--s-1) var(--s-2);
  background: var(--bg-inverse);
  color: var(--fg-inverse);
  font-size: var(--t-2xs);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  border-radius: var(--r-sm);
  box-shadow: var(--sh-3);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out) 250ms;
}
[data-tip]:hover::after, [data-tip]:focus-visible::after { opacity: 1; }
[data-tip-side="bottom"]::after { inset-block-end: auto; inset-block-start: calc(100% + 6px); }

/* ==========================================================================
   Alert / Callout
   ========================================================================== */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: var(--border-w) solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-subtle);
  font-size: var(--t-sm);
  line-height: var(--lh-snug);
  color: var(--fg-default);
}
.alert > svg { width: 18px; height: 18px; flex: 0 0 auto; margin-block-start: 1px; }
.alert strong { color: var(--fg-strong); font-weight: var(--fw-semi); }
.alert-info { background: var(--info-bg); border-color: var(--info-border); color: var(--info-fg); }
.alert-ok   { background: var(--ok-bg);   border-color: var(--ok-border);   color: var(--ok-fg); }
.alert-warn { background: var(--warn-bg); border-color: var(--warn-border); color: var(--warn-fg); }
.alert-bad  { background: var(--bad-bg);  border-color: var(--bad-border);  color: var(--bad-fg); }
.alert-info strong, .alert-ok strong, .alert-warn strong, .alert-bad strong { color: inherit; }

/* ==========================================================================
   Toast
   ========================================================================== */
.toast-region {
  position: fixed;
  inset-block-end: var(--s-5);
  inset-inline-end: var(--s-5);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  pointer-events: none;
  max-inline-size: min(420px, calc(100vw - var(--s-8)));
}
@media (max-width: 620px) {
  .toast-region { inset-inline: var(--s-3); inset-block-end: var(--s-3); max-inline-size: none; }
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--fg-muted);
  border-radius: var(--r-md);
  box-shadow: var(--sh-4);
  font-size: var(--t-sm);
  color: var(--fg-default);
  pointer-events: auto;
  animation: toast-in var(--dur-3) var(--ease-spring);
}
@keyframes toast-in { from { opacity: 0; transform: translateY(8px) scale(0.97); } }
.toast.is-leaving { animation: toast-out var(--dur-2) var(--ease-out) forwards; }
@keyframes toast-out { to { opacity: 0; transform: translateY(-6px); } }
.toast-ok   { border-inline-start-color: var(--ok-solid); }
.toast-bad  { border-inline-start-color: var(--bad-solid); }
.toast-warn { border-inline-start-color: var(--warn-solid); }
.toast-info { border-inline-start-color: var(--info-solid); }
.toast > svg { width: 18px; height: 18px; flex: 0 0 auto; margin-block-start: 1px; }
.toast-ok > svg { color: var(--ok-solid); }
.toast-bad > svg { color: var(--bad-solid); }
.toast .toast-title { font-weight: var(--fw-semi); color: var(--fg-strong); }

/* ==========================================================================
   Empty state — every list must ship one. It teaches, it does not apologise.
   ========================================================================== */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s-3);
  padding: var(--s-12) var(--s-6);
  color: var(--fg-muted);
}
.empty-art {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 56px;
  block-size: 56px;
  border-radius: var(--r-xl);
  background: var(--brand-bg);
  color: var(--brand-fg);
  margin-block-end: var(--s-1);
}
.empty-art svg { width: 26px; height: 26px; }
.empty h3 { font-size: var(--t-lg); color: var(--fg-strong); }
.empty p { max-inline-size: 46ch; font-size: var(--t-sm); }
.empty-actions { display: flex; gap: var(--s-2); flex-wrap: wrap; justify-content: center; margin-block-start: var(--s-2); }

/* ==========================================================================
   Skeleton / loading
   ========================================================================== */
.skel {
  background: var(--skeleton);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
  border-radius: var(--r-sm);
  block-size: 1em;
}
@keyframes shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
.skel-text { block-size: 0.8em; margin-block: 0.25em; }
.skel-title { block-size: 1.2em; inline-size: 40%; }
.skel-circle { border-radius: var(--r-full); }

.spinner {
  inline-size: 18px; block-size: 18px;
  border: 2px solid var(--border-strong);
  border-block-start-color: var(--brand-solid);
  border-radius: var(--r-full);
  animation: spin 0.6s linear infinite;
}

/* Progress bar */
.progress {
  block-size: 6px;
  inline-size: 100%;
  background: var(--bg-sunken);
  border-radius: var(--r-full);
  overflow: hidden;
}
.progress > i {
  display: block;
  block-size: 100%;
  background: var(--brand-solid);
  border-radius: var(--r-full);
  transition: inline-size var(--dur-4) var(--ease-out);
}
.progress-ok > i { background: var(--ok-solid); }
.progress-warn > i { background: var(--warn-solid); }
.progress-bad > i { background: var(--bad-solid); }

/* ==========================================================================
   Breadcrumb
   ========================================================================== */
.crumbs { display: flex; align-items: center; gap: var(--s-1); font-size: var(--t-sm); flex-wrap: wrap; }
.crumbs a { color: var(--fg-muted); }
.crumbs a:hover { color: var(--fg-strong); text-decoration: none; }
.crumbs .sep { color: var(--fg-disabled); display: flex; }
.crumbs .sep svg { width: 14px; height: 14px; transform: scaleX(var(--dir-flip)); }
.crumbs [aria-current="page"] { color: var(--fg-strong); font-weight: var(--fw-medium); }

/* ==========================================================================
   Search input (used in the command bar, pickers and list filters)
   ========================================================================== */
.search {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  height: var(--ctl-h);
  padding-inline: var(--s-3);
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--r-control);
  color: var(--fg-subtle);
  transition: border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}
.search:focus-within { border-color: var(--brand-solid); box-shadow: var(--sh-focus); }
.search svg { width: 16px; height: 16px; flex: 0 0 auto; }
.search input {
  flex: 1 1 auto;
  min-inline-size: 0;
  border: none;
  background: none;
  outline: none;
  color: var(--fg-strong);
  font-size: var(--t-md);
}
.search input::placeholder { color: var(--fg-subtle); }

/* ==========================================================================
   Misc utilities used across screens
   ========================================================================== */
.surface { background: var(--bg-surface); }
.sunken  { background: var(--bg-sunken); }
.bordered { border: var(--border-w) solid var(--border); }
.rounded { border-radius: var(--r-lg); }
.shadow-1 { box-shadow: var(--sh-1); }
.shadow-3 { box-shadow: var(--sh-3); }
.relative { position: relative; }
.sticky-top { position: sticky; inset-block-start: 0; z-index: var(--z-sticky); }
.scroll-y { overflow-y: auto; }
.scroll-x { overflow-x: auto; }
.no-select { user-select: none; }
.cursor-grab { cursor: grab; }
.cursor-grab:active { cursor: grabbing; }
.full-w { inline-size: 100%; }

/* ==========================================================================
   The app list, in list view, on a phone
   ==========================================================================

   OWNERSHIP NOTE — these four declarations belong in the page-scoped <style>
   block of pages/apps.html, beside the `#app-grid.is-list` rules they correct.
   They are here because that template is held by another agent while this is
   being fixed. Fold them back when it is free; nothing else in the product
   draws an `.app-card`.

   What they fix: in list view the card becomes a flex row of two children —
   `.app-card-main`, which carries the app's name, and `.app-card-meta`, which
   carries a badge, two counts and the actions menu. Main is `flex: 1` from a
   zero basis; meta keeps its content's width. So meta is served first and main
   divides what is left. At 375px that is 255px against 87px, and the name —
   `.truncate` inside it — renders **2.7px wide** against a 156px string. The
   most important thing in the row is the only thing you cannot read.

   Below the breakpoint apps.html already uses for its other two-column row,
   the two stack instead of competing: the name gets the whole width, the
   metadata keeps every value it had, one line lower. Nothing is hidden. */
@media (max-width: 620px) {
  #app-grid.is-list .app-card { flex-wrap: wrap; }
  #app-grid.is-list .app-card > .app-card-main { flex: 1 1 100%; }
  #app-grid.is-list .app-card > .app-card-meta { flex: 1 1 100%; padding-block-start: 0; }
}

/* Direction-dependent icons.
   `ui.Icon()` tags arrows, chevrons and the like with `.icon-dir`; everything
   else keeps its shape in both scripts. Mirroring here rather than shipping a
   second set of paths means one drawing per icon, so the two can never drift.
   --dir-flip is 1 in LTR and -1 in RTL, so this is a no-op in English. */
.icon-dir { transform: scaleX(var(--dir-flip)); }

/* ==========================================================================
   Kanban board
   ========================================================================== */
.board { display: flex; gap: var(--s-4); overflow-x: auto; padding-block-end: var(--s-4); align-items: flex-start; }
.board-col {
  flex: 0 0 300px;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-3);
  max-block-size: 100%;
}
.board-col-hd { display: flex; align-items: center; gap: var(--s-2); padding-inline: var(--s-1); }
.board-col-hd .name { font-size: var(--t-sm); font-weight: var(--fw-semi); color: var(--fg-strong); }
.board-col-body { display: flex; flex-direction: column; gap: var(--s-2); overflow-y: auto; min-block-size: 60px; }
.board-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--s-3);
  box-shadow: var(--sh-1);
  cursor: grab;
  transition: box-shadow var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.board-card:hover { box-shadow: var(--sh-3); transform: translateY(-1px); }
.board-card:active { cursor: grabbing; }
.board-card .title { font-size: var(--t-sm); font-weight: var(--fw-medium); color: var(--fg-strong); margin-block-end: var(--s-2); }
.board-card .meta { display: flex; align-items: center; gap: var(--s-2); font-size: var(--t-xs); color: var(--fg-subtle); }

/* ==========================================================================
   Stat tiles — the KPI row on dashboards
   ========================================================================== */
.stat {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-4);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  min-inline-size: 0;
}
.stat .k { font-size: var(--t-xs); color: var(--fg-muted); font-weight: var(--fw-medium); }
/* The figure takes the display face, so a theme whose character is monospaced
   numerals (Steel) gets them here — where the numbers people actually read
   are — rather than needing a rule of its own. */
.stat .v { font-size: var(--t-3xl); font-weight: var(--fw-semi); color: var(--fg-strong); line-height: 1.1; letter-spacing: var(--tracking-tight); font-family: var(--font-display); font-variant-numeric: tabular-nums; }
.stat .d { display: flex; align-items: center; gap: var(--s-1); font-size: var(--t-xs); }
.stat .d.up { color: var(--ok-fg); }
.stat .d.down { color: var(--bad-fg); }
.stat .d.flat { color: var(--fg-subtle); }

/* ==========================================================================
   Timeline — audit trails, workflow history, activity feeds
   ========================================================================== */
.timeline { display: flex; flex-direction: column; }
.tl-item { display: flex; gap: var(--s-3); padding-block-end: var(--s-4); position: relative; }
.tl-item:not(:last-child)::before {
  content: "";
  position: absolute;
  inset-block: 26px 0;
  inset-inline-start: 12px;
  inline-size: 1.5px;
  background: var(--border);
}
.tl-dot {
  inline-size: 25px; block-size: 25px;
  border-radius: var(--r-full);
  background: var(--bg-surface);
  border: 1.5px solid var(--border-strong);
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  z-index: 1;
  color: var(--fg-subtle);
}
.tl-dot svg { inline-size: 13px; block-size: 13px; }
.tl-dot.ok  { border-color: var(--ok-solid);   background: var(--ok-bg);   color: var(--ok-fg); }
.tl-dot.bad { border-color: var(--bad-solid);  background: var(--bad-bg);  color: var(--bad-fg); }
.tl-dot.warn{ border-color: var(--warn-solid); background: var(--warn-bg); color: var(--warn-fg); }
.tl-body { flex: 1 1 auto; min-width: 0; padding-block-start: 2px; }
.tl-body .who { font-size: var(--t-sm); color: var(--fg-strong); font-weight: var(--fw-medium); }
.tl-body .when { font-size: var(--t-xs); color: var(--fg-subtle); }
.tl-body .what { font-size: var(--t-sm); color: var(--fg-muted); margin-block-start: 2px; }

/* ==========================================================================
   Screen layout — how a rendered screen's twelve columns collapse
   ==========================================================================

   internal/app/runtime lays every screen out on a twelve-column grid and gives
   each field a span. The span is the author's data, so its *value* is written
   inline; the *declaration* lives here, because an inline `grid-column` is the
   most specific rule in the document and no media query anywhere could have
   reached it. That is why a span-3 KPI measured 74px on a 375px phone — twelve
   columns of a 343px page, minus eleven gaps, is 14px a column.

   Two breakpoints, the same 900 and 620 the rest of this system uses. Below
   900 nothing is narrower than half the screen; below 620 everything is the
   full width, because twelve columns on a phone is not a layout.

   The fallback of 1 is what a field inside a row container gets: a row is its
   own grid with the author's own column count, and a span does not apply
   there — one track each is exactly what those fields had before. */
[data-mq-field] { grid-column: span var(--mq-span, 1); }

@media (max-width: 900px) {
  [data-mq-field] { grid-column: span var(--mq-span-md, var(--mq-span, 1)); }
}

@media (max-width: 620px) {
  [data-mq-field] {
    grid-column: span var(--mq-span-sm, var(--mq-span-md, var(--mq-span, 1)));
  }
}

/* A row container is its own grid with the author's own column count, so the
   same reasoning applies one level down — three tracks across a phone gives
   each field about a hundred pixels. The count is written inline as a value and
   the declaration is owned here, exactly as the span is. */
[data-mq-row] { grid-template-columns: repeat(var(--mq-cols, 2), minmax(0, 1fr)); }
@media (max-width: 620px) {
  [data-mq-row] { grid-template-columns: minmax(0, 1fr); }
}

/* A column heading you can press.
   Styled as the heading it is rather than as a button: the affordance is the
   arrow and the hover, because a row of real buttons across the top of a table
   reads as a toolbar. It is a <button> underneath so it is reachable by
   keyboard and announced as a control; aria-sort lives on the <th>. */
.table th .th-sort {
  display: inline-flex; align-items: center; gap: var(--s-2);
  inline-size: 100%; padding: 0; border: 0; background: none;
  font: inherit; color: inherit; text-align: start; cursor: pointer;
}
.table th.num .th-sort { justify-content: flex-end; }
.table th .th-sort:hover { color: var(--fg-strong); }
.table th .th-sort:focus-visible { outline: 2px solid var(--brand-solid); outline-offset: 2px; }
.table th .th-arrow { color: var(--fg-disabled); font-size: var(--t-xs); }
.table th .th-sort.is-sorted { color: var(--fg-strong); font-weight: var(--fw-semi); }
.table th .th-sort.is-sorted .th-arrow { color: var(--brand-solid); }
