/* Atlas: design tokens (S0b, Material 3 Expressive).
   Spec: docs/superpowers/specs/2026-10-10-visual-style-m3-expressive.md. Names follow Material 3 colour roles.
   Every colour is light-dark(LIGHT, DARK): the theme is chosen by color-scheme on <html> (theme-light,
   theme-dark, or none = the device setting). tests/style.rs reads the pairs below and checks WCAG AA contrast
   in both themes, so keep one token per line in the form  --name: light-dark(#RRGGBB, #RRGGBB);  */

:root {
  color-scheme: light dark;

  /* Brand and accent roles */
  --primary: light-dark(#0B52CC, #B3C5FF);
  --on-primary: light-dark(#FFFFFF, #002B75);
  --primary-container: light-dark(#DAE1FF, #0047B8);
  --on-primary-container: light-dark(#001849, #DAE1FF);
  --secondary-container: light-dark(#E3EEFF, #414659);
  --on-secondary-container: light-dark(#002249, #DDE2F9);
  --tertiary: light-dark(#735471, #E2BBDC);

  /* Surfaces: tonal steps instead of borders and shadows */
  --bg: light-dark(#F2F3FE, #11131B);
  --card: light-dark(#FFFFFF, #1D1F27);
  --surface-container: light-dark(#EDEDF8, #1D1F27);
  --surface-container-high: light-dark(#E7E7F2, #282A32);
  --surface-container-highest: light-dark(#E1E2ED, #32343D);
  --on-surface: light-dark(#191B23, #E1E2ED);
  --on-surface-variant: light-dark(#424654, #C3C6D7);
  --inverse-surface: light-dark(#2E3038, #E1E2ED);
  --inverse-on-surface: light-dark(#F0F0FB, #2E3038);
  --inverse-primary: light-dark(#B3C5FF, #0C59DB);

  /* Lines and focus */
  --outline: light-dark(#737686, #8D90A0);
  --outline-variant: light-dark(#C3C6D7, #424654);
  --divider: light-dark(#E1E2ED, #32343D);
  --focus: light-dark(#0C59DB, #B3C5FF);

  /* Status: container + on-container text (chips, tiles, banners) */
  --ok: light-dark(#1E7B34, #98D598);
  --ok-container: light-dark(#CAF3C7, #1F5027);
  --on-ok-container: light-dark(#003911, #98D598);
  --warn-container: light-dark(#FFE4C4, #633F00);
  --on-warn-container: light-dark(#452B00, #F9BB64);
  --danger: light-dark(#A8221B, #FFB4AB);
  --on-danger: light-dark(#FFFFFF, #690005);
  --danger-container: light-dark(#FFE2DD, #410E0B);
  --on-danger-container: light-dark(#5F150F, #FFB4AB);
  --info-container: light-dark(#DEE9FF, #173F8A);
  --on-info-container: light-dark(#00315F, #B3C5FF);
  --neutral-container: light-dark(#E7E7F2, #32343D);
  --on-neutral-container: light-dark(#424654, #C3C6D7);

  /* Charts (S6): categorical lines and bars, in this fixed order (validated for colour-blind readers, light and dark).
     --chart-3 is under 3:1 on the light card, so a chart always has its words and a table beside it. */
  --chart-1: light-dark(#2A78D6, #3987E5);
  --chart-2: light-dark(#EB6834, #D95926);
  --chart-3: light-dark(#1BAF7A, #199E70);

  /* The Dashboard hero band and its action */
  --hero-action: light-dark(#0C59DB, #DAE1FF);
  --on-hero-action: light-dark(#FFFFFF, #001849);
  /* The soft shape behind the hero text: it darkens the band in dark, so the text keeps its contrast */
  --hero-shape: light-dark(rgb(12 89 219 / 0.12), rgb(0 24 73 / 0.22));
  /* The logo has no reversed version yet: in dark it sits on a white plate */
  --logo-plate: light-dark(#FFFFFF00, #FFFFFF);
  --scrim: light-dark(rgb(12 14 21 / 0.36), rgb(4 5 9 / 0.6));

  /* Scale (owner decision 2026-10-12, the denser scale): the root is 15 px (93.75 % of the browser's own 16 px; it
     was 17 px), and every size below and in the other files is in rem, so this one number scales the whole
     interface. Only hairlines and offsets of 1 to 3 px, shadows and letter-spacing stay in px (tests/style.rs). */
  --root-size: 93.75%;

  /* Type: Lato 400 body, 700 for every title, label and number (the emphasized scale).
     Body text is 16 px (1.0625rem), labels and small text 15 px (1rem). Nothing is drawn smaller than 14 px. */
  --font: "Lato", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --t-display-s: 700 2.25rem/2.75rem var(--font);
  --t-headline-m: 700 1.75rem/2.25rem var(--font);
  --t-headline-s: 700 1.5rem/2rem var(--font);
  --t-title-l: 700 1.375rem/1.75rem var(--font);
  --t-title-m: 700 1.125rem/1.625rem var(--font);
  --t-title-s: 700 1rem/1.5rem var(--font);
  --t-body-l: 400 1.0625rem/1.625rem var(--font);
  --t-body-m: 400 1rem/1.5rem var(--font);
  --t-label-l: 700 1rem/1.5rem var(--font);
  --t-label-m: 700 1rem/1.25rem var(--font);

  /* Shape ladder (Expressive), in rem like every size: about 4, 8, 11, 15, 19, 26 and 30 px at the 15 px root */
  --r-xs: 0.25rem;
  --r-sm: 0.5rem;
  --r-md: 0.75rem;
  --r-lg: 1rem;
  --r-lg-plus: 1.25rem;
  --r-xl: 1.75rem;
  --r-xl-plus: 2rem;
  --r-full: 9999px;

  /* Elevation: only floating things (menus, dialogs, toasts). Shadows vanish in dark; surfaces step up instead. */
  --shadow: light-dark(rgb(16 24 40 / 0.10), rgb(0 0 0 / 0));
  --shadow-soft: light-dark(rgb(16 24 40 / 0.06), rgb(0 0 0 / 0));
  --e-1: 0 1px 2px var(--shadow-soft), 0 1px 3px var(--shadow);
  --e-2: 0 4px 8px -2px var(--shadow), 0 2px 4px -2px var(--shadow-soft);
  --e-3: 0 20px 24px -4px var(--shadow), 0 8px 8px -4px var(--shadow-soft);

  /* State layers, drawn with color-mix(in srgb, currentColor N%, transparent) */
  --state-hover: 8%;
  --state-press: 12%;

  /* Motion: M3 easing and Expressive spring curves. All of it is off under prefers-reduced-motion (base.css). */
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --ease-effects: cubic-bezier(0.2, 0.6, 0.35, 1);
  --d-short: 150ms;
  --d-medium: 250ms;
  --spring-default: linear(0, 0.071, 0.227, 0.405, 0.572, 0.712, 0.82, 0.898, 0.95, 0.983, 1.002, 1.012, 1.015, 1.015, 1.013, 1.01, 1.007, 1.005, 1.003, 1.002, 1);
  --d-spring: 435ms;
  --spring-fast: linear(0, 0.104, 0.33, 0.579, 0.795, 0.95, 1.044, 1.087, 1.094, 1.08, 1.057, 1.034, 1.015, 1.002, 0.994, 0.991, 0.991, 0.993, 0.995, 0.997, 1);
  --d-spring-fast: 360ms;

  /* Sizes and layout (px at the 15 px root) */
  --target: 2.75rem;           /* 41 px: the smallest click target anywhere (never under 40 px) */
  --control-h: 3rem;           /* 45 px fields */
  --row-h: 3.25rem;            /* 49 px table rows */
  --rail-w: 7.5rem;            /* 112 px collapsed rail, 40-80rem: "Administration" at 15 px bold is 101 px */
  --sidebar-w: 17.5rem;        /* 263 px expanded rail, from 80rem: a name and its count on one line (shell.css) */
  --appbar-h: 4.25rem;         /* 64 px */
  --rail-item-h: 3.2rem;       /* 48 px rail items (Dashboard and the sections) */
  --nav-link-h: 2.8rem;        /* 42 px pages inside a section, in the rail and in a flyout */
  --gutter: 1rem;
}

html.theme-light { color-scheme: light; }
html.theme-dark { color-scheme: dark; }

/* 1.5rem (M3's 24 dp margin) from 40rem on. From 80rem it was 2rem until 2026-10-16, when the expanded rail took
   those 2 x 0.5rem (15 px) to hold a menu item's name and its count on one line: a card is as wide as it was at
   every window (972 px at 1280), so every table measured against it still fits. */
@media (min-width: 40rem) { :root { --gutter: 1.5rem; } }

/* Base: fonts, page defaults, focus, icons, and the media-query rules every part follows. */

@font-face {
  font-family: "Lato";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/lato-400.woff2") format("woff2");
}
@font-face {
  font-family: "Lato";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/lato-700.woff2") format("woff2");
}

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

/* A hidden element stays hidden even when a component rule (.btn is inline-flex) sets display. */
[hidden] { display: none !important; }

html {
  /* The one number that scales the interface (tokens.css): every rem follows it. */
  font-size: var(--root-size);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  background: var(--bg);
  color: var(--on-surface);
  accent-color: var(--primary);
  /* Sticky bars never cover the focused element (WCAG 2.4.11). */
  scroll-padding-block: calc(var(--appbar-h) + 0.5rem) 6rem;
}
body { margin: 0; min-height: 100dvh; font: var(--t-body-l); -webkit-font-smoothing: antialiased; }
h1, h2, h3, p, ul, ol, dl, dd, figure { margin: 0; }
ul[role="list"], ol[role="list"] { list-style: none; padding: 0; }
a { color: var(--primary); text-underline-offset: 3px; }
img, svg { display: block; }
button, input, select, textarea { font: inherit; color: inherit; }
code { font-size: 1rem; }
strong, b { font-weight: 700; }

h1 {
  font: var(--t-headline-s);
  font-size: clamp(1.5rem, 1.2rem + 1.05vw, 2rem);
  line-height: 1.2;
  letter-spacing: -0.2px;
}
h2 { font: var(--t-title-l); }
h3 { font: var(--t-title-m); }
h4 { font: var(--t-title-s); }

/* Focus is always visible: a real outline (forced-colors keeps it), 3 px, offset. */
:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; inset-inline-start: 0.5rem; top: -6rem; z-index: 200;
  padding: 0.75rem 1.25rem; border-radius: var(--r-full);
  background: var(--primary); color: var(--on-primary); font: var(--t-label-l); text-decoration: none;
}
.skip-link:focus { top: 0.5rem; }

/* Icons: always inside .icon-label next to their word (icons::label), sized with the text, coloured like it. */
.icon { width: 1.25em; height: 1.25em; flex: none; stroke: currentColor; }
.icon-label { display: inline-flex; align-items: center; gap: 0.5rem; }

.hint { color: var(--on-surface-variant); font: var(--t-body-m); }
.lede { color: var(--on-surface-variant); max-width: 70ch; margin-bottom: 1.25rem; }
.row { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }
.stack { display: grid; gap: 1.25rem; min-width: 0; }

/* A date and its time never break ("13 Oct 2026, 1:51 am IST" does not end one line with "1:51"). */
time { font-variant-numeric: tabular-nums lining-nums; white-space: nowrap; }

/* Motion is opt-in: nothing moves for people who ask for less motion. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation: none !important;
    scroll-behavior: auto !important;
  }
}

/* More contrast on request: muted text becomes full text, dividers become outlines. */
@media (prefers-contrast: more) {
  :root { --on-surface-variant: var(--on-surface); --divider: var(--outline); --outline-variant: var(--outline); }
}

/* Windows High Contrast: selection and focus use system colours; chips keep a border. */
@media (forced-colors: active) {
  :focus-visible { outline: 3px solid Highlight; }
  [aria-current="page"] { outline: 2px solid Highlight; }
  .status, .card, .fsec, .banner, .toast { border: 1px solid CanvasText; }
}

/* Components (web/STYLE.md lists each with its markup). Later pages use these classes and never hand-roll styles. */

/* ---- Buttons: pill at rest, square off to the field radius while pressed (fast spring). 41 px, never under 40. ---- */
.btn {
  --h: var(--target);
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  min-height: var(--h); min-width: var(--target); padding: 0 1.5rem;
  border: 0; border-radius: calc(var(--h) / 2); background: transparent; color: var(--primary);
  font: var(--t-label-l); text-decoration: none; white-space: nowrap; cursor: pointer;
  position: relative; isolation: isolate;
  transition: border-radius var(--d-spring-fast) var(--spring-fast), box-shadow var(--d-short) var(--ease-effects);
}
.btn::after {
  content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit;
  background: currentColor; opacity: 0; transition: opacity var(--d-short) var(--ease-effects);
}
.btn:hover::after { opacity: 0.08; }
.btn:active::after { opacity: 0.12; }
.btn:active { border-radius: var(--r-md); }
.btn .icon { width: 1.2rem; height: 1.2rem; }
.btn-filled { background: var(--primary); color: var(--on-primary); }
.btn-filled:hover { box-shadow: var(--e-1); }
.btn-tonal { background: var(--secondary-container); color: var(--on-secondary-container); }
.btn-outlined { box-shadow: inset 0 0 0 1px var(--outline); }
.btn-text { padding: 0 0.75rem; }
.btn-danger { box-shadow: inset 0 0 0 1px var(--danger); color: var(--danger); }
.btn-m { --h: 3.2rem; padding: 0 1.75rem; font: var(--t-title-m); }
.btn-m:active { border-radius: var(--r-lg); }
.btn-m .icon { width: 1.4rem; height: 1.4rem; }
.btn-wide { width: 100%; }
/* A button whose words come from a record's name and may be long ("Arm A: 10 minutes after publishing"): they wrap
   instead of running off a phone. */
.btn-wrap { white-space: normal; padding-block: 0.5rem; text-align: start; }

/* On a phone an action button takes the width it can, so none stands alone beside empty space: in a row of buttons
   (.row: Try again, Open in Upwork and Mark as submitted in Upwork) one fills the row, two short ones share it, and two
   that do not fit take a row each. A text button (Copy, a link) keeps its own width. The page header, a submit row,
   the action bar, a dialog and a table's action cell do the same in their own rules. */
@media (max-width: 39.99rem) {
  .row > .btn-filled, .row > .btn-tonal, .row > .btn-outlined, .row > .btn-danger { flex: 1 1 auto; }
}

/* ---- Connected button group (radio buttons): 2 px apart, outer ends round, the chosen one fully round ---- */
.choice-group { display: grid; gap: 0.5rem; min-width: 0; margin: 0; padding: 0; border: 0; }
.choice-group legend { padding: 0; margin-bottom: 0.5rem; font: var(--t-label-l); }
.choice-row { display: flex; flex-wrap: wrap; gap: 2px; }
.choice {
  position: relative; display: inline-flex; align-items: center; min-height: var(--target); padding: 0 1.125rem;
  border-radius: var(--r-sm); background: var(--surface-container-high); color: var(--on-surface-variant);
  font: var(--t-label-l); cursor: pointer;
  transition: border-radius var(--d-spring-fast) var(--spring-fast), background-color var(--d-short) var(--ease-effects);
}
.choice:first-child { border-start-start-radius: calc(var(--target) / 2); border-end-start-radius: calc(var(--target) / 2); }
.choice:last-child { border-start-end-radius: calc(var(--target) / 2); border-end-end-radius: calc(var(--target) / 2); }
.choice:hover { background: color-mix(in srgb, var(--on-surface) 8%, var(--surface-container-high)); }
.choice:has(input:checked) { background: var(--primary); color: var(--on-primary); border-radius: calc(var(--target) / 2); }
.choice:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
/* A segment that cannot be chosen now (the page says why beside the group): muted, no hover. */
.choice:has(input:disabled), .choice:has(input:disabled):hover { background: var(--surface-container); cursor: not-allowed; }
.choice input:disabled { cursor: not-allowed; }
.choice .icon { width: 1.2rem; height: 1.2rem; }
/* The radio button covers its whole segment: invisible, but it is what the keyboard and screen readers use. */
.choice input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
/* Four options stand two and two, never three and one (what a new experiment tests): equal segments in two columns,
   the group's four outer corners round. A segment's words may take two lines on a phone. */
.choice-pairs .choice-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.choice-pairs .choice { padding-block: 0.375rem; }
.choice-pairs .choice:first-child { border-end-start-radius: var(--r-sm); }
.choice-pairs .choice:nth-child(2) { border-start-end-radius: calc(var(--target) / 2); }
.choice-pairs .choice:nth-last-child(2) { border-end-start-radius: calc(var(--target) / 2); }
.choice-pairs .choice:last-child { border-start-end-radius: var(--r-sm); }
.choice-pairs .choice:has(input:checked) { border-radius: calc(var(--target) / 2); }
/* A group whose segments do not fit one line stacks instead of wrapping ragged: one column of full-width segments
   2 px apart, the first round on top, the last at the bottom, the chosen one still a full pill. .choice-stack
   stacks below 40rem (two long choices: who answers a Selection Prompt); .choice-stack-m below 80rem (three long
   choices in a form section: a Scanner's autopilot). A stacked group is 28rem at most, as a dropdown is. The two
   blocks below are the same rules at two widths. */
@media (max-width: 39.99rem) {
  .choice-stack .choice-row { display: grid; grid-template-columns: minmax(0, 1fr); max-width: 28rem; }
  .choice-stack .choice { padding-block: 0.375rem; border-radius: var(--r-sm); }
  .choice-stack .choice:first-child { border-start-start-radius: calc(var(--target) / 2); border-start-end-radius: calc(var(--target) / 2); }
  .choice-stack .choice:last-child { border-end-start-radius: calc(var(--target) / 2); border-end-end-radius: calc(var(--target) / 2); }
  .choice-stack .choice:has(input:checked) { border-radius: calc(var(--target) / 2); }
}
@media (max-width: 79.99rem) {
  .choice-stack-m .choice-row { display: grid; grid-template-columns: minmax(0, 1fr); max-width: 28rem; }
  .choice-stack-m .choice { padding-block: 0.375rem; border-radius: var(--r-sm); }
  .choice-stack-m .choice:first-child { border-start-start-radius: calc(var(--target) / 2); border-start-end-radius: calc(var(--target) / 2); }
  .choice-stack-m .choice:last-child { border-end-start-radius: calc(var(--target) / 2); border-end-end-radius: calc(var(--target) / 2); }
  .choice-stack-m .choice:has(input:checked) { border-radius: calc(var(--target) / 2); }
}

/* ---- Status chips: icon + word on a tonal container (crate::components::status) ---- */
.status {
  display: inline-flex; align-items: center; min-height: 1.875rem; padding: 0 0.75rem 0 0.5rem;
  border-radius: var(--r-full); font: var(--t-label-m); white-space: nowrap; vertical-align: middle;
  background: var(--neutral-container); color: var(--on-neutral-container);
}
.status .icon-label { gap: 0.375rem; }
.status .icon { width: 1.1rem; height: 1.1rem; }
.status-ok { background: var(--ok-container); color: var(--on-ok-container); }
.status-warn { background: var(--warn-container); color: var(--on-warn-container); }
.status-danger { background: var(--danger-container); color: var(--on-danger-container); }
.status-info { background: var(--info-container); color: var(--on-info-container); }
.status-neutral { background: var(--neutral-container); color: var(--on-neutral-container); }
/* A figure after a chip's word ("Follow-ups current: 92%", crate::components::status_value). */
.status-value { margin-inline-start: 0.25rem; font-variant-numeric: tabular-nums lining-nums; }
/* The note line directly above a report: a chip, then a sentence or two (how current follow-up is, and during the
   trial the double count). From 40rem the sentence stands beside the chip; on a phone under it. */
.note-line { display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem 0.75rem; min-width: 0; color: var(--on-surface-variant); font: var(--t-body-m); }
.note-line > span:last-child { flex: 1 1 18rem; min-width: 0; }

/* A count next to a menu item (Review Queue 3). The count is text; the name says what it counts. Its words never
   break inside the pill ("over 100" is one line, 70 px at the 15 px root) and a row never squeezes it (flex: none):
   where a row has too little room, the name beside it wraps, or the count takes a line of its own. */
.badge {
  display: inline-grid; place-items: center; flex: none; min-width: 1.4rem; height: 1.4rem; padding: 0 0.375rem;
  border-radius: 0.7rem; background: var(--danger); color: var(--on-danger); font: var(--t-label-m);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* ---- Cards and form sections: tonal, large corners (--r-lg-plus), no border ---- */
.card { display: grid; gap: 1rem; min-width: 0; padding: 1.25rem; border-radius: var(--r-lg-plus); background: var(--card); }
.card-title { font: var(--t-title-l); }
/* A card in the danger tone: what went wrong, where the page's work stopped, with the one button that fixes it (a
   job at Exception). The banner is for a message; this is a block with its own heading and action. */
.card-danger { background: var(--danger-container); color: var(--on-danger-container); }
.fsec { display: grid; gap: 1rem; min-width: 0; padding: 1.25rem; border-radius: var(--r-lg-plus); background: var(--card); }
.fsec-head h2 { font: var(--t-title-m); }
.fsec-head .hint { margin-top: 0.25rem; }
/* align-content: start, so a lone button is not stretched to the height of the hint beside it. The body is 45rem
   at most at every width (from 64rem that is its grid column; below, where the heading stands above it, a card can
   be 55rem wide), so a form grid's fields share the same rows wherever they stand side by side. */
.fsec-body { display: grid; gap: 1.25rem; align-content: start; min-width: 0; max-width: 45rem; }
@media (min-width: 64rem) {
  .card { padding: 1.75rem; }
  .fsec { grid-template-columns: 14rem minmax(0, 45rem); gap: 1rem 2rem; padding: 1.75rem; }
}

/* Two short cards side by side from 64rem (System Health: the letter writer and GigRadar), each as tall as the
   taller; one under the other below. */
.pair-grid { display: grid; gap: 1.25rem; min-width: 0; }
.pair-grid > * { min-width: 0; }
/* The shorter card is as tall as the taller, and what it holds starts at its top instead of being spread over it. */
.pair-grid > .card { align-content: start; }
@media (min-width: 64rem) { .pair-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* A row of state chips that are links to their cards (System Health's first row): each link is a 41 px target
   around its chip, and the chip says what it is the state of ("Webhook deliveries: Problems found"). */
.chip-links { display: flex; flex-wrap: wrap; gap: 0 0.5rem; margin: 0; padding: 0; list-style: none; }
.chip-link { display: inline-flex; align-items: center; min-height: var(--target); border-radius: var(--r-full); text-decoration: none; }
.chip-link .status { white-space: normal; }
.chip-link:hover .status { box-shadow: inset 0 0 0 1px currentColor; }

/* ---- Facts: label and value rows ---- */
.facts { display: grid; }
.facts > div {
  display: grid; grid-template-columns: minmax(8rem, 12rem) minmax(0, 1fr); gap: 0.75rem;
  padding: 0.625rem 0; border-bottom: 1px solid var(--divider);
}
.facts > div:last-child { border-bottom: 0; }
.facts dt { color: var(--on-surface-variant); }
.facts dd { font: var(--t-label-l); overflow-wrap: anywhere; }
@media (max-width: 39.99rem) { .facts > div { grid-template-columns: minmax(0, 1fr); gap: 2px; } }
/* Facts grid: short values (a chip, a date, a number, a name) with the label above, three or four across in a main
   column, two in a side rail and on a phone. No row hairlines. A long value takes the whole row (.fact-wide). */
.facts-grid { grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr)); gap: 0.75rem 1.5rem; }
.facts-grid > div { grid-template-columns: minmax(0, 1fr); gap: 2px; align-content: start; padding: 0; border-bottom: 0; }
.facts-grid > .fact-wide { grid-column: 1 / -1; }
/* A chip longer than its column wraps inside it instead of running over the next fact. A value that takes two lines
   in a narrow column takes two even ones (a date stands over its time, not over its zone alone). */
.facts-grid .status { white-space: normal; }
.facts-grid dd { text-wrap: balance; }
/* Everywhere else a date and its time never break (base.css); in a narrow column of a facts grid, and in a table
   cell of a few words, they may take two even lines. */
.facts-grid time, .table .col-words time { white-space: normal; }
/* A second, lesser fact under a value (why an experiment stopped, under when) wraps in its fact's column. */
.facts-grid .cell-note { width: auto; max-width: none; }
/* C6: a date under a value (a snippet's Saved by and Decided by) may take two lines there too, as a date of its own does. */
.facts-grid .cell-note time { white-space: normal; }
@media (max-width: 39.99rem) { .facts-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Facts whose label is a sentence and whose value is a chip or a few words (a Scanner's hard checks): the sentence
   at the start and its value at the end of the same line, from 40rem; on a phone the value stands under it. */
.facts-end > div { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
.facts-end dt { color: var(--on-surface); }
@media (max-width: 39.99rem) { .facts-end > div { grid-template-columns: minmax(0, 1fr); } }

/* ---- Forms and fields: label above, 45 px outlined fields (--control-h) with --r-md corners ---- */
.form { display: grid; gap: 1.125rem; min-width: 0; }
.form-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* Two buttons that send different forms (Replay, Mark as reviewed) stand in one row: each form is drawn as its
   button alone, as in the page header. */
.form-actions > form { display: contents; }
/* On a phone a submit row's buttons share the width: one button fills the row, two short ones stand side by side,
   and two that do not fit one row take a row each. No button stands alone beside empty space. */
@media (max-width: 39.99rem) { .form-actions > .btn, .form-actions > form > .btn { flex: 1 1 auto; } }
.field { display: grid; gap: 0.375rem; min-width: 0; }
.field > label { font: var(--t-label-l); }
/* E5: a link (`type="url"`, the Knowledge page's Add) is drawn as every text field. */
input[type="text"], input[type="email"], input[type="url"], input[type="password"], .field input[type="search"], select {
  width: 100%; min-height: var(--control-h); padding: 0 0.875rem;
  border: 1px solid var(--outline); border-radius: var(--r-md); background: var(--card); color: var(--on-surface);
  font: var(--t-body-l); transition: border-color var(--d-short) var(--ease-effects);
}
input[type="text"]:hover, input[type="email"]:hover, input[type="url"]:hover, input[type="password"]:hover,
.field input[type="search"]:hover, select:hover { border-color: var(--on-surface); }
/* No dropdown is ever page-wide, even in a template that gives it no width class. */
select { max-width: 28rem; }
input[aria-invalid="true"] { border-color: var(--danger); box-shadow: inset 0 0 0 1px var(--danger); }
.field-problem { color: var(--danger); font: var(--t-label-l); }
/* A problem of several lines keeps its icon on the first line, not beside the middle of the block. */
.field-problem .icon-label { align-items: flex-start; }
.field-problem .icon { width: 1.1rem; height: 1.1rem; margin-top: 0.2rem; }

/* ---- Content widths: a stand-alone form or reading column that is not inside a form section ---- */
.measure { max-width: 60rem; min-width: 0; }

/* ---- Form grid (2026-10-12): fields side by side, each as wide as what it holds; one column on a phone. Every
   child carries a width class: -s (numbers, days, short fixed words), -m (a name, a stage, a date, a password),
   -l (a named record, an email, a one-line title), -full (links, long text, a hint a row shares). A child with no
   width class takes the whole row.
   20rem and 28rem are the GREATEST widths of -m and -l: each gives up to a fifth of it away (down to 16rem and
   22rem) before it wraps, so two medium fields, or a long and a short one, still share a row in a form section that
   is narrower than 45rem (574 px at a 1024 px window). ---- */
.form-grid { display: flex; flex-wrap: wrap; gap: 1.125rem 1rem; align-items: flex-start; min-width: 0; }
.form-grid > * { flex: 1 1 100%; min-width: 0; }
.form-grid > .field-s { flex: 0 1 12rem; }
.form-grid > .field-m { flex: 1 1 16rem; max-width: 20rem; }
.form-grid > .field-l { flex: 1 1 22rem; max-width: 28rem; }
.form-grid > .field-full { flex: 1 1 100%; }
/* A connected group of two or three is as wide as its segments (-fit), so two groups, or a group and a field,
   share a row; with -full, or no width class, it has the row to itself. */
.form-grid > .field-fit { flex: 0 1 auto; max-width: 100%; }
/* One field and its button on one line, the button as tall as the control and level with it. Its hint goes under
   the row. On a phone the button drops under the field at full width. */
.field-row { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: flex-end; min-width: 0; }
.field-row > .field { flex: 1 1 12rem; }
/* A field with a width class keeps that width beside its button (an owner select, a Scanner select). */
.field-row > .field-s { flex: 0 1 12rem; }
.field-row > .field-m { flex: 0 1 20rem; }
.field-row > .field-l { flex: 0 1 28rem; }
.field-row > .btn { --h: var(--control-h); flex: none; }
/* A problem of a field that shares its row with a button is the row's last part, not the field's: it takes a line of
   its own under both, as close as a problem is to its control inside a field, so the button stays level with the
   control. On a phone, where the button drops under the field, the problem stands between them, under its field. */
.field-row > .field-problem { flex: 1 1 100%; margin-top: -0.375rem; }
@media (max-width: 39.99rem) {
  .form-grid > .field-s, .form-grid > .field-m, .form-grid > .field-l { flex-basis: 100%; max-width: none; }
  .form-grid > .field-fit { flex-basis: 100%; }
  /* A date field fills its row like its neighbours (its 16rem cap stays from 40rem). */
  .form-grid input[type="date"] { max-width: none; }
  .field-row > .field, .field-row > .btn { flex: 1 1 100%; }
  .field-row > .field-problem { order: 1; }
  .field-row > .btn { order: 2; }
}
/* The sticky bar of a form longer than about two screens, and of the job page at Review: the main action first. */
.action-bar {
  position: sticky; bottom: 0; z-index: 15; display: flex; flex-wrap: wrap; gap: 0.5rem;
  padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom));
  border-radius: var(--r-lg) var(--r-lg) 0 0; background: var(--surface-container-high); box-shadow: var(--e-2);
}
/* The bar's height, for what must keep clear of it: a field that takes focus scrolls clear of the bar (WCAG
   2.4.11), and the toast stands above it. app.js measures the bar and sets --action-bar-h on <html>; without script
   the tallest bar is assumed: one row of buttons, or on a phone two rows (the job page at Review). The room kept
   under a focused field is never less than the 6rem every page keeps.
   On a phone the bar is two rows at most, so the letter under it keeps the screen: the filled button has the first
   row, the others stand two across (a lone one fills its row). A button that would make a third row leaves the
   bar on a phone (.phone-hidden) and stands in the page instead (.phone-only: "Write new drafts", at the end of
   step 1). */
html:has(.action-bar) { --action-bar-h: 4.5rem; scroll-padding-block-end: max(6rem, calc(var(--action-bar-h) + 1.5rem)); }
/* U1 (spec 15, #16): a page with the bar ends with room as tall as the bar, so its last field and heading scroll clear
   of it at every width. */
main:has(.action-bar) { padding-block-end: var(--action-bar-h); }
@media (max-width: 39.99rem) {
  .action-bar > .btn { flex: 1 1 calc(50% - 0.25rem); min-width: 0; padding-inline: 0.75rem; }
  .action-bar > .btn-filled { flex-basis: 100%; }
  html:has(.action-bar) { --action-bar-h: 8rem; }
}
/* A part drawn on a phone only, and a part drawn everywhere but on a phone. Only one of the two is ever in the
   page for the eye, the keyboard and a screen reader. */
@media (min-width: 40rem) { .phone-only { display: none !important; } }
@media (max-width: 39.99rem) { .phone-hidden { display: none !important; } }

/* ---- Order grid: one small number per named thing (a Scanner's Writer Profiles in drafting order). Each is a
   5rem number field with the thing's name as its label beside it; three sit in a row in a form section, two at a
   1024 px window, one on a phone. The hint stands once, under the legend of the group. ---- */
.order-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(12rem, 100%), 1fr)); gap: 0.75rem 1rem; min-width: 0; }
.order-field { display: flex; align-items: center; gap: 0.75rem; min-width: 0; }
.order-field > input[type="number"] { flex: none; width: 5rem; }
.order-field > label { min-width: 0; font: var(--t-label-l); overflow-wrap: anywhere; }

/* ---- Two short fields that belong together (jobs to aim for and the stop target, both per arm): side by side on a
   phone too, as the one exception a form grid makes there. Their labels, controls and hints share three rows
   (subgrid), so the two controls stay level when one label takes two lines. A problem of either stands under the
   pair (`field-problem field-full`), not inside a field. ---- */
.field-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.375rem 1rem; min-width: 0; }
.field-pair > .field { grid-row: span 3; grid-template-rows: subgrid; }
.field-pair > .field > label { align-self: end; }
.form-grid > .field-pair { flex: 0 1 25rem; }
@media (max-width: 39.99rem) { .form-grid > .field-pair { flex-basis: 100%; } }

/* ---- Arm grid (a new experiment's arms): one row per arm from 40rem: the arm, its share (a 7rem number) and what
   it uses (12rem to 22rem, so no dropdown is page-wide), or the minutes it waits. Each arm is a fieldset with its
   legend; the legend is floated, which makes it an ordinary part of the row. The column labels show once, above the
   first row: every input keeps its own label, clipped (not removed) from the second row on, so a screen reader
   still reads it. On a phone an arm is its legend and one row: the share (6.5rem, with a shorter label) and what
   it uses in the rest. ---- */
.arm-grid { display: grid; gap: 0.75rem; min-width: 0; }
.arm {
  position: relative; display: grid; grid-template-columns: 6.5rem minmax(0, 1fr); gap: 0.375rem 0.75rem; align-items: end;
  min-width: 0; margin: 0; padding: 0; border: 0;
}
.arm > legend { float: inline-start; grid-column: 1 / -1; width: 100%; padding: 0; font: var(--t-label-l); }
@media (min-width: 40rem) {
  .arm { grid-template-columns: 4.5rem 9rem minmax(12rem, 22rem); column-gap: 1rem; }
  .arm-grid-timing .arm { grid-template-columns: 4.5rem 9rem 13rem; }
  .arm > legend { grid-column: auto; width: auto; display: flex; align-items: center; min-height: var(--control-h); }
  .arm input[type="number"] { max-width: 7rem; }
  .arm + .arm .field > label {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
}

/* ---- Filter bar (c::filter_bar): a GET form in a slim card with no heading. From 40rem the selects share one row
   and the buttons end it, on a row of their own only when they do not fit. On a phone the selects sit two across
   (each holds one short word; five stacked rows would push the list a screen down) and the buttons take a full row. ---- */
.filter-bar { min-width: 0; padding: 0.75rem 1rem; border-radius: var(--r-lg-plus); background: var(--card); }
.filter-fields {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem 1rem; align-items: end;
  min-width: 0; margin: 0; padding: 0; border: 0;
}
/* A filter whose choices are long words ("Writer Profile and version") has a row of its own on a phone, so the
   chosen one is not cut. From 40rem the bar is a flex row and this does nothing. */
.filter-fields > .filter-wide { grid-column: 1 / -1; }
/* The last of an odd number of filters takes the row too, so none stands beside an empty half (All Jobs' three, the
   Pipeline's five). A wide filter has its row anyway and is not counted (All Jobs' search field stands before its
   three selects, 2026-10-16). */
.filter-fields > .field:not(.filter-wide):nth-last-child(1 of .field:not(.filter-wide)):nth-child(odd of .field:not(.filter-wide)) { grid-column: 1 / -1; }
.filter-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; grid-column: 1 / -1; }
.filter-actions > .btn { flex: 1 1 auto; }
/* Two across on a phone a select is about 140 px wide: with less padding its chosen words are whole at 360 px
   ("Last 365 days" is 95 px beside the arrow). */
@media (max-width: 39.99rem) { .filter-fields select { padding-inline: 0.5rem; } }
@media (min-width: 40rem) {
  .filter-fields { display: flex; flex-wrap: wrap; align-items: flex-end; }
  .filter-fields > .field { flex: 1 1 9rem; max-width: 16rem; }
  .filter-actions { flex-wrap: nowrap; margin-inline-start: auto; }
  .filter-actions > .btn { flex: none; }
}

/* ---- Banners (in the page), toasts (after a change) ---- */
.banner { display: flex; padding: 0.875rem 1rem; border-radius: var(--r-lg); font: var(--t-body-m); }
.banner .icon-label { align-items: flex-start; gap: 0.75rem; }
.banner .icon { width: 1.4rem; height: 1.4rem; }
.banner-danger { background: var(--danger-container); color: var(--on-danger-container); }
.banner-info { background: var(--info-container); color: var(--on-info-container); }
.banner-ok { background: var(--ok-container); color: var(--on-ok-container); }
/* C2: a Scheduled job paused (spec 6.10): a warning that waits for a person, not an error. */
.banner-warn { background: var(--warn-container); color: var(--on-warn-container); }

.toast {
  position: fixed; inset-inline: 0.75rem; bottom: calc(1rem + env(safe-area-inset-bottom)); z-index: 40; max-width: 33rem; margin-inline: auto;
  display: flex; align-items: center; min-height: 2.8rem; padding: 0.75rem 1rem;
  border-radius: var(--r-sm); background: var(--inverse-surface); color: var(--inverse-on-surface);
  font: var(--t-body-m); box-shadow: var(--e-3);
}
.toast .icon { color: var(--inverse-primary); }
.toast-static { position: static; margin: 0; }
/* Things fixed to the bottom do not cover each other. On a phone the toast stands above the floating "New job"
   button where a page has one (the Dashboard and the Jobs lists); at every width it stands above the sticky action
   bar where a page has one (the job page at Review, a long form). Elsewhere it keeps its place at the bottom. */
@media (max-width: 39.99rem) { body:has(.fab) .toast:not(.toast-static) { bottom: calc(5.5rem + env(safe-area-inset-bottom)); } }
@media (min-width: 40rem) { .toast:not(.toast-static) { inset-inline: calc(var(--rail-w) + 1.5rem) auto; bottom: 1.5rem; margin: 0; } }
@media (min-width: 80rem) { .toast:not(.toast-static) { inset-inline-start: calc(var(--sidebar-w) + 1.5rem); } }
body:has(.action-bar) .toast:not(.toast-static) { bottom: calc(var(--action-bar-h) + 0.75rem); }
@media (prefers-reduced-motion: no-preference) {
  .toast:not(.toast-static) {
    animation: toast-in var(--d-spring) var(--spring-default), toast-out 300ms var(--ease-standard) 8s forwards;
  }
  .toast:hover, .toast:focus-within { animation-play-state: paused; }
}
/* Only movement on the way in: the text keeps full contrast from the first frame. */
@keyframes toast-in { from { translate: 0 1.5rem; } }
@keyframes toast-out { to { opacity: 0; visibility: hidden; } }

/* ---- Dialogs: --r-xl corners; a bottom sheet on phones ---- */
dialog.dialog {
  width: min(33rem, calc(100vw - 2rem)); padding: 1.5rem; border: 0; border-radius: var(--r-xl);
  background: var(--surface-container-high); color: var(--on-surface); box-shadow: var(--e-3);
}
dialog.dialog::backdrop { background: var(--scrim); }
/* An open dialog is a grid: its title, text, fields and buttons keep one gap (a closed one stays display: none). */
dialog.dialog[open] { display: grid; gap: 0.75rem; }
.dialog h2 { font: var(--t-headline-s); }
.dialog p { color: var(--on-surface-variant); }
.dialog-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem; margin-top: 0.75rem; }
@media (max-width: 39.99rem) {
  /* A bottom sheet: the buttons stack at full width, the main action (the last in the HTML) on top, above the
     phone's home bar. */
  dialog.dialog[open] {
    margin: auto 0 0; width: 100%; max-width: 100%; border-radius: var(--r-xl) var(--r-xl) 0 0;
    padding-bottom: calc(1.5rem + env(safe-area-inset-bottom));
  }
  .dialog-actions { flex-direction: column-reverse; flex-wrap: nowrap; }
  .dialog-actions > .btn { width: 100%; }
}
@media (prefers-reduced-motion: no-preference) { dialog.dialog[open] { animation: dialog-in var(--d-spring) var(--spring-default); } }
@keyframes dialog-in { from { opacity: 0; scale: 0.94; } }

/* ---- Tables: calm, hairlines only, 49 px rows (--row-h). A record table (.table-stack, below) becomes short rows
   where its card cannot hold a table; a figures or a technical table scrolls sideways inside its card. ---- */
.table-card { min-width: 0; padding: 0.25rem 0 0.5rem; border-radius: var(--r-lg-plus); background: var(--card); }
/* The scrolling area is positioned: text inside a table that is hidden but read out (.visually-hidden is absolute)
   is then clipped with the table, and cannot widen the page when a table is wider than its card. */
.table-scroll { position: relative; overflow-x: auto; overscroll-behavior-x: contain; container-type: inline-size; }
.table-scroll:focus-visible { outline-offset: -3px; }
.table { width: 100%; border-collapse: separate; border-spacing: 0; font: var(--t-body-m); }
/* The caption is never wider than the card (100cqi, the scrolling area's own width) and stays put while a wide table
   scrolls sideways under it, so its words are not cut at the card's edge on a phone. */
.table caption {
  position: sticky; inset-inline-start: 0; max-width: 100cqi;
  padding: 1rem 1.25rem 0.5rem; text-align: start; color: var(--on-surface-variant);
}
.table th {
  height: 2.8rem; padding: 0 0.75rem; text-align: start; white-space: nowrap;
  font: var(--t-title-s); color: var(--on-surface-variant); border-bottom: 1px solid var(--outline-variant);
}
.table td { height: var(--row-h); padding: 0.25rem 0.75rem; vertical-align: middle; border-bottom: 1px solid var(--divider); }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr { transition: background-color var(--d-short) var(--ease-effects); }
.table tbody tr:hover { background: color-mix(in srgb, var(--on-surface) var(--state-hover), transparent); }
.table tr > :first-child { padding-inline-start: 1.25rem; }
.row-link { display: inline-flex; align-items: center; min-height: var(--target); min-width: var(--target); font: var(--t-title-s); }
.card .table-card { padding: 0; background: none; }
/* A table's download (c::table_card with an address): one "Download CSV" button of the card. Where the card is
   36rem or wider (a container query: the card's own width, in the root's rem) it stands at the end of the caption's
   row, and the caption keeps clear of it; in a narrower card (a phone, a table beside its chart) under the table. */
.table-card-tools { position: relative; container-type: inline-size; }
.table-tools { display: flex; flex-wrap: wrap; gap: 0 0.25rem; padding: 0.25rem 0.5rem 0; }
.card .table-tools { padding-inline: 0; }
@container (min-width: 36rem) {
  .table-tools { position: absolute; inset-block-start: 0.375rem; inset-inline-end: 0.5rem; padding: 0; }
  .card .table-tools { inset-block-start: 0.125rem; inset-inline-end: 0; }
  .table-card-tools caption { padding-inline-end: 11.5rem; }
  /* Two tools (a sorted table that has a download): "Default order" and "Download CSV" stand side by side, and
     the caption keeps clear of both. */
  .table-card-tools:has(.table-tools > :nth-child(2)) caption { padding-inline-end: 20.5rem; }
}
/* A narrow table with a download fills its card, which is as wide as such a table may be (40rem): the caption has
   the card's width and the button ends its row, instead of standing a card's width away from a table of 20rem. */
.table-card-tools:not(.table-card-reset):has(.table-fit) { max-width: 40rem; }
.table-card-tools:not(.table-card-reset) .table-fit { width: 100%; }
/* A narrow table whose only tool is "Default order" (sorted, no download) keeps its own width, so its columns do
   not move with every sort: the button stands under it at every width, where a phone has it anyway. */
.table-card-reset:has(.table-fit) .table-tools { position: static; padding: 0.25rem 0.5rem 0; }
.card .table-card-reset:has(.table-fit) .table-tools { padding-inline: 0; }
.table-card-reset:has(.table-fit) caption { padding-inline-end: 1.25rem; }

/* ---- Sortable tables (c::sort_th, c::sort_by; web/STYLE.md, "Sortable tables"). A sortable header's word is a
   link: the header's own colour and type, no underline at rest, a 41 px target, the usual focus ring. The sorted
   column (aria-sort) is drawn in the stronger colour and carries the arrow beside its word, so only that column
   gains the arrow's width and a table measured to fit its card still fits. ---- */
.sort-link {
  display: inline-flex; align-items: center; gap: 0.25rem; min-height: var(--target); min-width: var(--target);
  color: inherit; font: inherit; text-decoration: none;
}
.sort-link:hover { text-decoration: underline; }
.sort-link .icon { width: 1em; height: 1em; }
th[aria-sort] .sort-link { color: var(--on-surface); }
/* A number's header ends where its numbers end. */
.table th.num .sort-link { justify-content: flex-end; }
/* Headings that take two lines (.table-wrap-heads): the link wraps with the heading, the arrow stays beside the
   last line and never takes a line of its own. The link keeps the 41 px target by taking the header's own padding. */
.table-wrap-heads thead th:has(.sort-link) { padding-block: 0; }
.table-wrap-heads .sort-link { align-items: flex-end; padding-block: 0.5rem; text-align: inherit; }
.table-wrap-heads .sort-link .icon { margin-block-end: 0.2em; }
/* A heading that wraps at the START of its row (a two-word heading of the column that stays in place, "Experiment
   arm"; a narrow table's first heading on a phone, "Writer Profile"): its link is a block of text, not a row of
   two parts, so the arrow follows the last word instead of standing at the column's end, a word's width away from
   its heading. The arrow takes no room of its own in the line (it stands in the link's end padding), so it never
   wraps to a line by itself. The block is still the 41 px target, its words centred in it. The rules stand below,
   where each of those two columns is set (.table-fit on a phone, .table-sticky in a narrow card). */

/* The "Sort by" control (c::sort_by): a slim card like the filter bar, directly above its table's card. It is
   drawn only where the table is short rows and shows no header row: below 64rem (.table-stack), or below 72rem
   with .sort-by-m (.table-stack-m). On a phone the select has a row and the two direction buttons share the next;
   from 40rem they are one row where the card has the room (a 482 px card at a 640 px window has not: the buttons
   then stand under the select at their own width, and nothing runs off the card). The current direction is the
   tonal button. */
.sort-by {
  display: none; flex-wrap: wrap; align-items: flex-end; gap: 0.75rem 1rem;
  min-width: 0; padding: 0.75rem 1rem; border-radius: var(--r-lg-plus); background: var(--card);
}
.sort-by > .field { flex: 1 1 100%; }
.sort-by-dirs { display: flex; flex: 1 1 100%; gap: 0.5rem; min-width: 0; }
.sort-by-dirs > .btn { flex: 1 1 0; }
@media (max-width: 63.99rem) { .sort-by { display: flex; } }
@media (max-width: 71.99rem) { .sort-by-m { display: flex; } }
/* A sort may land on its table (the address ends "#<caption id>": a table far down its page). Where the control is
   drawn it stands directly above that table's card, so the landing leaves room for it: the control that sorted the
   list is still on screen above the list, ready for the next press. */
@media (max-width: 63.99rem) { .sort-by + .table-card caption { scroll-margin-block-start: 13rem; } }
@media (max-width: 71.99rem) { .sort-by-m + .table-card caption { scroll-margin-block-start: 13rem; } }
@media (min-width: 40rem) {
  .sort-by > .field { flex: 0 1 16rem; }
  .sort-by-dirs { flex: 0 1 auto; }
  .sort-by-dirs > .btn { flex: none; }
}
/* A row header (th in tbody, or in tfoot: a totals row such as Insights > Screening's "All profiles") is drawn like a
   cell; only the column headers in thead keep the header style. */
.table tbody th, .table tfoot th {
  height: var(--row-h); padding: 0.25rem 0.75rem; vertical-align: middle; white-space: normal;
  color: var(--on-surface); border-bottom: 1px solid var(--divider);
}
.table tbody tr:last-child th { border-bottom: 0; }
/* A totals row stands under a rule of its own, apart from the rows it sums. */
.table tfoot tr:last-child > * { border-top: 1px solid var(--outline-variant); border-bottom: 0; }

/* Columns (2026-10-12). In a record table the title cell takes the free width (.col-main, at most two lines) and
   every other cell is as narrow as what it holds and never wraps (.col-tight), so dates, chips and names stay on
   one line; the action cell is last (.col-actions). One long-text column per table may wrap (.col-text). A note goes
   under its chip (.cell-note). Numbers and their headers line up at the end (.num). */
.table .col-main { min-width: 16rem; }
/* In a card narrower than 72rem (a 972 px card at a 1280 px window) the title gives up to 3rem of that away before
   the table grows wider than its card: a wide chip ("Would submit (trial)", "With GigRadar, waiting to be sent"), a
   long owner's name or the arrow of a sorted column then still fit, and the title keeps its two lines. */
@container (max-width: 72rem) { .table .col-main { min-width: 13rem; } }
.table .col-tight { width: 1%; white-space: nowrap; }
.table .col-actions { width: 1%; white-space: nowrap; text-align: end; }
/* An action cell that holds a next-stage control (Follow-ups) may put its link on a line of its own while its CARD
   is narrower than 72rem (1080 px: in a container query a rem is the root's 15 px); the control's two buttons still
   stay together. The card's width decides, not the window's: at a 1280 px window the expanded rail appears and the
   card is 972 px, narrower than at 1152 px, so "below 80rem" cut the More button off at the most common laptop
   width. Every other action cell keeps its buttons on one line. */
@container (max-width: 72rem) { .table .col-actions:has(.move-actions) { white-space: normal; } }
/* To decide's row actions (C0): three buttons that open the shared dialogs. In a card narrower than 72rem they take
   lines of their own instead of widening the table past its card. */
@container (max-width: 72rem) { .table .col-actions:has([data-row-action]) { white-space: normal; } }
.table .col-text { min-width: 12rem; max-width: 60ch; }
/* A cell of a few words (a person, a profile, a mode) in a table of six columns or more: it keeps 8rem and takes a
   second line where the card is narrow, so the table still fits a 972 px card. Chips, numbers and dates stay
   .col-tight. Without the least width the table squeezes such a cell to one word a line. Its lines are balanced
   (a date that must wrap stands over its time), and a word longer than the cell (a link, an address from the old
   app's data) breaks inside it instead of widening the table. Where two dates and four other columns cannot share
   a 972 px card (Users), the dates are .col-words too. */
.table .col-words { min-width: 8rem; overflow-wrap: anywhere; text-wrap: balance; }
/* C2b (owner, 2026-10-17: "names medium"): a name from data (a Scanner, a person, a profile, a time zone, an
   address) is .col-name, never .col-tight: it keeps 8rem and wraps, so production's long names never widen a table
   past its card. */
.table .col-name { min-width: 8rem; overflow-wrap: anywhere; text-wrap: balance; }
/* A technical value (an event type, a task kind, an error code) breaks inside itself rather than widen its table. */
.table td code { overflow-wrap: anywhere; }
/* C2b: a row's buttons stand one under another, as wide as the widest, so a row of three fits its card (To decide);
   in a stacked row they sit side by side and wrap, as every stacked row's buttons do. */
.row-actions { display: flex; flex-direction: column; align-items: stretch; gap: 0.25rem; }
@media (max-width: 71.99rem) { .table-stack-m .row-actions { flex-flow: row wrap; } }
.table-stack-a .row-actions { flex-flow: row wrap; }
/* A note under a chip is as wide as its words up to 18rem: a short one (a word and a date) keeps one line, a
   sentence wraps there instead of stretching its never-wrapping cell. A date and its time never break. Under a
   title (.col-main) a note has the title's own width. */
.cell-note { display: block; width: max-content; max-width: 18rem; white-space: normal; }
.cell-note time { white-space: nowrap; }
.col-main .cell-note { width: auto; max-width: none; overflow-wrap: anywhere; }
/* A run of rows about the same thing (Activity: one job's events) prints its title once: the title cells after the
   first (.col-again) hold it for a screen reader only, and no hairline cuts the title off from its run. */
.table tr:has(+ tr > .col-again) > .col-main { border-bottom-color: transparent; }
.table .num { text-align: end; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* A note under a number (a rate's count, "Too few to judge") stands at the number's edge, on one line. */
.table .num .cell-note { margin-inline-start: auto; }
/* A note under a row header (what an arm uses, the chip of a row with too few jobs) wraps in the header's width. */
.table tbody th .cell-note, .table tfoot th .cell-note { width: auto; max-width: none; font: var(--t-body-m); }
/* Headings of a few words may take two balanced lines, so ten columns of short figures fit their card (an
   experiment's arms); the caption gives the full wording. */
.table-wrap-heads thead th { height: auto; padding-block: 0.5rem; white-space: normal; text-wrap: balance; vertical-align: bottom; }
/* (A number's heading too: .num alone keeps a cell on one line, and would keep "No response" from wrapping.) */
.table-wrap-heads thead th.num { white-space: normal; }
/* Its cells stand 0.5rem apart, not 0.75rem, and a chip on a row's label may wrap: ten columns fit a 920 px card. */
.table-wrap-heads th, .table-wrap-heads td { padding-inline: 0.5rem; }
.table-wrap-heads tr > :first-child { padding-inline-start: 1.25rem; }
.table-wrap-heads tbody th .status { white-space: normal; }
/* A table narrower than its card does not stretch across it. On a phone its cells stand 0.375rem apart and its
   first heading may wrap, so four short columns fit the card of a 360 px phone with the arrow of the sorted one
   (Cost: Writer Profile, Drafts, AI cost, Per draft; 0.5rem before the headers sorted, 2026-10-13). */
.table-fit { width: auto; max-width: 40rem; }
@media (max-width: 39.99rem) {
  .table-fit th, .table-fit td { padding-inline: 0.375rem; }
  .table-fit tr > :first-child { padding-inline-start: 1.25rem; }
  .table-fit thead th:first-child { white-space: normal; }
  /* A row header stands as close as the cells (`.table tbody th` would keep 0.75rem), and a number's heading may take
     two lines, so a long row name (Cost's "Questions First version 1", which wraps) does not push the table past its
     card on a 360 px phone. */
  .table-fit tbody th { padding-inline-end: 0.375rem; }
  .table-fit thead th.num { white-space: normal; }
  /* (Its sort link is a block of text whose arrow follows the last word: see "A heading that wraps at the start".) */
  .table-fit thead th:first-child .sort-link { display: block; align-content: center; }
  .table-fit thead th[aria-sort]:first-child .sort-link { padding-inline-end: 1.25em; }
  .table-fit thead th:first-child .sort-link .icon { display: inline-block; vertical-align: -0.125em; margin-inline: 0.25rem calc(-1em - 0.25rem); }
}
@media (min-width: 40rem) {
  /* The title is at most two lines; a single line still fills the 41 px target. */
  /* (Clear borders, not padding, make up the height: a cut third line would show through padding.) */
  .table .col-main .row-link {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
    min-height: 0; border-block: calc((var(--target) - 1.5rem) / 2) solid transparent;
  }
}
/* Figure and technical tables scroll sideways in their card: where the scrolling area is narrower than 56rem (840 px:
   in a container query a rem is the root's 15 px) the first column stays in place. The CARD's width decides, not
   the window's: a card is 866 px at a 1024 px window, where ten columns of figures still scroll, and 972 px at
   1280 px, where they fit. The column is 8.5rem to 9rem wide: a date ("14 Sep 2026", 83 px) and a two-word heading
   ("Week starting", 95 px) keep one line beside the cell's padding, and only a longer label wraps. Without the
   least width the table squeezes the column to its longest word and every row is two lines tall. */
@container (max-width: 56rem) {
  .table-sticky tr > :first-child {
    position: sticky; inset-inline-start: 0; z-index: 1; min-width: 8.5rem; max-width: 9rem; white-space: normal;
    background: var(--card); border-inline-end: 1px solid var(--divider);
  }
  /* While it is the sorted column its heading gains the arrow's width (1.25rem), as any sorted column does, so a
     two-word heading ("Week starting") still stands on one line beside its arrow. */
  .table-sticky thead th[aria-sort]:first-child { min-width: 9.75rem; max-width: 10.25rem; }
  /* A longer heading wraps ("Experiment arm"): its sort link is a block of text whose arrow follows the last word
     (see "A heading that wraps at the start"). */
  .table-sticky thead th:first-child .sort-link { display: block; align-content: center; }
  .table-sticky thead th[aria-sort]:first-child .sort-link { padding-inline-end: 1.25em; }
  .table-sticky thead th:first-child .sort-link .icon { display: inline-block; vertical-align: -0.125em; margin-inline: 0.25rem calc(-1em - 0.25rem); }
  .table-sticky tbody tr:hover > :first-child { background: color-mix(in srgb, var(--on-surface) var(--state-hover), var(--card)); }
  /* A chip under the label of the column that stays in place wraps inside it. */
  .table-sticky tr > :first-child .status { white-space: normal; }
}

/* Record tables: a row is one thing with a title and a few facts, so where a table does not fit its card each row
   becomes a short list. The column headers are clipped, not removed (screen readers keep them); the title cell comes
   first at full width; an empty cell is not drawn; the action cell is last. Nothing scrolls sideways.
   - Below 40rem (a phone) every other cell is a "label: value" line whose label is its data-label, and the buttons
     fill the width. The label is a float pulled into the cell's start padding, 0.5rem from its value: the value
     keeps its own flow beside it, whatever it holds (a chip with a note under it, a time and a hint), and a label
     that wraps makes its line taller instead of running into the next. A row's inline padding is the caption's
     (1.25rem), so titles and labels start under it.
     The label is 30 % of the card (cqi: the scrolling area of c::table_card is the container), but never so wide
     that the value has less than 12.75rem (191 px), and never under 6rem: 100cqi - 15.75rem is the card less the
     row's padding (2.5rem), the gap (0.5rem) and those 12.75rem. The value must hold the widest date of the team's
     zones on one line: "22 May 2026, 12:00 pm ADT" is 187.5 px (digits are all one width; May, pm and ADT are the
     widest of their kind). At a 375 px window the label is 103 px, as it always was; at 360 px it is 94 px and the
     value 191 px. With a plain 30 % and a 0.75rem gap the value had 182 px there, and a date ran 1 to 5 px over it
     on some days and hours; ./scripts/check.sh --e2e now measures that date itself, whatever the clock says.
   - From 40rem up to where the table fits (a tablet; the collapsed rail leaves a card 482 px at a 640 px window) the
     facts sit side by side as "label value" pairs on a wrapping line under the title, the buttons at their own
     width; a long text (.col-text) takes a line of its own beside its label.
   - .table-stack is a table from 64rem: four columns or fewer (a card is 866 px at a 1024 px window).
     .table-stack-m is a table from 72rem: five or six columns of chips and dates, nine of figures, or a long text
     beside a button (All Jobs, the Pipeline, Follow-ups, Scheduled, Exceptions, an experiment's arms). Such a table
     must also fit 972 px, the card at a 1280 px window, where the expanded rail appears.
   Short cells that say when and who (.col-meta: a time and a person, a kind and a time) share one hint line and
   carry no label: what they hold says what it is. A note in such a cell (the Audit Log: who, under the time in the
   table) follows on the same line. A cell whose own words say what it is (.col-plain: what happened,
   on Activity) takes the row's width and its label is not printed. A title cell that only repeats the row above for
   a screen reader (.col-again) takes no line. A short cell that belongs with the title (.col-beside: the record an
   Audit Log entry is about) stands on the title's line, right after it, with no label; where the two do not fit
   one line it takes the next. The row's other cells start on a new line (an empty part as wide as the row ends
   the title's line). The label of a cell is a word from domain::words; one
   that is a name from data (an Archive table's field) and too long for its column breaks inside it. A cell that
   has nothing to say (.cell-none: a dash for "no verdict") is left out of a stacked row, as an empty one is. The
   two blocks below are the same rules at two widths; so are the two after them. */
.table-stack, .table-stack-m { --stack-label: max(6rem, min(30cqi, 100cqi - 15.75rem)); --stack-gap: 0.5rem; }
@media (max-width: 63.99rem) {
  .table-stack, .table-stack caption, .table-stack tbody, .table-stack td, .table-stack tbody th { display: block; }
  /* (Hidden, not only clipped: a sortable header is a link, and a clipped link would still take keyboard focus
     unseen. The Sort by control above the table sorts here, and each cell names itself with its data-label.) */
  .table-stack thead {
    position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
    visibility: hidden;
  }
  .table-stack tr { display: flex; flex-flow: row wrap; gap: 0.25rem 0.75rem; padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--divider); }
  .table-stack tbody tr:last-child { border-bottom: 0; }
  .table.table-stack td, .table.table-stack tbody th {
    flex: 1 1 100%; width: auto; max-width: none; min-width: 0; height: auto; padding: 0; border: 0; white-space: normal; text-align: start;
  }
  .table.table-stack td[data-label] { display: flow-root; padding-inline-start: calc(var(--stack-label) + var(--stack-gap)); }
  .table-stack td[data-label]::before {
    content: attr(data-label); float: inline-start; width: var(--stack-label); overflow-wrap: anywhere;
    margin-inline: calc(-1 * (var(--stack-label) + var(--stack-gap))) var(--stack-gap); color: var(--on-surface-variant);
  }
  .table.table-stack td.col-meta { flex: 0 1 auto; padding-inline-start: 0; color: var(--on-surface-variant); }
  .table-stack td.col-meta::before { content: none; }
  .table-stack td.col-meta .cell-note { display: inline; margin-inline-start: 0.75rem; }
  .table.table-stack td.col-plain { padding-inline-start: 0; }
  .table-stack td.col-plain::before { content: none; }
  .table.table-stack td.col-beside { order: -1; flex: 1 1 auto; padding-inline-start: 0; }
  .table-stack td.col-beside::before { content: none; }
  .table.table-stack tr:has(> .col-beside) > .col-main { flex: 0 1 auto; align-self: center; }
  .table.table-stack tr:has(> .col-beside)::after { content: ""; order: -1; flex: 0 0 100%; height: 0; }
  .table-stack .cell-note { width: auto; max-width: none; }
  .table-stack .status { white-space: normal; }
  .table.table-stack td.col-again { position: absolute; }
  .table.table-stack .col-main { order: -1; font: var(--t-title-s); }
  .table.table-stack .col-actions { order: 1; display: flex; flex-wrap: wrap; gap: 0.5rem; padding-top: 0.5rem; }
  .table-stack .col-actions > *, .table-stack .col-actions .btn { flex: 1 1 auto; }
  .table-stack time { white-space: nowrap; }
}
@media (max-width: 71.99rem) {
  .table-stack-m, .table-stack-m caption, .table-stack-m tbody, .table-stack-m td, .table-stack-m tbody th { display: block; }
  .table-stack-m thead {
    position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
    visibility: hidden;
  }
  .table-stack-m tr { display: flex; flex-flow: row wrap; gap: 0.25rem 0.75rem; padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--divider); }
  .table-stack-m tbody tr:last-child { border-bottom: 0; }
  .table.table-stack-m td, .table.table-stack-m tbody th {
    flex: 1 1 100%; width: auto; max-width: none; min-width: 0; height: auto; padding: 0; border: 0; white-space: normal; text-align: start;
  }
  .table.table-stack-m td[data-label] { display: flow-root; padding-inline-start: calc(var(--stack-label) + var(--stack-gap)); }
  .table-stack-m td[data-label]::before {
    content: attr(data-label); float: inline-start; width: var(--stack-label); overflow-wrap: anywhere;
    margin-inline: calc(-1 * (var(--stack-label) + var(--stack-gap))) var(--stack-gap); color: var(--on-surface-variant);
  }
  .table.table-stack-m td.col-meta { flex: 0 1 auto; padding-inline-start: 0; color: var(--on-surface-variant); }
  .table-stack-m td.col-meta::before { content: none; }
  .table-stack-m td.col-meta .cell-note { display: inline; margin-inline-start: 0.75rem; }
  .table.table-stack-m td.col-plain { padding-inline-start: 0; }
  .table-stack-m td.col-plain::before { content: none; }
  .table.table-stack-m td.col-beside { order: -1; flex: 1 1 auto; padding-inline-start: 0; }
  .table-stack-m td.col-beside::before { content: none; }
  .table.table-stack-m tr:has(> .col-beside) > .col-main { flex: 0 1 auto; align-self: center; }
  .table.table-stack-m tr:has(> .col-beside)::after { content: ""; order: -1; flex: 0 0 100%; height: 0; }
  .table-stack-m .cell-note { width: auto; max-width: none; }
  .table-stack-m .status { white-space: normal; }
  .table.table-stack-m td.col-again { position: absolute; }
  .table.table-stack-m .col-main { order: -1; font: var(--t-title-s); }
  .table.table-stack-m .col-actions { order: 1; display: flex; flex-wrap: wrap; gap: 0.5rem; padding-top: 0.5rem; }
  .table-stack-m .col-actions > *, .table-stack-m .col-actions .btn { flex: 1 1 auto; }
  .table-stack-m time { white-space: nowrap; }
}

/* From 40rem a stacked row has room to put its facts side by side: "label value" pairs on a wrapping line under the
   title, and its buttons at their own width. A list of chips and dates would scroll sideways as a table here;
   stacked like a phone's it would be three times as long. A long text takes a line of its own, its label beside it
   and every paragraph of it in the same column; where no label is printed (.col-plain) the words stand first and
   their note beside them. */
@media (min-width: 40rem) and (max-width: 63.99rem) {
  .table-stack tr { column-gap: 1.5rem; }
  .table.table-stack td[data-label] {
    flex: 0 1 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.5rem; padding-inline-start: 0;
  }
  .table-stack td[data-label]::before { float: none; width: auto; margin: 0; }
  .table.table-stack td.col-text[data-label] {
    flex: 1 1 100%; display: grid; grid-template-columns: minmax(0, max-content) minmax(0, 1fr); align-items: start;
  }
  .table-stack td.col-text[data-label] > * { grid-column: 2; }
  .table-stack .col-actions > *, .table-stack .col-actions .btn { flex: 0 1 auto; }
}
@media (min-width: 40rem) and (max-width: 71.99rem) {
  .table-stack-m tr { column-gap: 1.5rem; }
  .table.table-stack-m td[data-label] {
    flex: 0 1 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.5rem; padding-inline-start: 0;
  }
  .table-stack-m td[data-label]::before { float: none; width: auto; margin: 0; }
  .table.table-stack-m td.col-text[data-label] {
    flex: 1 1 100%; display: grid; grid-template-columns: minmax(0, max-content) minmax(0, 1fr); align-items: start;
  }
  .table-stack-m td.col-text[data-label] > * { grid-column: 2; }
  .table-stack-m .col-actions > *, .table-stack-m .col-actions .btn { flex: 0 1 auto; }
}

/* A stacked row of many short figures (.stack-grid: an experiment's arms): on a phone they stand two across, the
   label above each figure and a rate's count beside its percent, instead of nine "label: value" lines. */
@media (max-width: 39.99rem) {
  .table.stack-grid td[data-label] { flex: 0 0 calc(50% - 0.375rem); display: block; padding-inline-start: 0; }
  .stack-grid td[data-label]::before { display: block; float: none; width: auto; margin: 0; }
  .stack-grid td .cell-note { display: inline; }
}
/* Wherever the arms are stacked, a rate's count stands beside its percent, so it is in brackets as the bars above
   the table print it ("27% (3 of 11)"): "27% 3 of 11" reads as one number. In the table the count is on a line of
   its own, under the percent, and needs none. */
@media (max-width: 71.99rem) {
  .stack-grid td .cell-note::before { content: "("; }
  .stack-grid td .cell-note::after { content: ")"; }
}
/* C3: a record table that is short rows at every width (.table-stack-a), for a card that never holds its columns: a
   job's versions in the job page's main column (541 px at a 1280 px window). The rules of .table-stack-m without
   the width condition, the "label value" pairs from 40rem; its Sort by control is .sort-by-a. */
.table-stack-a { --stack-label: max(6rem, min(30cqi, 100cqi - 15.75rem)); --stack-gap: 0.5rem; }
.table-stack-a, .table-stack-a caption, .table-stack-a tbody, .table-stack-a td, .table-stack-a tbody th { display: block; }
.table-stack-a thead {
  position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  visibility: hidden;
}
.table-stack-a tr { display: flex; flex-flow: row wrap; gap: 0.25rem 0.75rem; padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--divider); }
.table-stack-a tbody tr:last-child { border-bottom: 0; }
.table.table-stack-a td, .table.table-stack-a tbody th {
  flex: 1 1 100%; width: auto; max-width: none; min-width: 0; height: auto; padding: 0; border: 0; white-space: normal; text-align: start;
}
.table.table-stack-a td[data-label] { display: flow-root; padding-inline-start: calc(var(--stack-label) + var(--stack-gap)); }
.table-stack-a td[data-label]::before {
  content: attr(data-label); float: inline-start; width: var(--stack-label); overflow-wrap: anywhere;
  margin-inline: calc(-1 * (var(--stack-label) + var(--stack-gap))) var(--stack-gap); color: var(--on-surface-variant);
}
.table.table-stack-a td.col-plain { padding-inline-start: 0; }
.table-stack-a td.col-plain::before { content: none; }
.table-stack-a .status { white-space: normal; }
.table.table-stack-a .col-main { order: -1; font: var(--t-title-s); }
.table-stack-a time { white-space: nowrap; }
@media (min-width: 40rem) {
  .table-stack-a tr { column-gap: 1.5rem; }
  .table.table-stack-a td[data-label] {
    flex: 0 1 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.5rem; padding-inline-start: 0;
  }
  .table-stack-a td[data-label]::before { float: none; width: auto; margin: 0; }
  .table.table-stack-a td.col-text[data-label] { flex: 1 1 100%; display: block; }
}
.table.table-stack-a td:empty { display: none; }
.sort-by-a { display: flex; }
.sort-by-a + .table-card caption { scroll-margin-block-start: 13rem; }

/* An empty cell, and one that has nothing to say (.cell-none), is not drawn in a stacked row, at any width a table
   stacks at. These stand after the rules above, which lay a labelled cell out and would draw it again. */
@media (max-width: 63.99rem) { .table.table-stack td:empty, .table.table-stack td.cell-none { display: none; } }
@media (max-width: 71.99rem) { .table.table-stack-m td:empty, .table.table-stack-m td.cell-none { display: none; } }
/* In a stacked row a note under a number reads on the number's line, at the start. */
@media (max-width: 71.99rem) { .table-stack-m .num .cell-note { margin-inline-start: 0; } }
@media (max-width: 63.99rem) { .table-stack .num .cell-note { margin-inline-start: 0; } }

/* ---- Empty state: icon in a soft 63 px squircle, heading, one sentence ---- */
.empty {
  display: grid; justify-items: center; gap: 0.75rem; padding: 2.5rem 1.25rem; text-align: center;
  border-radius: var(--r-lg-plus); background: var(--card);
}
.empty-title { font: var(--t-title-l); }
.empty-title .icon-label { flex-direction: column; gap: 1rem; }
.empty-title .icon {
  width: 4.2rem; height: 4.2rem; padding: 1.05rem; border-radius: var(--r-xl);
  background: var(--secondary-container); color: var(--on-secondary-container);
}
.empty p { max-width: 44ch; color: var(--on-surface-variant); }

/* ---- Hero row (Dashboard): the greeting card and the queue tiles on the figures' four-column grid ---- */
/* Owner request 2026-10-13: one row, no empty half. From 64rem the row is the four columns of the figures under it,
   0.5rem apart: the card takes the left two, the tiles the right two (two across, so one or two tiles are one row and
   three or four a two-by-two block as tall as the card). With no tile the card takes the row (hero-row-alone). Below
   64rem the card takes the row and the tiles stand under it, two across from 40rem and one on a phone. */
.hero-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.5rem; margin-bottom: 0.5rem; }
@media (min-width: 64rem) {
  .hero-row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .hero-row > .hero, .hero-row > .seg-grid { grid-column: span 2; }
  .hero-row-alone > .hero { grid-column: 1 / -1; }
}
/* The greeting card: primary container, --r-xl-plus corners, big emphasized type. Compact: the time, the greeting and
   the one button under them, at the start; as tall as the tiles beside it, its words in the middle of its height. */
.hero {
  position: relative; overflow: hidden; isolation: isolate; display: grid; gap: 0.125rem; align-content: center;
  justify-items: start; min-width: 0; padding: 1rem 1.5rem; border-radius: var(--r-xl-plus);
  background: var(--primary-container); color: var(--on-primary-container);
}
/* The decorative shape starts after the time and the greeting, so they stand on the card's plain part: 23.5rem from
   the card's start in a half-width card (it shows 5rem of itself at a 1024 px window and nothing on a phone, where
   the greeting needs the width), and in the last 28% of a wider one. It costs no space and no contrast. */
.hero::before {
  content: ""; position: absolute; z-index: -1; width: 24.5rem; height: 24.5rem; inset-inline-start: max(72%, 23.5rem); top: -10.5rem;
  border-radius: 38% 62% 55% 45% / 45% 40% 60% 55%; background: var(--hero-shape);
}
.hero-time { font: var(--t-title-s); }
.hero-title { font: var(--t-headline-m); font-size: clamp(1.75rem, 1.25rem + 0.9vw, 2rem); line-height: 1.2; letter-spacing: -0.3px; overflow-wrap: anywhere; }
.hero-body { max-width: 60ch; }
.hero-actions { margin-top: 0.5rem; }
.hero .btn-filled { background: var(--hero-action); color: var(--on-hero-action); }
/* "Your local time" and the time are one line on a 360 px phone, whatever the time is. The widest there is ("22 May
   2026, 12:00 pm +1345": a two-digit day and hour, May, pm, the widest zone a person may choose) makes the line
   309.7 px in 15 px bold, and that phone's card had 285 px for it: the line broke whenever the hour had two digits.
   So on a phone the card's words start where every card's do (1.25rem: 292.5 px at 360) and the line is drawn at the
   scale's floor (14 px: 290.3 px). The time itself never breaks (base.css). */
@media (max-width: 39.99rem) {
  .hero { padding-inline: 1.25rem; }
  .hero-time { font-size: 0.9375rem; }
}

/* ---- Figures (KPI tiles) ---- */
/* Denser since 2026-10-12: 10rem tiles, so two stand side by side on a phone and a row of five holds at 1024; a
   tile's label, number and hint start at its top, whatever the height of its neighbours. */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr)); gap: 0.5rem; }
.kpi {
  display: grid; gap: 2px; align-content: start; min-width: 0; padding: 0.75rem 1rem;
  border-radius: var(--r-lg-plus); background: var(--surface-container-high);
}
/* On a phone the tiles stand two across, and the last of an odd number takes the row: no tile is left alone beside
   an empty half (Cost's five figures). */
@media (max-width: 39.99rem) {
  .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kpis > .kpi:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
.kpi-label { font: var(--t-label-l); color: var(--on-surface-variant); }
.kpi-value { font: var(--t-display-s); font-variant-numeric: tabular-nums lining-nums; overflow-wrap: anywhere; }
.kpi-hint { font: var(--t-body-m); color: var(--on-surface-variant); }
/* Six figures (Trial Comparison): one row from 64rem, three and three from 40rem, two across on a phone; never an
   orphan tile. */
.kpis-six { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 40rem) { .kpis-six { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 64rem) { .kpis-six { grid-template-columns: repeat(6, minmax(0, 1fr)); } }

/* ---- Segmented list: tiles 2 px apart inside a --r-lg container, --r-xs inner corners ---- */
.seg { display: flex; flex-direction: column; gap: 2px; border-radius: var(--r-lg); overflow: hidden; }
.tile {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 0.25rem 0.875rem;
  min-height: 4.2rem; padding: 0.75rem 1rem; border-radius: var(--r-xs); background: var(--card);
  color: var(--on-surface); text-decoration: none; transition: border-radius var(--d-spring) var(--spring-default);
}
.card .tile { background: var(--surface-container-high); }
.seg > li:first-child > .tile { border-start-start-radius: var(--r-lg); border-start-end-radius: var(--r-lg); }
.seg > li:last-child > .tile { border-end-start-radius: var(--r-lg); border-end-end-radius: var(--r-lg); }
.tile:hover { background: color-mix(in srgb, var(--on-surface) 5%, var(--card)); }
.card .tile:hover { background: color-mix(in srgb, var(--on-surface) 5%, var(--surface-container-high)); }
.tile:active { border-radius: var(--r-lg); }
.tile:focus-visible { outline-offset: -3px; }
.tile-text { display: grid; gap: 0.25rem; min-width: 0; }
.tile-label { font: var(--t-title-s); }
.tile-label .icon-label { gap: 0.875rem; }
.tile-label .icon { width: 2.4rem; height: 2.4rem; padding: 0.55rem; border-radius: var(--r-md); }
.tile-ok .tile-label .icon { background: var(--ok-container); color: var(--on-ok-container); }
.tile-warn .tile-label .icon { background: var(--warn-container); color: var(--on-warn-container); }
.tile-danger .tile-label .icon { background: var(--danger-container); color: var(--on-danger-container); }
.tile-info .tile-label .icon { background: var(--info-container); color: var(--on-info-container); }
.tile-neutral .tile-label .icon { background: var(--neutral-container); color: var(--on-neutral-container); }
.tile-hint { padding-inline-start: 3.275rem; font: var(--t-body-m); color: var(--on-surface-variant); }
.tile-count { font: var(--t-display-s); font-size: 2rem; line-height: 1; font-variant-numeric: tabular-nums; }
/* Queue tiles as a grid (the Dashboard's first row): two across from 40rem, one on a phone, 0.5rem apart like the
   figures, each tile a card of its own. The tiles of a row are equally tall, and so are the rows of an even number
   of tiles; a lone last tile takes both columns (seg-odd) and is as tall as what it holds, not as the row above it.
   In a tile the icon stands at the start, and the name, the count and the hint share one start edge beside it.

   Where the counts stand is decided for the whole list, never by one tile: all beside their names, or all under
   them ("44" beside its name and "over 30" under its own, in one row, read unevenly). The list is a size container
   and names the room a tile needs for its longest count beside the longest name, "Review Queue" (--count-room; the
   server gives the class from the longest count, components::count_room): 14rem for one digit, 15.2rem for two
   (seg-count-s), 16.4rem for three or four (seg-count-m), 18.1rem for "over 30" (seg-count-l) and 20.8rem for
   "over 100" (seg-count-xl), each 5 px or more above what was measured, so a font drawn a little wider still
   fits. In a tile narrower than that every count takes the whole line (its min-width becomes 100%); in a wider one
   every count stays beside its name. Should one still not fit, it wraps instead of being cut (flex-wrap). */
.seg-grid {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.5rem; border-radius: 0; overflow: visible;
  container: seg-grid / inline-size; --tile-width: 100cqi; --count-room: 14rem;
}
.seg-count-s { --count-room: 15.2rem; }
.seg-count-m { --count-room: 16.4rem; }
.seg-count-l { --count-room: 18.1rem; }
.seg-count-xl { --count-room: 20.8rem; }
.seg-grid > li { display: grid; min-width: 0; }
.seg.seg-grid > li > .tile { border-radius: var(--r-lg-plus); }
.seg.seg-grid > li > .tile:active { border-radius: var(--r-md); }
.seg-grid .tile {
  display: flex; flex-wrap: wrap; align-items: center; align-content: start; gap: 0.25rem 0.75rem;
  padding-block: 1.125rem; padding-inline: 4.275rem 1rem;
}
.seg-grid .tile-text { display: contents; }
.seg-grid .tile-label { order: 1; display: flex; align-items: center; min-width: 0; min-height: 2.4rem; }
.seg-grid .tile-label .icon { margin-inline-start: -3.275rem; }
.seg-grid .tile-count { order: 2; min-width: clamp(0rem, (var(--count-room) - var(--tile-width)) * 9999, 100%); overflow-wrap: anywhere; }
.seg-grid .tile-hint { order: 3; flex: 1 1 100%; padding-inline-start: 0; }
@media (min-width: 40rem) {
  .seg-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .seg-grid:not(.seg-odd) { grid-auto-rows: 1fr; }
  .seg-odd > li:last-child { grid-column: 1 / -1; }
  .seg-grid > li:not(:only-child) { --tile-width: calc(50cqi - 0.25rem); }
}

/* ---- Loading: a morphing shape with its word ---- */
.loading { display: inline-flex; align-items: center; gap: 0.75rem; font: var(--t-label-l); color: var(--on-surface-variant); }
/* U1 (spec 15, #6): a ring with a gap, so it reads as work going on even standing still (reduced motion, a screenshot),
   never as a solid square; where motion is welcome it turns. */
.loading-shape {
  flex: none; width: 1.875rem; height: 1.875rem; border-radius: 50%;
  border: 0.25rem solid var(--primary); border-block-start-color: transparent; background: none;
}
@media (prefers-reduced-motion: no-preference) { .loading-shape { animation: morph 1s linear infinite; } }
@keyframes morph { to { rotate: 360deg; } }

/* ---- Busy button (c::busy_button; Sync Scanners' loader, 2026-10-14): two faces in one grid cell, so the button
   keeps the width of the wider. At rest the loading face is hidden (and so not read); at work (data-busy, with
   disabled) the rest face is. The shape is the loading shape at icon size in the button's own colour: it turns only
   where motion is welcome (the rule above), and stands still under reduced motion beside its word. ---- */
.btn-busy { display: inline-grid; place-items: center; }
.btn-busy > span { grid-area: 1 / 1; }
.btn-busy-on { display: inline-flex; align-items: center; gap: 0.5rem; visibility: hidden; }
.btn-busy[data-busy] > .btn-busy-idle { visibility: hidden; }
.btn-busy[data-busy] > .btn-busy-on { visibility: visible; }
.btn-busy[data-busy] { cursor: progress; }
.btn-busy[data-busy]::after { opacity: 0; }
.btn-busy .loading-shape { width: 1.2rem; height: 1.2rem; border-width: 0.1875rem; border-color: currentColor; border-block-start-color: transparent; }

/* ---- Inner card: a block inside a card (one draft on the job page), one tone up from the card ---- */
.subcard {
  display: grid; gap: 0.75rem; align-content: start; min-width: 0; padding: 1rem;
  border-radius: var(--r-lg); background: var(--surface-container-high); color: var(--on-surface);
}

/* ---- Disclosure: closed by default; the summary is a 45 px row with a chevron drawn in CSS (not an icon) ---- */
/* Owner, 2026-10-21: the summary is a tinted band and the open body stands on the card colour, a hairline between,
   so a pane's heading never reads as part of its body (light and dark: the tokens step the same way). */
.disclosure { min-width: 0; border-radius: var(--r-lg); background: var(--card); color: var(--on-surface); }
.disclosure > summary {
  display: flex; align-items: center; gap: 0.75rem; min-height: var(--control-h); padding: 0 1rem;
  border-radius: var(--r-lg); background: var(--surface-container-highest); font: var(--t-label-l); cursor: pointer; list-style: none;
  transition: background-color var(--d-short) var(--ease-effects);
}
.disclosure[open] > summary { border-end-start-radius: 0; border-end-end-radius: 0; }
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary::after {
  content: ""; width: 0.5rem; height: 0.5rem; margin-inline: auto 0.25rem;
  border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor;
  rotate: 45deg; transition: rotate var(--d-spring) var(--spring-default);
}
.disclosure[open] > summary::after { rotate: 225deg; }
.disclosure > summary:hover { background: color-mix(in srgb, var(--on-surface) 8%, var(--surface-container-highest)); }
.disclosure > summary:focus-visible { outline-offset: -3px; }
.disclosure-body { display: grid; gap: 1rem; min-width: 0; padding: 0.75rem 1rem 1rem; border-block-start: 1px solid var(--outline-variant); }
/* A disclosure whose summary is a line of facts (the job page's "Screening (fit)", owner 2026-10-16: folded, its
   verdict and data points in view). The line wraps on a phone with room above and below; the chevron keeps its size;
   the verdict, its first part, is bold. */
.disclosure-glance > summary { padding-block: 0.625rem; }
.disclosure-glance > summary::after { flex: none; }
.glance { min-width: 0; overflow-wrap: anywhere; }
/* A part breaks onto the next line whole ("Must-haves met: 1 of 1" is never split), unless it is wider than the line. */
.glance-part { display: inline-block; max-width: 100%; }
.glance > .glance-part:first-child > .glance-text { font-weight: 700; }
/* C2b: a folded job step says what it holds after its title, in the summary's own line; open, the step shows it. */
.step > summary { flex-wrap: wrap; row-gap: 0.25rem; }
.step-glance { flex: 1 1 16rem; min-width: 0; overflow-wrap: anywhere; font: var(--t-body-m); color: var(--on-surface-variant); }
.step[open] > summary > .step-glance { display: none; }
/* Owner, 2026-10-17: the drafts that could not be written, after the written ones, under a small heading. */
.drafts-failed-title { margin: 0; font: var(--t-title-s); color: var(--on-surface-variant); }

/* ---- Long text: a letter or a job description keeps its line breaks ---- */
.text-block { max-width: 75ch; white-space: pre-wrap; overflow-wrap: anywhere; }
/* E1, A1 (enhancements spec 5.1): a letter or an answer exactly as Upwork shows it (`c::preview`): `.text-block` keeps
   every space and line break, and a long unbroken line wraps anywhere on a phone. The note above it names the
   formatting marks it holds, quietly, outside the text. */
.letter-preview { overflow-wrap: anywhere; }
.formatting-note { margin: 0; overflow-wrap: anywhere; }
/* E1, A2 (enhancements spec 5.2): the line under an answer: its words, and how the writer shortened it. */
.answer-words { margin: 0; }
/* A long text cut to about 12 lines (c::clamp): its end fades, and "Show all" under it is a checkbox drawn as a text
   button, so it opens without script. The checkbox covers its button (as a choice's radio button does): invisible,
   but it is what the keyboard and screen readers use. Ticked, the cut is gone and the button is filled. */
.clamp { display: grid; gap: 0.25rem; justify-items: start; min-width: 0; }
.clamp > :first-child {
  max-height: calc(1.625rem * 12); overflow: hidden;
  mask-image: linear-gradient(to bottom, #000 calc(100% - 3.25rem), transparent);
}
.clamp:has(input:checked) > :first-child { max-height: none; overflow: visible; mask-image: none; }
.clamp-toggle input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.clamp-toggle:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
.clamp-toggle:has(input:checked) { background: var(--secondary-container); color: var(--on-secondary-container); }

/* ---- Stacked facts: long labels (screening questions) above their values (answers) ---- */
.facts-stacked > div { grid-template-columns: minmax(0, 1fr); gap: 0.25rem; }
.facts-stacked dd { font: var(--t-body-m); }
/* In a draft's card the first answer starts right under its heading. */
.subcard .facts-stacked > div:first-child { padding-top: 0; }

/* ---- Timeline: oldest first, a dot on a line down the start edge; each step has its time, what happened and a
   detail. The dot and the line are decoration; the words carry the meaning. ---- */
.timeline { display: grid; margin: 0; padding: 0; list-style: none; }
.timeline-item {
  position: relative; display: grid; gap: 2px; margin-inline-start: 0.375rem; padding: 0 0 1rem 1.375rem;
  border-inline-start: 2px solid var(--outline-variant);
}
.timeline-item:last-child { padding-bottom: 0; border-inline-start-color: transparent; }
.timeline-item::before {
  content: ""; position: absolute; inset-inline-start: calc(-0.375rem - 1px); top: 0.375rem; width: 0.75rem; height: 0.75rem;
  border-radius: 50%; background: var(--primary);
}
.timeline-when { font: var(--t-body-m); color: var(--on-surface-variant); }
.timeline-what { font: var(--t-label-l); }

/* ---- Steps (the job page, S2): numbered, open where the work is; a finished step folds but can be opened. The
   summary holds the number and an h2; the chevron is drawn in CSS like the disclosure's ---- */
.step { min-width: 0; border-radius: var(--r-lg-plus); background: var(--card); color: var(--on-surface); }
.step > summary {
  display: flex; align-items: center; gap: 1rem; min-height: var(--control-h); padding: 1rem 1.25rem;
  border-radius: var(--r-lg-plus); cursor: pointer; list-style: none;
  transition: background-color var(--d-short) var(--ease-effects);
}
.step > summary::-webkit-details-marker { display: none; }
.step > summary::after {
  content: ""; width: 0.5rem; height: 0.5rem; margin-inline: auto 0.25rem; flex: none;
  border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor;
  rotate: 45deg; transition: rotate var(--d-spring) var(--spring-default);
}
.step[open] > summary::after { rotate: 225deg; }
.step > summary:hover { background: color-mix(in srgb, var(--on-surface) 5%, var(--card)); }
.step > summary:focus-visible { outline-offset: -3px; }
.step-number {
  display: inline-grid; place-items: center; flex: none; width: 2.4rem; height: 2.4rem; border-radius: var(--r-md);
  background: var(--primary-container); color: var(--on-primary-container); font: var(--t-title-m);
}
.step-title { font: var(--t-title-l); }
.step-body { display: grid; gap: 1rem; min-width: 0; padding: 0 1.25rem 1.25rem; }
@media (min-width: 64rem) {
  .step > summary { padding: 1.25rem 1.75rem; }
  .step-body { padding: 0 1.75rem 1.75rem; }
}

/* ---- Long fields (a letter, an answer, a note): like the one-line fields, taller, and they grow by hand ---- */
textarea {
  width: 100%; min-height: calc(var(--control-h) * 2); padding: 0.75rem 0.875rem;
  border: 1px solid var(--outline); border-radius: var(--r-md); background: var(--card); color: var(--on-surface);
  font: var(--t-body-l); resize: vertical; transition: border-color var(--d-short) var(--ease-effects);
}
textarea:hover { border-color: var(--on-surface); }
textarea[aria-invalid="true"] { border-color: var(--danger); box-shadow: inset 0 0 0 1px var(--danger); }

/* ---- Live box: work in progress that htmx refreshes every 10 seconds; the words say what is happening ---- */
.live { display: grid; gap: 0.5rem; min-width: 0; padding: 1rem; border-radius: var(--r-lg); background: var(--surface-container-high); }

/* ---- Copy buttons: hidden until app.js can copy; "Copied." appears next to the button ---- */
.copy-done { align-self: center; }

/* A count inside a button (the Review Queue's two views). */
.btn .badge { margin-inline-start: 0.25rem; }

/* ---- Number and code fields (S3): a number has the outline and corners of a one-line field; multi-line text is
   S2's long field (textarea) ---- */
input[type="number"] {
  width: 100%; max-width: 12rem; min-height: var(--control-h); padding: 0 0.875rem;
  border: 1px solid var(--outline); border-radius: var(--r-md); background: var(--card); color: var(--on-surface);
  font: var(--t-body-l); transition: border-color var(--d-short) var(--ease-effects);
}
input[type="number"]:hover { border-color: var(--on-surface); }
/* A number or a dropdown a refused form marks is drawn like a marked one-line field. */
input[type="number"][aria-invalid="true"], select[aria-invalid="true"] {
  border-color: var(--danger); box-shadow: inset 0 0 0 1px var(--danger);
}
/* Templates and JSON: every character shows as typed. */
.field-code { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
.hint code { overflow-wrap: anywhere; }

/* ---- Date fields (S5: a contract's start): the outline and corners of a one-line field ---- */
/* C2: a due time (Ask for review, Change due time) is a date-time field drawn as a date field. */
input[type="date"], input[type="datetime-local"] {
  width: 100%; max-width: 16rem; min-height: var(--control-h); padding: 0 0.875rem;
  border: 1px solid var(--outline); border-radius: var(--r-md); background: var(--card); color: var(--on-surface);
  font: var(--t-body-l); transition: border-color var(--d-short) var(--ease-effects);
}
input[type="date"]:hover, input[type="datetime-local"]:hover { border-color: var(--on-surface); }

/* Charts (S6): drawn on the server by crate::charts. The words (labels, values, axis, legend) are text in the page, so
   they stay 15 px or more at every width; the drawings scale. Every chart has a table with the same numbers next to it. */
/* The bars of one chart share one set of columns (from 40rem the list holds them and each row is a subgrid), so
   every track is equally long whatever its row's value says ("Too few to judge" is wider than a percent). On a
   phone a row is its label and value over a full-width bar. */
.chart-bars { display: grid; gap: 0.75rem; margin: 0; padding: 0; list-style: none; }
.chart-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.75rem; align-items: center; }
.chart-label { overflow-wrap: anywhere; }
.chart-bar { display: block; width: 100%; height: 0.75rem; }
.chart-track { fill: var(--surface-container-highest); }
.chart-fill { fill: var(--chart-1); }
.chart-muted .chart-fill { fill: var(--outline); }
.chart-muted .chart-label, .chart-muted .chart-value { color: var(--on-surface-variant); }
.chart-value { text-align: end; font-variant-numeric: tabular-nums; }
.chart-note { display: block; color: var(--on-surface-variant); }
@media (max-width: 39.99rem) { .chart-bar { grid-column: 1 / -1; grid-row: 2; } }
@media (min-width: 40rem) {
  .chart-bars { grid-template-columns: minmax(8rem, 14rem) minmax(4rem, 1fr) minmax(7rem, auto); column-gap: 0.75rem; }
  .chart-row { grid-column: 1 / -1; grid-template-columns: subgrid; }
}
.chart { display: grid; gap: 0.5rem; margin: 0; }
.chart-title { font: var(--t-title-s); }
.chart-plot { display: grid; grid-template-columns: 4rem minmax(0, 1fr); gap: 0.5rem; }
.chart-y { display: flex; flex-direction: column; justify-content: space-between; text-align: end; color: var(--on-surface-variant); font-variant-numeric: tabular-nums; }
.chart-lines { display: block; width: 100%; height: 12rem; }
.chart-grid { stroke: var(--divider); stroke-width: 1; }
.chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-series-1 { stroke: var(--chart-1); background: var(--chart-1); }
.chart-series-2 { stroke: var(--chart-2); background: var(--chart-2); }
.chart-series-3 { stroke: var(--chart-3); background: var(--chart-3); }
/* Never colour alone: the second and third lines are dashed and dotted, and so are their legend swatches. */
.chart-line.chart-series-2 { stroke-dasharray: 8 4; }
.chart-line.chart-series-3 { stroke-dasharray: 2 4; }
.chart-swatch.chart-series-2 { background: repeating-linear-gradient(90deg, var(--chart-2) 0 0.5rem, transparent 0.5rem 0.75rem); }
.chart-swatch.chart-series-3 { background: repeating-linear-gradient(90deg, var(--chart-3) 0 2px, transparent 2px 0.375rem); }
.chart-line.chart-dot { stroke-width: 6; stroke-dasharray: none; }
.chart-hit { fill: transparent; }
.chart-hit:hover { fill: color-mix(in srgb, var(--on-surface) 6%, transparent); }
.chart-x { display: flex; justify-content: space-between; padding-inline-start: calc(4rem + 0.5rem); color: var(--on-surface-variant); }
.chart-legend { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; margin: 0; padding: 0; list-style: none; }
.chart-legend li { display: inline-flex; gap: 0.5rem; align-items: center; }
.chart-swatch { display: inline-block; width: 1rem; height: 0.25rem; border-radius: var(--r-full); }
@media (forced-colors: active) { .chart-fill, .chart-line, .chart-swatch { forced-color-adjust: none; } }
/* A chart and the table with the same numbers, side by side from 72rem: the chart takes what the table leaves (28rem
   for a table of words and figures, 24rem with -s for one of short figures). Stacked below. */
.chart-split { display: grid; gap: 1rem 1.5rem; align-items: start; min-width: 0; }
.chart-split > * { min-width: 0; }
@media (min-width: 72rem) {
  .chart-split { grid-template-columns: minmax(0, 1fr) minmax(0, 28rem); }
  .chart-split-s { grid-template-columns: minmax(0, 1fr) minmax(0, 24rem); }
  /* The chart is shorter than a table of fourteen steps: it stays in view while the table scrolls past. */
  .chart-split > :first-child { position: sticky; top: calc(var(--appbar-h) + 1rem); }
}

/* ---- Row lists (S6 Task 17): rows a person adds and removes (Upwork Profile facts). A row's fields sit side by side
   and wrap on phones; Remove ends the row. The buttons stay hidden until app.js shows them; without it one empty row
   waits, and empty rows are not saved. ---- */
.rows { display: grid; gap: 0.75rem; justify-items: start; min-width: 0; }
.rows-list { display: grid; width: 100%; min-width: 0; margin: 0; padding: 0; list-style: none; }
.rows-item {
  display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: flex-end; min-width: 0;
  padding: 0.75rem 0; border-bottom: 1px solid var(--divider);
}
/* A row holds one line from 40rem, in a form section as narrow as 444 px (a 640 px window): its fields share the
   width, a short one (.field-s: years, a Yes or No) keeps to 7.5rem, and Remove ends the line. On a phone a field
   takes the line, and a short one shares its line with Remove. */
.rows-item > .field { flex: 1 1 7rem; }
.rows-item > .field-s { flex: 0 1 7.5rem; }
@media (max-width: 39.99rem) {
  .rows-item > .field { flex: 1 1 100%; }
  .rows-item > .field-s { flex: 1 1 7.5rem; }
}

/* ---- View switch (c::view_switch): the same page seen another way. One rounded container of equal segments 2 px
   apart, like the connected button group, but links; the current one (aria-current="true") is filled and fully
   round. Full width on a phone, as wide as its words from 40rem. A count is a badge after the word. ---- */
.tabs { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 2px; min-width: 0; }
.tab {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; min-width: 0;
  min-height: var(--target); padding: 0.25rem 0.75rem; border-radius: var(--r-sm);
  background: var(--surface-container-high); color: var(--on-surface-variant);
  font: var(--t-label-l); text-align: center; text-decoration: none;
  transition: border-radius var(--d-spring-fast) var(--spring-fast), background-color var(--d-short) var(--ease-effects);
}
.tab:first-child { border-start-start-radius: calc(var(--target) / 2); border-end-start-radius: calc(var(--target) / 2); }
.tab:last-child { border-start-end-radius: calc(var(--target) / 2); border-end-end-radius: calc(var(--target) / 2); }
.tab:hover { background: color-mix(in srgb, var(--on-surface) var(--state-hover), var(--surface-container-high)); }
.tab[aria-current] { background: var(--primary); color: var(--on-primary); border-radius: calc(var(--target) / 2); }
.tab .icon { width: 1.2rem; height: 1.2rem; }
/* A view whose words are too long for a phone's segment has short ones too (Tab.short): one of the two is drawn. */
.tab-wide { display: none; }
.tab-narrow { display: contents; }
/* On a phone a segment's count stands beside its words where they fit, and on the line under them where they do not
   ("To review" and "over 100" need 171 px; a segment has 149 at a 375 px window). */
@media (max-width: 39.99rem) { .tab { flex-wrap: wrap; gap: 0.25rem 0.5rem; } }
@media (min-width: 40rem) {
  .tabs { display: inline-grid; justify-self: start; }
  .tab { padding: 0 1.125rem; white-space: nowrap; }
  .tab-wide { display: contents; }
  .tab-narrow { display: none; }
}
/* The row under the page header: the view switch at the start, a hint at the end from 40rem; stacked on a phone. */
.toolbar { display: flex; flex-direction: column; gap: 0.75rem; min-width: 0; }
@media (min-width: 40rem) {
  .toolbar { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem 1.5rem; }
  .toolbar > .hint { flex: 1 1 20rem; max-width: 60ch; text-align: end; }
}
/* A line of status in the toolbar (the Scanners list's last read of GigRadar): its date and time never break. */
.toolbar time { white-space: nowrap; }

/* ---- Next-stage control (partials/stage_move.html): the one filled button for the stage that comes next, and
   "More" beside it, which opens the menu of every other update. The two never wrap apart; in a stacked table row
   and on a phone they share the width. ---- */
.move-actions { display: inline-flex; flex-wrap: nowrap; gap: 0.5rem; white-space: nowrap; vertical-align: middle; }
/* The menu reads from the start even in an action cell, which is end-aligned. */
.move-actions .menu { white-space: normal; text-align: start; }
@media (max-width: 39.99rem) {
  .move-actions { display: flex; }
  .move-actions > .btn { flex: 1 1 auto; padding-inline: 1rem; }
  /* "More" is the right-hand button there: its menu opens toward the start, its end edge under the button's, so
     it keeps the card's gutter instead of running to the edge of the screen. */
  .move-actions .menu-anchored { position-area: bottom span-left; }
}
/* In a stacked row of a record table the control is a row of its own among the row's buttons. */
@media (max-width: 63.99rem) { .table-stack .col-actions > .move-actions { display: flex; } }
@media (max-width: 71.99rem) { .table-stack-m .col-actions > .move-actions { display: flex; } }

/* ---- Result list (global search, 2026-10-16): one result per item, its title an h3 whose link is the whole line
   (at least the 41 px target), then a line of facts (a chip first) and a snippet. A match is a <mark>: a tonal
   background AND bold, never colour alone. Hairlines between results, as between a table's rows. ---- */
.results { display: grid; margin: 0; padding: 0; list-style: none; }
.result { display: grid; gap: 0.25rem; min-width: 0; padding: 0.75rem 0; border-top: 1px solid var(--outline-variant); }
.result:first-child { padding-top: 0; border-top: 0; }
.result:last-child { padding-bottom: 0; }
.result-title { margin: 0; font: var(--t-title-s); overflow-wrap: anywhere; }
.result-title a { display: block; min-height: var(--target); padding-block: calc((var(--target) - 1.5rem) / 2); }
.result-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 1rem; margin: 0;
  font: var(--t-body-m); color: var(--on-surface-variant); overflow-wrap: anywhere;
}
.result-snippet { max-width: 75ch; margin: 0; font: var(--t-body-m); overflow-wrap: anywhere; }
mark { padding: 0 0.125rem; border-radius: 0.25rem; background: var(--warn-container); color: var(--on-warn-container); font-weight: 700; }

/* ---- C2b: the Style Guide's picture of a Slack card (web/STYLE.md, "Slack card preview"). A picture, never a
   control: the buttons are words in an outline. The JSON wraps, so a phone never scrolls sideways. ---- */
.slack-mock {
  display: grid; gap: 0.5rem; min-width: 0; padding: 0.75rem 1rem;
  border-inline-start: 0.25rem solid var(--outline-variant); border-radius: var(--r-sm); background: var(--surface-container);
}
.slack-mock-text { margin: 0; overflow-wrap: anywhere; }
.slack-mock-head { font-weight: 700; }
.slack-mock-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem 1rem; margin: 0; }
.slack-mock-fields dt { font-weight: 700; }
.slack-mock-fields dd { margin: 0; overflow-wrap: anywhere; }
.slack-mock-buttons { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; }
.slack-mock-button { padding: 0.25rem 0.75rem; border: 1px solid var(--outline); border-radius: var(--r-sm); }
.slack-json {
  margin: 0; padding: 0.75rem; overflow-wrap: anywhere; white-space: pre-wrap;
  border-radius: var(--r-sm); background: var(--surface-container-high); font: var(--t-body-m);
}

/* ---- C3: two versions compared word by word (web/STYLE.md, "Version parts"). A removed word is struck through on
   the danger container, an added word underlined on the ok container: never colour alone, and each says so to a
   screen reader first. ---- */
.diff del { padding: 0 0.125rem; border-radius: 0.25rem; background: var(--danger-container); color: var(--on-danger-container); text-decoration: line-through; }
.diff ins { padding: 0 0.125rem; border-radius: 0.25rem; background: var(--ok-container); color: var(--on-ok-container); text-decoration: underline; }

/* ---- C6: a file input (a job's Attachments, New Manual Job's step 1) is a control of the form's height, as wide as
   its field, the browser's own button inside it at the label's size. ---- */
input[type="file"] {
  width: 100%; min-height: var(--control-h); padding: 0.375rem 0.875rem;
  border: 1px solid var(--outline); border-radius: var(--r-md); background: var(--card); color: var(--on-surface);
  font: var(--t-body-l);
}
input[type="file"]::file-selector-button { min-height: calc(var(--control-h) - 0.75rem); margin-inline-end: 0.75rem; font: var(--t-label-l); }

/* ---- C6: a manual job's files (web/STYLE.md, "Attachment parts"). One file an item: its name as the download link
   (the paperclip with the name, at least the 41 px target), a line of facts (type, size, pages) with the state of its
   text as a chip, then who added it and when. Hairlines between files, as between search results. ---- */
.file-list { display: grid; margin: 0; padding: 0; list-style: none; }
.file-item { display: grid; gap: 0.25rem; min-width: 0; padding: 0.75rem 0; border-top: 1px solid var(--outline-variant); }
.file-item:first-child { padding-top: 0; border-top: 0; }
.file-name { overflow-wrap: anywhere; }
.file-facts {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 1rem; margin: 0;
  font: var(--t-body-m); color: var(--on-surface-variant); overflow-wrap: anywhere;
}

/* ---- C6: a letter's paragraphs with "Save to library" (web/STYLE.md, "Library parts"). One paragraph a line: its
   first words, then its button (or "In the library") at the end; on a phone the button takes the next line. The
   dialog's read-only paragraph is a text block of its own. ---- */
.paragraph-list { display: grid; gap: 0.25rem; margin: 0; padding: 0; list-style: none; }
.paragraph-item { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0 1rem; min-width: 0; }
.paragraph-item > span:first-child { flex: 1 1 16rem; min-width: 0; overflow-wrap: anywhere; }
.library-text { max-width: 75ch; }
/* Without script every paragraph that can be saved stands in the dialog, each under its name (10.10 AL2). */
.library-choice { display: grid; gap: 0.25rem; min-width: 0; }
.library-choice + .library-choice { margin-top: 0.75rem; }

/* ---- U1 (UI rethink spec 3.3): the decision-first parts (web/STYLE.md, "Decision-first parts") ---- */
/* The decision card: first in the page's work. Its tone is a band at its start edge, never colour alone (the headline
   says the state in words). The outcome card is the same card, calmer: no filled button. */
.decision { gap: 0.75rem; border-inline-start: 0.375rem solid var(--neutral-container); }
.decision-neutral { border-inline-start-color: var(--neutral-container); }
.decision-info { border-inline-start-color: var(--info-container); }
.decision-ok { border-inline-start-color: var(--ok-container); }
.decision-warn { border-inline-start-color: var(--warn-container); }
.decision-danger { border-inline-start-color: var(--danger-container); }
.decision-outcome { background: var(--surface-container-high); }
.decision-head { font: var(--t-title-m); }
.decision-reason { font: var(--t-body-l); max-width: 60rem; }
.decision-why > summary { padding-block: 0.375rem; }
.decision-facts { margin: 0; }
.decision-actions { display: flex; flex-wrap: wrap; gap: 0.75rem 1rem; align-items: center; }
.decision-action { display: inline-flex; flex-wrap: wrap; gap: 0.25rem 0.75rem; align-items: center; }
.decision-consequence { overflow-wrap: anywhere; }
/* One line of chips next to an action: what blocks it, what it asks first, what it only shows. */
.blocker-line { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0; }
.blocker-line .status { white-space: normal; }
/* The glance strip under the title: 4 to 6 facts, each part breaking whole; where the job came from on line two. */
.glance-strip { display: grid; gap: 0.25rem; min-width: 0; font: var(--t-body-m); }
.glance-strip-second { color: var(--on-surface-variant); }
/* P-items (spec 6): the AI interviewer's line under the strip: the icon and its words on one line, wrapping whole. */
.glance-strip-note { margin: 0; color: var(--on-surface-variant); }
.glance-strip-second > .glance-part:first-child > .glance-text { font-weight: 400; }
/* A form that opens in place from a text button; never open by default. */
.form-on-demand { min-width: 0; }
.form-on-demand > summary { list-style: none; width: fit-content; }
.form-on-demand > summary::-webkit-details-marker { display: none; }
.form-on-demand-body { display: grid; gap: 1rem; min-width: 0; padding-block: 0.75rem 0.25rem; }
@media (max-width: 39.99rem) {
  .form-on-demand[open] > .form-on-demand-body { padding: 1rem; border-radius: var(--r-lg) var(--r-lg) 0 0; background: var(--surface-container-high); }
}
/* Needs attention: a configuration page's risky set-ups as warn chips. A chip's words wrap (a long warning would
   otherwise widen the card past a 375 px phone). */
.needs-attention { display: grid; gap: 0.5rem; min-width: 0; }
.needs-attention .status { white-space: normal; }
.needs-attention-title { font: var(--t-label-l); }
/* A configuration page's read view, then its folded edit sections. */
.read-view { gap: 0.75rem; }
.read-view-facts { margin: 0; }
@media (max-width: 39.99rem) { .read-view-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.edit-section > .disclosure-body > form { min-width: 0; }
.timeline-filtered { display: grid; gap: 0.5rem; }
.pane { min-width: 0; }
/* ---- U2 (UI rethink spec 4.3, 4.4): the job page before and during drafting (web/STYLE.md, "Work rows",
   "People list") ---- */
/* One row per Writer Profile (or failed step) in the decision card: the name, its state chip and its class chip on one
   line that wraps whole, then why, in plain words, and the page that fixes it. */
.work-rows { display: grid; gap: 0.75rem; margin: 0; padding: 0; list-style: none; }
.work-row { display: grid; gap: 0.25rem; min-width: 0; padding-block: 0.5rem; border-block-start: 1px solid var(--outline-variant); }
.work-row:first-child { border-block-start: 0; padding-block-start: 0; }
.work-row-head { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0; }
.work-row-line { margin: 0; max-width: 75ch; overflow-wrap: anywhere; }
/* E2: what a redraft or "More drafts" draft was asked with, and what the newest one did when it made no version. */
.draft-asked { margin: 0; max-width: 75ch; overflow-wrap: anywhere; }
.steer-note { margin: 0; max-width: 75ch; }
.live-card { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: center; }
/* The job's description in the decision-first page: cut to about 6 lines, so the folds below stay in the first
   screen (spec 1, the To decide row). The cap goes when "Show all" is ticked (it would outrank .clamp's unclamp rule). */
.job-page-u2 .job-text .clamp:not(:has(input:checked)) > :first-child { max-height: calc(1.625rem * 6); }
/* The card's note (UA62): whole at every width; on a phone a long one is cut to two lines with "Show all" under it (the
   checkbox inside the label, as .clamp's). Screen readers read the whole note either way. */
.decision-reason-clamp { display: grid; gap: 0.25rem; justify-items: start; min-width: 0; }
.decision-reason-more { display: none; }
@media (max-width: 39.99rem) {
  .decision-reason-more { display: inline-flex; position: relative; }
  .decision-reason-clamp:not(:has(input:checked)) > .decision-reason {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
  }
}
/* Reassign's people: one radio button per person, 41 px rows, the list scrolls inside the dialog. */
.people-list { display: grid; gap: 0.5rem; min-width: 0; margin: 0; padding: 0; border: 0; }
.people-list > legend { font: var(--t-label-l); padding: 0; margin-block-end: 0.25rem; }
.people-list-items { display: grid; max-height: min(20rem, 50vh); overflow-y: auto; margin: 0; padding: 0; list-style: none; }
.people-list-item { display: flex; gap: 0.75rem; align-items: center; min-height: 2.5625rem; padding-inline: 0.25rem; cursor: pointer; }
.people-list-item input { flex: none; width: 1.25rem; height: 1.25rem; margin: 0; }
/* On a phone the decision card's actions take the width they can, as every row of buttons does (above): an action that
   is not a text button fills its row, with its consequence under it. */
@media (max-width: 39.99rem) {
  .decision-action:has(> .btn-filled, > .btn-tonal, > .btn-outlined) { flex: 1 1 100%; }
  .decision-action > .btn-filled, .decision-action > .btn-tonal, .decision-action > .btn-outlined { flex: 1 1 auto; }
}
/* ---- U3 (UI rethink spec 4.5, 5.5): the Versions pane and the Compare page (web/STYLE.md, "Version parts") ---- */
/* One row per Version, newest first: its name and chips on one line that wraps whole, how it was made, its actions,
   then its details folded. A list, not a table: nothing scrolls sideways. */
.version-rows { display: grid; gap: 0.75rem; margin: 0; padding: 0; list-style: none; }
.version-row { display: grid; gap: 0.25rem; min-width: 0; padding-block: 0.5rem; border-block-start: 1px solid var(--outline-variant); }
.version-row:first-child { border-block-start: 0; padding-block-start: 0; }
.version-row-head { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0; }
/* A changed paragraph: before and after side by side where there is room, one under the other on a phone. */
.compare-pair { display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); padding-block: 0.75rem; border-block-start: 1px solid var(--outline-variant); }
.compare-pair > .hint { grid-column: 1 / -1; margin: 0; }
.compare-side { display: grid; gap: 0.25rem; min-width: 0; }
.compare-side-label { font: var(--t-label-l); margin: 0; }
/* ---- U3 (UI rethink spec 3.3, 4.5, 13): the letter view (web/STYLE.md, "Letter view") ---- */
/* The letter once, paragraph by paragraph. A paragraph's comments stand after it: under it on a phone, in a rail beside
   it from 72rem (every paragraph keeps the same text width then, so the letter reads as one column). */
.letter-view { display: grid; gap: 0.75rem; min-width: 0; }
.letter-view-head { align-items: center; }
.letter-view-title { font: var(--t-title-s); margin: 0; }
/* E1, A1: the blank lines between paragraphs are the letter's own (`p.letter-text` ends with them), so no gap. */
.letter-paragraphs { display: grid; gap: 0; min-width: 0; }
.letter-paragraph { display: grid; gap: 0.5rem; min-width: 0; }
.letter-text { margin: 0; max-width: 75ch; white-space: pre-wrap; overflow-wrap: anywhere; font: var(--t-body-l); }
.letter-text mark { font-weight: inherit; }
.letter-notes { display: grid; gap: 0.5rem; min-width: 0; }
.letter-note { display: grid; gap: 0.25rem; min-width: 0; padding: 0.5rem 0.75rem; border-inline-start: 0.1875rem solid var(--warn-container); border-radius: var(--r-sm); background: var(--surface-container); }
.letter-note > .row, .letter-note > .hint { margin: 0; }
.letter-answers { margin: 0; }
@media (min-width: 72rem) {
  .letter-view:has(.letter-notes) .letter-paragraph { grid-template-columns: minmax(0, 1fr) 18rem; align-items: start; column-gap: 1.5rem; }
}
/* ---- U3 (UI rethink spec 4.5, 13): the job page at Review (web/STYLE.md, "Review bar") ---- */
/* The description folds under "The job" here; clamped to about 6 lines when opened, as before drafting. The cap goes
   when "Show all" is ticked (it would outrank .clamp's unclamp rule). */
.job-page-u3 .job-text .clamp:not(:has(input:checked)) > :first-child { max-height: calc(1.625rem * 6); }
/* Spec 4.5, 13.3 (UA79): the blocker line is the bar's first row, as wide as the bar and above its buttons, so what
   blocks or asks stands on screen beside Approve wherever the page is scrolled. Its chips stand closer there (the
   label type stays). Without script the room kept for the bar counts the line. */
.review-bar > .blocker-line { flex: 1 1 100%; gap: 0.375rem; }
.review-bar > .blocker-line .status { min-height: 1.5rem; }
html:has(.review-bar > .blocker-line) { --action-bar-h: 7rem; }
/* The card's actions stand in the sticky bar. On a phone the bar is two rows of buttons at most, under its blocker
   line: Approve has the first, Skip and the rest stand two across (More leaves the bar on a phone: "Write new drafts"
   stands with the drafts). */
@media (max-width: 39.99rem) {
  html:has(.review-bar > .blocker-line) { --action-bar-h: 10.5rem; }
  /* Spec 13.3: on a phone the blocker line takes the fewest rows it can: its chips run on as text, each on its tone
     and with its icon, a chip breaking between words; a bar with the line stands its rows a little closer. */
  .review-bar:has(> .blocker-line) { row-gap: 0.375rem; padding-block: 0.5rem calc(0.5rem + env(safe-area-inset-bottom)); }
  .review-bar > .blocker-line { display: block; line-height: 1.5rem; }
  .review-bar > .blocker-line .status {
    display: inline; min-height: 0; padding: 0.0625rem 0.375rem; margin-inline-end: 0.25rem; border-radius: var(--r-sm);
    -webkit-box-decoration-break: clone; box-decoration-break: clone;
  }
  .review-bar > .blocker-line .status .icon-label { display: inline; }
  .review-bar > .blocker-line .status .icon {
    display: inline-block; width: 1rem; height: 1rem; margin-inline-end: 0.25rem; vertical-align: -0.15em;
  }
  .review-bar > .decision-action { flex: 1 1 calc(50% - 0.25rem); min-width: 0; }
  .review-bar > .decision-action:has(> .btn-filled) { flex-basis: 100%; }
  .review-bar > .decision-action:has(> .btn-outlined, > .btn-tonal) { flex: 1 1 calc(50% - 0.25rem); }
  /* (The bar's own phone padding, as .action-bar > .btn has: the card's actions stand one level down, in their span.) */
  .review-bar > .decision-action > .btn { flex: 1 1 auto; min-width: 0; padding-inline: 0.75rem; }
}
/* Spec 1 (UA80), 13: on a phone the letter's first lines stand in the first screen, above the sticky bar. A letter
   title that repeats the card's going-out line is for screen readers only, so Edit and "Comment on this passage" share
   one row; a Reviewer's card is her request (her version, its Writer Profile and the answers stand in the letter view
   under it, who approved in the Discussion); the card's facts read label then value on one line; the parts, the
   card's padding and a fold's two-line summary stand closer (a summary keeps its 45 px row). Nothing leaves the
   page. */
@media (max-width: 39.99rem) {
  .letter-view-title-in-card {
    position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  .review-request-first #decision .decision-facts { display: none; }
  /* The going-out card's facts (the answers, who approved) stand in the letter view and the Discussion; an empty
     answer is the bar's blocker line, beside Approve (UA84). */
  .job-page-u3:has(.letter-view-title-in-card) #decision .decision-facts { display: none; }
  .job-page-u3 .decision-facts { grid-template-columns: minmax(0, 1fr); gap: 0.25rem; }
  .job-page-u3 .decision-facts > div { display: flex; flex-wrap: wrap; column-gap: 0.5rem; align-items: baseline; }
  .job-page.job-page-u3 { gap: 0.75rem; }
  /* 0.5rem since the merge of live 7k (its taller open-fold body): with 0.75rem a fold's summary at 375 x 900 stood
     as a strip at the sticky bar's edge (axe target-size, attachments.spec.ts), U3 final review finding 4. */
  .job-page-u3 > #decision { padding: 0.5rem 1rem; }
  .job-page-u3 > .pane.disclosure-glance > summary { padding-block: 0.375rem; }
}
/* "Tell the AI what to change" inside the Review page's "Change with AI" fold: the fold is its heading. */
.revise-pane { display: grid; gap: 1rem; min-width: 0; }
/* U2 browser checks (Task 10). The consequence of a phone-wide action stands under its button, so the button fills
   its row (/jobs/3's Try again). */
@media (max-width: 39.99rem) {
  .decision-action:has(> .btn-filled, > .btn-tonal, > .btn-outlined) > .decision-consequence { flex: 1 1 100%; }
}
/* The decision-first page is one column at every width: it has no side column, so .job-page's two columns from
   72rem would put every other fold beside the one before it (the Technical details' tables then overflowed). */
@media (min-width: 72rem) {
  .job-page.job-page-u2, .job-page.job-page-u3 { grid-template-columns: minmax(0, 1fr); }
}
/* A dialog's reasons stack at every width: five reasons do not fit one line of a dialog (Skip, spec 4.3). */
.dialog .choice-stack .choice-row { display: grid; grid-template-columns: minmax(0, 1fr); max-width: 28rem; }
.dialog .choice-stack .choice { padding-block: 0.375rem; border-radius: var(--r-sm); }
.dialog .choice-stack .choice:first-child { border-start-start-radius: calc(var(--target) / 2); border-start-end-radius: calc(var(--target) / 2); }
.dialog .choice-stack .choice:last-child { border-end-start-radius: calc(var(--target) / 2); border-end-end-radius: calc(var(--target) / 2); }
.dialog .choice-stack .choice:has(input:checked) { border-radius: calc(var(--target) / 2); }
/* Reassign's people: the radio button covers its whole row (as a choice's does): invisible, but it is what the
   keyboard and screen readers use; the row draws the round mark, filled when chosen. A row is one click target high
   (--target, 41 px at every scale; 2.5625rem is under 40 px at the denser scale). */
.people-list-item { position: relative; min-height: var(--target); border-radius: var(--r-sm); }
.people-list-item input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
.people-list-item::before {
  content: ""; flex: none; box-sizing: border-box; width: 1.25rem; height: 1.25rem;
  border: 2px solid var(--on-surface-variant); border-radius: 50%;
}
.people-list-item:has(input:checked) { background: var(--secondary-container); }
.people-list-item:has(input:checked)::before { border-color: var(--primary); background: radial-gradient(circle, var(--primary) 0 0.3125rem, transparent 0.34rem); }
.people-list-item:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: -3px; }

/* U4 (UI rethink spec 4.6, 4.7): the job page from Scheduled on (job_record.html). The follow-up control (the next
   stage and More) stands in the card after its facts, as its actions do; the record's notes are rows. */
.job-page-u4 #decision .move-actions { margin-top: 0.75rem; }

/* The shell: the rail (menu), the app bar, the page head. Breakpoints (rem, so zoom works):
   below 40rem   app bar with Menu button; the rail is a modal popover; New Manual Job is a floating button
   40-80rem      112 px collapsed rail; each section opens a flyout (popover)
   from 80rem    263 px expanded rail with <details> groups
   Media queries in rem measure the browser's own 16 px, not the 15 px root: 40rem is 640 px and 80rem 1280 px.
   A menu item with a count needs its name and the count on one line (owner report 2026-10-16: "Review Queue" and
   "over 100" broke over two lines, the count's own words too). The longest is the current page "Review Queue" in
   bold (icon and word 123 px) beside "over 100" (70.4 px) with the 0.5rem gap between: 201 px, measured in Chromium.
   Every menu leaves at least 206.25 px (13.75rem) for it, 5 px over that: the expanded rail (263 px, less 2 x 0.75rem
   of rail and 1.75rem + 0.5rem of item), a flyout (16.5rem, less 2 x 0.5rem and 1.25rem + 0.5rem) and the phone's
   open menu (18rem: 213.75 px). ./scripts/check.sh --e2e measures all three. */

.app { display: grid; grid-template-columns: minmax(0, 1fr); min-height: 100dvh; }
.main-col { min-width: 0; display: flex; flex-direction: column; }
main {
  flex: 1; width: 100%; max-width: calc(90rem + 2 * var(--gutter)); margin-inline: auto;
  padding: 0.25rem var(--gutter) 6rem;
}
@media (min-width: 40rem) { .app { grid-template-columns: var(--rail-w) minmax(0, 1fr); } }
@media (min-width: 80rem) { .app { grid-template-columns: var(--sidebar-w) minmax(0, 1fr); } }

/* ---- Brand: the NEENOPAL logo (a white plate behind it in dark) and the app name ---- */
.brand {
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 0.25rem;
  min-height: var(--target); padding: 0.25rem 0; color: var(--on-surface); text-decoration: none; font: var(--t-title-s);
}
.brand-logo { height: 1.6rem; width: auto; padding: 2px 0.25rem; border-radius: var(--r-sm); background: var(--logo-plate); }
/* The app's name ("Atlas", owner request 2026-10-12, renamed 2026-10-23): always on ONE line under the logo.
   14 px bold, the scale's floor (the longer 2026-10-12 name took about 201 px); the expanded rail's head leaves it 229 px (e2e measures it). */
.brand-name { font: 700 0.9375rem/1.25rem var(--font); white-space: nowrap; }

/* ---- The rail. Base = expanded: from 80rem, and inside the modal popover below 40rem ---- */
.rail {
  display: flex; flex-direction: column; gap: 0.25rem; padding: 0.5rem 0.75rem 1rem;
  background: var(--bg); color: var(--on-surface-variant);
  overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
}
.rail[popover] {
  margin: 0; border: 0; inset: 0 auto 0 0; height: 100dvh; max-height: none; width: min(18rem, 86vw);
  padding: 0.5rem 0.75rem 1rem; background: var(--surface-container); color: var(--on-surface-variant);
  border-radius: 0 var(--r-lg) var(--r-lg) 0; box-shadow: var(--e-3);
}
.rail[popover]::backdrop { background: var(--scrim); }
/* Below 40rem the rail is hidden until the Menu button opens it (`.rail` above would otherwise show it). */
.rail[popover]:not(:popover-open) { display: none; }
@media (min-width: 40rem) {
  /* The same element stands in the page as the rail; the Menu button that opens it as a popover is hidden. */
  /* It stays in view while the page scrolls: sticky at the top. `inset` (which undoes the popover's) is written
     WITH top: 0; a bare `inset: auto` after `top: 0` reset top to auto, so the rail scrolled away (fix 2026-10-26). */
  .rail[popover]:not(:popover-open) {
    display: flex; position: sticky; inset: 0 auto auto 0; align-self: start; z-index: 30;
    height: 100dvh; width: auto; border-radius: 0; box-shadow: none; background: var(--bg);
  }
}
.rail-head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  min-height: 3.75rem; padding: 0 0 0 0.75rem; margin-bottom: 0.25rem;
}
.rail-close { display: none; }
.rail:popover-open .rail-close { display: inline-flex; }
/* The phone's modal rail (270 px): the head is two rows. The logo and "Close menu" share the first; the name stands
   alone on the second, at the head's full width, so it never wraps beside the button. The link keeps one box (its
   logo and name sit on the head's own rows through a subgrid). */
.rail:popover-open .rail-head {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto auto; gap: 0.5rem;
  align-items: center; padding-block: 0.25rem 0.5rem;
}
.rail:popover-open .rail-head .brand {
  display: grid; grid-column: 1 / -1; grid-row: 1 / span 2; grid-template-columns: subgrid; grid-template-rows: subgrid;
  gap: 0.5rem; justify-items: start; align-items: center; padding: 0;
}
.rail:popover-open .rail-head .brand-logo { grid-column: 1; grid-row: 1; }
.rail:popover-open .rail-head .brand-name { grid-column: 1 / -1; grid-row: 2; }
.rail:popover-open .rail-head .rail-close { grid-column: 2; grid-row: 1; }
/* That one box also holds "Close menu", so the focus ring is drawn on what the link shows, the logo and the name,
   and not around the button as well (the rows are 0.5rem apart so the name's ring stays clear of the button). */
.rail:popover-open .rail-head .brand:focus-visible { outline-style: none; }
.rail:popover-open .rail-head .brand:focus-visible .brand-logo,
.rail:popover-open .rail-head .brand:focus-visible .brand-name { outline: 3px solid var(--focus); outline-offset: 2px; }
.rail:popover-open .rail-head .brand:focus-visible .brand-name { border-radius: var(--r-xs); }
.rail-list { display: flex; flex-direction: column; gap: 2px; }

.rail-item {
  display: flex; align-items: center; gap: 0.75rem; width: 100%; min-height: var(--rail-item-h); padding: 0 1rem;
  border: 0; border-radius: var(--r-xl); background: none; color: var(--on-surface-variant);
  font: var(--t-label-l); text-align: start; text-decoration: none; cursor: pointer; list-style: none;
  position: relative; user-select: none; transition: background-color var(--d-short) var(--ease-effects);
}
.rail-item::-webkit-details-marker { display: none; }
.rail-item:hover { background: color-mix(in srgb, var(--on-surface) var(--state-hover), transparent); }
.rail-item .icon { width: 1.4rem; height: 1.4rem; }
.rail-item:focus-visible, .nav-link:focus-visible, .menu-item:focus-visible { outline-offset: -3px; }

/* The section chevron: drawn in CSS, so it is not an icon without a word. */
summary.rail-item::after {
  content: ""; width: 0.5rem; height: 0.5rem; margin-inline: auto 0.375rem;
  border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor;
  rotate: 45deg; transition: rotate var(--d-spring) var(--spring-default);
}
details[open] > summary.rail-item::after { rotate: 225deg; }
details:has([aria-current="page"]) > summary.rail-item { color: var(--on-secondary-container); font-weight: 700; }

.nav-items { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 0.375rem; }
.nav-link {
  display: flex; align-items: center; gap: 0.5rem; min-height: var(--nav-link-h); padding: 0 0.5rem 0 1.75rem;
  border-radius: var(--r-xl); color: var(--on-surface-variant); text-decoration: none; font: var(--t-body-m);
  position: relative; transition: background-color var(--d-short) var(--ease-effects);
}
.nav-link .icon { width: 1.2rem; height: 1.2rem; }
.nav-link:hover { background: color-mix(in srgb, var(--on-surface) var(--state-hover), transparent); }

/* The current page (spec 4.1): a light blue pill, bold text, a bar, and aria-current. Never colour alone. */
.rail-item[aria-current="page"], .nav-link[aria-current="page"] {
  background: var(--secondary-container); color: var(--on-secondary-container); font-weight: 700;
}
.rail-item[aria-current="page"]::before, .nav-link[aria-current="page"]::before, .rail-fly.has-current::before {
  content: ""; position: absolute; inset-inline-start: 0.375rem; top: 50%; translate: 0 -50%;
  width: 0.25rem; height: 1.4rem; border-radius: 2px; background: var(--primary);
}

/* Flyouts: only in the collapsed rail. Popovers, so Esc and a click outside close them, and focus returns. */
.rail-fly { display: none; }
.flyout {
  margin: 0; border: 0; padding: 0.5rem; width: 16.5rem; max-height: calc(100dvh - 1rem); overflow: auto;
  background: var(--surface-container-high); color: var(--on-surface);
  border-radius: var(--r-lg); box-shadow: var(--e-2);
}
.flyout-title { font: var(--t-title-s); color: var(--on-surface-variant); padding: 0.5rem 0.75rem 0.375rem; }
.flyout .nav-items { margin: 0; }
.flyout .nav-link {
  min-height: var(--nav-link-h); padding: 0 0.5rem 0 1.25rem; border-radius: var(--r-md); color: var(--on-surface);
}
/* Each flyout opens beside its own section button (CSS anchor positioning), else near the top of the rail. */
.rail-fly[popovertarget="fly-jobs"] { anchor-name: --fly-jobs; }
.rail-fly[popovertarget="fly-insights"] { anchor-name: --fly-insights; }
.rail-fly[popovertarget="fly-configuration"] { anchor-name: --fly-configuration; }
.rail-fly[popovertarget="fly-administration"] { anchor-name: --fly-administration; }
#fly-jobs { position-anchor: --fly-jobs; }
#fly-insights { position-anchor: --fly-insights; }
#fly-configuration { position-anchor: --fly-configuration; }
#fly-administration { position-anchor: --fly-administration; }
@supports (position-area: right span-bottom) {
  .flyout { position-area: right span-bottom; position-try-fallbacks: flip-block; margin-inline-start: 0.25rem; }
}
@supports not (position-area: right span-bottom) {
  .flyout { inset: auto; top: var(--appbar-h); left: calc(var(--rail-w) - 0.25rem); }
}
@media (prefers-reduced-motion: no-preference) {
  .flyout { transition: opacity var(--d-short) var(--ease-effects), scale var(--d-spring) var(--spring-default),
    display var(--d-short) allow-discrete, overlay var(--d-short) allow-discrete; transform-origin: left top; }
  @starting-style { .flyout:popover-open { opacity: 0; scale: 0.92; } }
}

/* ---- 40-80rem: the 112 px collapsed rail (icon in a 48 x 28 pill, word under it) ---- */
@media (min-width: 40rem) and (max-width: 79.99rem) {
  .rail:not(:popover-open) { width: var(--rail-w); padding: 0.75rem 0 1rem; gap: 0.25rem; overflow-x: hidden; }
  .rail:not(:popover-open) .rail-head, .rail:not(:popover-open) .nav-group { display: none; }
  .rail:not(:popover-open) .rail-fly { display: flex; }
  .rail:not(:popover-open) .rail-list { gap: 0.25rem; }
  .rail:not(:popover-open) .rail-item {
    flex-direction: column; justify-content: center; min-height: 3.75rem; padding: 0.25rem;
    border-radius: var(--r-md); font: var(--t-label-m); text-align: center; background: none;
  }
  .rail:not(:popover-open) .rail-item .icon-label { flex-direction: column; gap: 0.25rem; }
  /* Whole words only: never a hyphen inside a word ("Configura-tion"); two or more words may wrap between words.
     The label token is 15 px here, so the longest word ("Administration", 101 px) fits the rail's 104 px. */
  .rail:not(:popover-open) .rail-item .icon-label > span {
    max-width: calc(var(--rail-w) - 0.5rem); overflow-wrap: normal; word-break: normal; hyphens: none;
  }
  .rail:not(:popover-open) .rail-item .icon {
    width: 3.2rem; height: 1.875rem; padding: 0.25rem 0.9rem; border-radius: var(--r-lg);
    transition: background-color var(--d-short) var(--ease-effects), scale var(--d-spring) var(--spring-default);
  }
  .rail:not(:popover-open) .rail-item:hover .icon { background: color-mix(in srgb, var(--on-surface) var(--state-hover), transparent); }
  .rail:not(:popover-open) .rail-item:active .icon { scale: 0.94; }
  .rail:not(:popover-open) .rail-item[aria-current="page"] .icon,
  .rail:not(:popover-open) .rail-fly.has-current .icon,
  .rail:not(:popover-open) .rail-fly:has(+ .flyout:popover-open) .icon {
    background: var(--secondary-container); color: var(--on-secondary-container);
  }
  .rail:not(:popover-open) .rail-fly.has-current { color: var(--on-secondary-container); font-weight: 700; }
  .rail:not(:popover-open) .rail-item[aria-current="page"]::before,
  .rail:not(:popover-open) .rail-fly.has-current::before { inset-inline-start: 0; top: 0.5rem; translate: none; height: 1.875rem; }
}

/* ---- The app bar: pill search (Expressive search app bar) ---- */
.appbar {
  position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 0.75rem;
  min-height: var(--appbar-h); padding: 0.5rem var(--gutter); background: var(--bg);
}
.menu-btn { display: none; }
.appbar-brand { display: inline-flex; align-items: center; min-height: var(--target); flex: none; }
.appbar-brand .brand-logo { height: 1.2rem; }
@media (min-width: 80rem) { .appbar-brand { display: none; } }
/* On a phone the logo gives its room to the search, so that the placeholder is not cut (the open menu shows it). */
@media (max-width: 39.99rem) { .appbar-brand { display: none; } }
.search {
  display: flex; align-items: center; gap: 0.25rem; flex: 1; min-width: 0; max-width: 44rem;
  height: 3.2rem; padding: 0 0.25rem 0 1.25rem; border-radius: var(--r-xl);
  background: var(--surface-container-high); color: var(--on-surface-variant);
}
.search input {
  flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; background: none;
  font: var(--t-body-l); color: var(--on-surface);
}
.search input::placeholder { color: var(--on-surface-variant); opacity: 1; }
.search input:focus-visible { outline: none; }
.search:focus-within { outline: 3px solid var(--focus); outline-offset: 2px; }
.search-go { color: var(--on-surface-variant); }
.new-job { margin-inline-start: auto; flex: none; }
.avatar-btn {
  display: inline-flex; align-items: center; gap: 0.625rem; min-height: 2.8rem; min-width: 2.8rem; padding: 0.2rem;
  border: 0; border-radius: var(--r-full); background: none; color: var(--on-surface); font: var(--t-label-l);
  cursor: pointer; flex: none; anchor-name: --avatar;
}
.avatar-btn:hover { background: color-mix(in srgb, var(--on-surface) var(--state-hover), transparent); }
.avatar {
  display: inline-grid; place-items: center; width: 2.4rem; height: 2.4rem; flex: none; border-radius: 50%;
  background: var(--primary-container); color: var(--on-primary-container); font: var(--t-label-m);
}
.avatar-lg { width: 2.8rem; height: 2.8rem; }
@media (min-width: 80rem) { .avatar-btn { padding-inline-end: 1rem; } }
@media (max-width: 79.99rem) {
  .avatar-name {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }
}

/* The user menu (popover under the avatar). */
.menu {
  margin: 0; border: 0; padding: 0.5rem; min-width: 14rem; max-width: calc(100vw - 1.5rem);
  background: var(--surface-container-high); color: var(--on-surface);
  border-radius: var(--r-lg); box-shadow: var(--e-2);
}
.user-menu { position: fixed; inset: auto; top: calc(var(--appbar-h) - 0.25rem); inset-inline-end: 0.75rem; }
@supports (position-area: bottom span-left) {
  .user-menu { inset: auto; position-anchor: --avatar; position-area: bottom span-left; margin-top: 0.25rem; }
}
/* Any other menu: it opens under the button that opens it (a popovertarget button is the popover's implicit
   anchor). Without anchor positioning it opens in the middle of the window, never in a corner. */
.menu-anchored { inset: 0; margin: auto; }
@supports (position-area: bottom span-right) {
  .menu-anchored { inset: auto; margin: 0.25rem 0 0; position-area: bottom span-right; position-try-fallbacks: flip-block, flip-inline; }
}
.menu-who { display: flex; gap: 0.75rem; align-items: center; padding: 0.5rem 0.75rem 0.75rem; }
.menu-who p { display: grid; }
.menu-who strong { font: var(--t-title-m); }
.menu-who-label, .menu-who-meta { font: var(--t-body-m); color: var(--on-surface-variant); }
.menu-items { display: grid; gap: 2px; }
.user-menu .menu-items:last-child { margin-top: 2px; }
.menu form { margin: 0; }
.menu-item {
  display: flex; align-items: center; width: 100%; min-height: var(--target); padding: 0 0.75rem 0 1rem;
  border: 0; border-radius: var(--r-md); background: none; color: var(--on-surface);
  font: var(--t-body-m); text-align: start; text-decoration: none; cursor: pointer; position: relative;
}
.menu-item:hover { background: color-mix(in srgb, var(--on-surface) var(--state-hover), transparent); }
/* The current page or the current choice (the theme): the light blue pill, bold text and a bar. Never colour alone. */
.menu-item[aria-current] { background: var(--secondary-container); color: var(--on-secondary-container); font-weight: 700; }
.menu-item[aria-current]::before {
  content: ""; position: absolute; inset-inline-start: 0.25rem; top: 50%; translate: 0 -50%;
  width: 0.25rem; height: 1.4rem; border-radius: 2px; background: var(--primary);
}
.menu-title { font: var(--t-title-s); color: var(--on-surface-variant); padding: 0.5rem 1rem 0.375rem; }

/* The theme switch (2026-10-12), on every signed-in page. From 64rem: a button in the app bar next to the avatar
   (the current theme's icon and word) that opens its own menu. Below 64rem the app bar has no room for it next to
   the search, so the same three choices sit in the avatar's menu, and the word "Theme" stands at the foot of the
   rail, where it is seen without opening a menu: in the collapsed rail (40-64rem) a "Theme" item that opens the
   theme menu beside it, as a section opens its flyout; in the phone's modal rail the three choices under their
   title. One of these is always there. */
.theme-btn { display: none; flex: none; }
.menu-theme { margin-block: 0.25rem; padding-block: 0.25rem; border-block: 1px solid var(--outline-variant); }
.rail-theme { margin-top: auto; padding-top: 0.5rem; }
.rail-theme-btn { display: none; }
.rail-theme-choices { padding-top: 0.25rem; border-top: 1px solid var(--outline-variant); }
@media (min-width: 40rem) and (max-width: 63.99rem) {
  .rail:not(:popover-open) .rail-theme-btn { display: flex; }
  .rail:not(:popover-open) .rail-theme-choices { display: none; }
  /* The rail's item is the only button that opens the theme menu here: it opens to the right of the item. */
  #theme-menu { position-area: right span-top; margin: 0 0 0 0.25rem; }
}
@media (min-width: 64rem) {
  .theme-btn { display: inline-flex; }
  .menu-theme, .rail-theme { display: none; }
}
@media (prefers-reduced-motion: no-preference) {
  .menu { transition: opacity var(--d-short) var(--ease-effects), scale var(--d-spring) var(--spring-default),
    display var(--d-short) allow-discrete, overlay var(--d-short) allow-discrete; transform-origin: top right; }
  @starting-style { .menu:popover-open { opacity: 0; scale: 0.92; } }
}

/* "New job" floats bottom right on phones (extended FAB), above the home bar's safe area; in the app bar elsewhere.
   base.html draws it only where Layout.show_fab says: the Dashboard and the Jobs lists. */
.fab { display: none; }

@media (max-width: 39.99rem) {
  .appbar { padding: 0.5rem 0.75rem; gap: 0.25rem; }
  .menu-btn { display: inline-flex; }
  .search { height: 2.8rem; padding-inline-start: 0.75rem; }
  .search-go, .new-job { display: none; }
  .fab {
    display: inline-flex; position: fixed; inset-inline-end: 1rem; bottom: calc(1rem + env(safe-area-inset-bottom)); z-index: 25;
    --h: 3.2rem; padding: 0 1.25rem 0 1rem; border-radius: var(--r-lg);
    background: var(--primary-container); color: var(--on-primary-container); font: var(--t-title-s);
    box-shadow: var(--e-3);
  }
  .fab:active { border-radius: var(--r-xl); }
}

/* ---- Page head: breadcrumb, then the title row: the title with a status chip beside it (block page_status) and the
   page's buttons at the end (block page_actions; one filled button per page). On a phone the buttons stand under
   the title and the main one fills the width. A page with no buttons leaves an empty holder, which is not drawn. ---- */
.page-head { display: grid; gap: 0.25rem; padding: 0.5rem 0 1.25rem; }
.page-title-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem 1rem; min-width: 0; }
.page-title { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; min-width: 0; }
.page-head-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.page-head-actions:empty { display: none; }
/* A button that sends a form (Start experiment) is drawn as the button alone: the form has no box of its own, so
   the row lays its button out like any other. */
.page-head-actions > form { display: contents; }
@media (max-width: 39.99rem) {
  .page-title-row { flex-direction: column; align-items: stretch; }
  .page-head-actions > .btn-filled, .page-head-actions > form > .btn-filled { flex: 1 1 100%; }
}
.breadcrumb ol {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px; margin: 0; padding: 0; list-style: none;
  font: var(--t-label-l); color: var(--on-surface-variant);
}
.breadcrumb li { display: flex; align-items: center; min-width: 0; }
.breadcrumb li + li::before { content: "\203A"; padding-inline: 0.25rem; }
.breadcrumb a, .breadcrumb span { display: inline-flex; align-items: center; min-height: var(--target); padding: 0 0.375rem; }
.breadcrumb a { color: var(--on-surface-variant); text-decoration: none; border-radius: var(--r-sm); }
.breadcrumb a:hover { color: var(--on-surface); text-decoration: underline; }
.breadcrumb [aria-current="page"] { color: var(--on-surface); }
/* The breadcrumb wraps between its items. A long title in it (a job's) wraps between its words inside the row,
   and a single word longer than the row breaks instead of pushing the page sideways. */
.breadcrumb a, .breadcrumb span { min-width: 0; overflow-wrap: anywhere; }
/* U2 (UA63): on a phone a job page's breadcrumb names the list only; the job's title is the H1 right under it. */
@media (max-width: 39.99rem) {
  main:has(.job-page) .breadcrumb li:has(> [aria-current="page"]) { display: none; }
}

/* A menu item's count badge (S2): at the end of the row, after the word, 0.5rem from it at least (the row's gap),
   in the round end of the item's pill (the row's end padding is 0.5rem). The widths that keep the two on one line
   are at the top of this file. */
.nav-link .badge { margin-inline-start: auto; }

/* Page-specific touches. Keep this short: a pattern used on two pages belongs in components.css. */

/* Sign-in and the plain error pages (bare.html): the brand above one centred card; the app's name on one line,
   centred under the logo. */
.bare-head { display: flex; justify-content: center; padding: 2rem var(--gutter) 0; }
.bare-head .brand { align-items: center; text-align: center; }
.bare-main { max-width: 30rem; margin: 1.5rem auto 3rem; padding: 0 var(--gutter); }
.sign-in { gap: 1.25rem; }
/* MCP-R1 (MCP spec 10.2): the consent page's loopback warning is a sentence; its chip's words wrap (on one line it
   widened the card past a 375 px phone). */
.sign-in .status { white-space: normal; }
/* The consent page (owner 2026-10-26): wider than sign-in (45rem, a card of about 630 px, from a tablet; the full
   width less the gutter on a phone), tighter spacing, and every line with Allow and Cancel on one screen at every
   width, even 390x844. */
.bare:has(.consent) .bare-head { padding-top: 0.75rem; }
.bare-main:has(> .consent) { max-width: 45rem; margin-block: 0.75rem; }
.consent { gap: 0.75rem; }
.consent .form { gap: 0.75rem; }
.consent .choice-group { gap: 0.25rem; }
.consent .choice-group legend { margin-bottom: 0.25rem; }
.consent-who { display: flex; flex-wrap: wrap; align-items: center; gap: 0 0.5rem; }
.consent-who > p { flex: 1 1 20rem; }
.consent .form-actions { margin-top: 0.25rem; }
/* Read is ticked and cannot be unticked: the shared disabled background with the ticked words' colour drew "Read"
   nearly invisible, so it takes the surface's own words colour. */
.consent .choice:has(input:checked:disabled) { color: var(--on-surface); }
.error-page { justify-items: center; text-align: center; }
.error-page code { overflow-wrap: anywhere; }
/* The error page's one button: as wide as its words from 40rem, the card's width on a phone. */
.error-action { display: flex; justify-content: center; }
@media (max-width: 39.99rem) {
  .error-action { justify-self: stretch; }
  .error-action > .btn { flex: 1 1 auto; }
}

/* Style Guide: cards stack; rows wrap. */
.card > .row + .row { margin-top: 0.25rem; }

/* The job page (2026-10-12). Two parts: the work (.job-main: the summary line, the stage's block, the description,
   the steps) and the context (.job-side: About this job, Autopilot, Timeline). The work is first in the page, so
   below 72rem the context follows it; from 72rem the context is the right column and stays in view while the work
   scrolls, scrolling by itself when it is taller than the window (it can take focus, so the keyboard reaches it). */
.job-page { display: grid; gap: 1.25rem; align-items: start; min-width: 0; }
.job-main { min-width: 0; }
.job-side { min-width: 0; border-radius: var(--r-lg-plus); }
.job-side:focus-visible { outline-offset: -3px; }
@media (min-width: 72rem) {
  .job-page { grid-template-columns: minmax(0, 1fr) minmax(18rem, 24rem); }
  .job-side {
    position: sticky; top: calc(var(--appbar-h) + 0.5rem); max-height: calc(100dvh - var(--appbar-h) - 1rem);
    overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
  }
  /* In the narrow column the facts stand two across, as on a phone, and the cards keep the phone's padding. */
  .job-side .card { padding: 1.25rem; }
  .job-side .facts-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The job page's drafts side by side (S2): one column on phones, as many 22rem columns as fit from 64rem. */
/* A short card ends where its content ends (align-items: start), instead of being stretched to its neighbour. */
.draft-grid { display: grid; gap: 1rem; align-items: start; min-width: 0; }
@media (min-width: 64rem) { .draft-grid { grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); } }

/* The Pipeline: four tables with one column set. Wherever they are tables (from 72rem) every column after the job
   has ONE width in all four (fixed layout, set on the headers: the widest stage chip, a date with its time and zone,
   a name, the Scanner, one word for the source) and the job takes the rest, so the columns line up down the page
   whatever each phase holds, and a table is never wider than its card (972 px at a 1280 px window). A name too long
   for its column wraps instead of pushing the next one. */
@media (min-width: 72rem) {
  table[id^="phase-"] { table-layout: fixed; }
  table[id^="phase-"] th:nth-child(2) { width: 10.75rem; }
  /* The widest date: two-digit day and hour and a four-letter zone ("12 Oct 2026, 10:02 am AEDT", about 180 px). */
  table[id^="phase-"] th:nth-child(3) { width: 14rem; }
  table[id^="phase-"] th:nth-child(4) { width: 8rem; }
  table[id^="phase-"] th:nth-child(5) { width: 9rem; }
  table[id^="phase-"] th:nth-child(6) { width: 6rem; }
  table[id^="phase-"] td:is(:nth-child(4), :nth-child(5), :nth-child(6)) { white-space: normal; overflow-wrap: anywhere; }
}

/* Configuration > Scanners: the last read of GigRadar and Sync Scanners (2026-10-14). On a phone the last read, then
   the button at full width with what it costs under it. From 40rem the last read at the start and the button at the
   end of one row, its hint under it ending where the button ends. */
.sync-status { display: flex; flex-direction: column; gap: 0.25rem; min-width: 0; }
.sync-form { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }
@media (min-width: 40rem) {
  .sync-bar { align-items: flex-start; flex-wrap: nowrap; }
  .sync-status { flex: 1 1 auto; }
  .sync-form { flex: 0 1 25rem; align-items: flex-end; text-align: end; }
}

/* New Manual Job, step 3 (2026-10-14): a Scanner OR the job's own setup. Only the chosen way's controls show, with
   CSS alone, so it works without script; a hidden control leaves the accessibility tree and still posts, and the
   server ignores the other way's fields. With no way chosen (a form this app did not draw) both show. */
.manual-setup:has(input[name=setup][value=scanner]:checked) .manual-way-own,
.manual-setup:has(input[name=setup][value=own]:checked) .manual-way-scanner { display: none; }

/* C4 (spec 8.6): the revise card's paragraph choice stands only while "One paragraph" is chosen, with CSS alone, so it
   works without script; a hidden select still posts and the server reads it only for one paragraph. */
.revise-form:has(input[name=scope][value=letter]:checked) .revise-paragraph { display: none; }

/* MCP-P (2026-10-24): Connect Claude. The connector's address and the Claude Code command break inside themselves
   rather than widen a phone's page. */
.connect-code { overflow-wrap: anywhere; }

/* MCP-P: Test the connection's lines stand one under another, each with its icon. */
.check-lines { display: grid; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }

/* MCP-P: a device's recent calls link the record a call was about and the Audit Log inside their lines; each link is
   a full click target (41 px tall and wide), never under 40 px. */
[id^="device-"] .timeline a { display: inline-flex; align-items: center; min-height: var(--target); min-width: var(--target); }

/* MCP-P: Administration > Claude connections. Seven columns share a 972 px card at a 1280 px window without
   scrolling sideways: the buttons stand one under another (.row-actions) and why a status is so wraps at 12rem under
   its chip rather than at a note's usual 18rem. In a card narrower than 72rem the app, what it may do and the two
   dates keep 7rem (a date still stands over its time) and the status 11rem (its widest chip, Needs re-allowing).
   Where the card has room (72rem and wider) the sorted column's heading keeps one line, so its arrow stands right
   after its words; in a narrower card it may take two lines, as every heading here may. */
.table.table-claude-connections .col-text .cell-note { max-width: 12rem; }
@container (min-width: 72rem) { .table.table-claude-connections thead th[aria-sort] { white-space: nowrap; } }
@container (max-width: 72rem) {
  .table.table-claude-connections :is(.col-name, .col-words) { min-width: 7rem; }
  .table.table-claude-connections .col-text { min-width: 11rem; }
  .table.table-claude-connections .col-text .cell-note { max-width: 11rem; }
}
