/* Pincite — "Record" design system: the application shell.
   Requires tokens.css, loaded first.

   Serves the operator/counsel pages that are not the transcript itself:
   `templates/authorize.html`, `connect.html`, `upload.html`, `clio_pull.html`, `login.html`,
   and the two one-time-link pages `set_password.html` / `link_status.html` (#G5/#G6), which
   spend the `.login-card` block below rather than introducing a second credential-page style.
   Before this file each of those carried its own inline <style> block with its own
   greys, blues, and radii — four private design systems on one origin. The blocks are
   gone; every value here is a token.

   Why the <style> blocks had to go rather than merely be re-coloured: the marketing
   site's CSP forbids inline styles, and the app should not be the surface where that
   discipline lapses. An external sheet is also the only version a firm's own CSP can
   allow without `'unsafe-inline'`.

   Same rule as everywhere: anything the machine produced — an id, a URL, a digest, a
   filename, a timestamp, an enum — is set in --font-mono. */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background-color: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

main {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--space-5) 1.5rem var(--space-6);
}

main.wide { max-width: var(--page-wide); }

p { margin: 0 0 var(--space-3); max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

h1, h2, h3 { color: var(--ink); text-wrap: balance; }

h1 {
  font-size: var(--step-3);
  font-weight: 650;
  line-height: 1.2;
  letter-spacing: -0.018em;
  margin: 0 0 var(--space-3);
}

h2 {
  font-size: var(--step-1);
  font-weight: 640;
  line-height: 1.3;
  letter-spacing: -0.008em;
  margin: var(--space-4) 0 var(--space-1);
}

/* #433 gives the matter roster two sub-blocks under one `<h2>`, the first `<h3>` in any app
   template. Same rhythm as the rule above, one step down and a shade lighter, so a sub-heading
   reads as subordinate to its section rather than as a second section. Tokens only. */
h3 {
  font-size: var(--step-0);
  font-weight: 620;
  line-height: 1.35;
  margin: var(--space-4) 0 var(--space-1);
}

code {
  font-family: var(--font-mono);
  font-size: 0.875em;
  background-color: var(--code-bg);
  padding: 0.12em 0.36em;
  border-radius: var(--radius-chip);
  word-break: break-word;
}

ol, ul { padding-left: 1.2rem; max-width: var(--measure); }
li { margin: 0.4rem 0; }

/* ---- links: the four states, and no others -------------------------- */

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { color: var(--accent-lift); text-decoration-thickness: 2px; }
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }

/* ---- brand ---------------------------------------------------------- */
/* Two marks, one per theme, swapped by media query — the swap is CSS because there is
   no script on these pages that may decide what renders. The mark is a pincite:
   brackets around a single point in the record.

   Note the coupling, and why NO page currently ships the lockup: `TRANSCRIBE_BRAND_NAME`
   renames the brand TEXT on in-product surfaces (config.py:47) and does not repaint
   `static/brand/*.svg`. A page that pairs the mark with a hard-coded "Pincite" would go
   silently wrong the day that variable is set. So the pages carry the mark only as a
   favicon, and this is the reviewed shape for a lockup on a surface that is
   unambiguously ours — the certificate head below is its one live consumer. */

.brand { display: flex; align-items: center; gap: 0.7rem; }
.brand img { width: 22px; height: 22px; display: block; }
.brand-name { font-weight: 700; font-size: var(--step-0); letter-spacing: -0.02em; color: var(--ink); }

/* The brand name set INSIDE a heading — it inherits the heading's size and only gains
   weight. Distinct from .brand-name, which is the lockup's own 17px label. */
.brand-inline { font-weight: 700; letter-spacing: -0.02em; }

.brand .mark-dark { display: none; }
@media (prefers-color-scheme: dark) {
  .brand .mark-light { display: none; }
  .brand .mark-dark { display: block; }
}

.app-head {
  border-bottom: 1px solid var(--rule);
  padding: var(--space-3) 1.5rem;
}

.app-head-inner {
  max-width: var(--page);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.app-head.wide .app-head-inner { max-width: var(--page-wide); }

/* ---- card ------------------------------------------------------------ */

.card {
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  padding: var(--space-3);
  margin: var(--space-3) 0;
  max-width: var(--measure);
}

.card p { margin-bottom: var(--space-2); }
.card p:last-child { margin-bottom: 0; }

/* #378 S4: when the card IS the page (`<main class="card">` — integrations, oauth_signin),
   `.card`'s own `margin: var(--space-3) 0` was overriding `main`'s `margin: 0 auto`, so the
   measure-wide card hugged the left edge of the viewport. Centered like every other page. */
main.card { margin-left: auto; margin-right: auto; }

/* The one accent-edged block: an advisory the reader must not skim past. Same shape as
   the marketing site's status callout, on purpose — the product and the page that
   describes it should not disagree about what a warning looks like. */
.callout {
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-card) var(--radius-card) 0;
  padding: var(--space-3);
  margin: var(--space-3) 0;
  max-width: var(--measure);
  font-size: 1rem;
  color: var(--ink-muted);
}

.callout strong { color: var(--ink); }
.callout p:last-child { margin-bottom: 0; }

/* `.warning` is the same shape: `oauth.py::_warning_block` emits it around a registered
   disclaimer, and the consent screen is the one place a reader must not skim. Its ink is
   full strength, not muted — a warning set in support-grey is a warning nobody read. */
.warning {
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-card) var(--radius-card) 0;
  padding: var(--space-3);
  margin: var(--space-3) 0;
  max-width: var(--measure);
  font-size: 1rem;
  color: var(--ink);
}

/* The scope line and the tier prompt: supporting text under a claim, not the claim.
   `.note` is the same weight — the connect page's standing statement about what the
   connector delivers. */
.scope,
.note { color: var(--ink-muted); font-size: 1rem; }

/* ---- machine strings -------------------------------------------------- */

.mono,
.tx-meta,
.tx-file-name,
.url {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.tx-meta { color: var(--ink-muted); font-size: var(--step-mono); }

/* Prose, not machine output — so it stays sans. A hint set in mono would read as
   something the system produced, which is the one distinction this system enforces. */
.tx-hint { color: var(--gutter); font-size: var(--step-mono); }

/* The FAILURE state on the matter pages. Deliberately NOT `.tx-hint`: it sits in the same slot as
   "No matters yet", so if the two rendered alike a reader could not tell "you have no matters" from
   "we could not load them" — which is the finding (F5), not the fix.
   Both ARE muted greys; what separates them is the accent rule and the absence of `.tx-hint`'s mono
   size — so the border is load-bearing, not decoration, and the guard asserts it. Same shape as
   `.login-error`, built only from tokens already in the contrast inventory
   (`tests/guards/test_design_tokens.py`). */
.tx-error {
  border-left: 2px solid var(--accent);
  padding-left: var(--space-2);
  color: var(--ink-muted);
}

/* #124: what this product's own record says about the kind the reader has chosen, on `/upload` and
   `/clio/pull`. `.tx-error`'s shape and not `.tx-hint`'s, for `.tx-error`'s own recorded reason:
   this CORRECTS the option above it — the picker offers a kind the record says is not delivered —
   and a correction set like "Matter names could not be loaded" reads as the same weight as a
   loading hiccup. `--accent` is the palette's "the record is incomplete" accent
   (MARKETING_SITE_SPEC §5), which is exactly what this says. `--measure` because it is four
   sentences of prose, not a status line. Every token is already in the contrast inventory
   (`tests/guards/test_design_tokens.py`). */
.tx-kind-notice {
  border-left: 2px solid var(--accent);
  padding-left: var(--space-2);
  color: var(--ink-muted);
  max-width: var(--measure);
}

/* The connector URL. It is the one string on that page a reader must copy exactly, so
   it is set as an artifact: mono, full width, breaking rather than truncating. */
.url {
  display: block;
  background-color: var(--code-bg);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-card) var(--radius-card) 0;
  padding: 0.8rem 1rem;
  margin: var(--space-3) 0;
  font-size: var(--step-mono);
  line-height: 1.6;
  word-break: break-all;
}

/* ---- forms ------------------------------------------------------------ */

fieldset {
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  background-color: var(--surface);
  padding: var(--space-3);
  margin: var(--space-3) 0;
  max-width: var(--measure);
}

legend { font-weight: 650; padding: 0 0.4rem; color: var(--ink); }

label { display: block; margin: 0.4rem 0; }

input[type="text"],
input[type="search"],
input[type="email"],
input[type="password"],
select {
  font: inherit;
  font-size: 1rem;
  color: var(--ink);
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-chip);
  padding: 0.4rem 0.6rem;
}

input:focus-visible,
select:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

button {
  font: inherit;
  font-size: 1rem;
  font-weight: 650;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius-chip);
  border: 1px solid var(--rule);
  background-color: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-lift); }
button:disabled { color: var(--gutter); cursor: not-allowed; }

/* The accent fills exactly one button per page — the one that commits the action. */
button.approve,
button.tx-cta {
  background-color: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}

button.approve:hover:not(:disabled),
button.tx-cta:hover:not(:disabled) {
  background-color: var(--accent-lift);
  border-color: var(--accent-lift);
  color: var(--bg);
}

button.tx-cta:disabled {
  background-color: var(--surface);
  border-color: var(--rule);
  color: var(--gutter);
}

button.deny { margin-left: 0.5rem; }

/* ---- row: a labelled control ------------------------------------------ */

.tx-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin: var(--space-2) 0;
  flex-wrap: wrap;
}

.tx-row label {
  min-width: 8rem;
  margin: 0;
  font-size: var(--step-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gutter);
}

/* ---- lists of files: the queue and the Clio pickers -------------------- */

.tx-list,
.tx-queue {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  background-color: var(--surface);
  max-width: none;
  overflow: hidden;
}

.tx-list { max-height: 300px; overflow-y: auto; }

.tx-list li,
.tx-queue li {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  padding: 0.45rem 0.75rem;
  border-bottom: 1px solid var(--rule-soft);
  font-size: var(--step-mono);
  margin: 0;
}

.tx-list li:last-child,
.tx-queue li:last-child { border-bottom: none; }

/* A pointer cursor and a full-width hover highlight are a PROMISE that the row is a click target.
   #36 wrote these two rules when `templates/clio_pull.html` was `.tx-list`'s only consumer and its
   rows genuinely carry an `<li>` click handler (`static/js/clio.js`) — and exempted the one
   non-interactive list it had, `#tx-results`, by id in the same block. #47 then reused `.tx-list`
   for the matter index without the exemption and without a handler, and four more inert lists
   inherited it after that. By #508 the promise was false on six of the eight consumers, which is
   how a bug report arrived saying a delete button was broken: rows lit up, the pointer changed,
   nothing happened.

   Inverted to OPT-IN (#508). Exempting by id is a denylist, and the six exemptions nobody wrote are
   the defect. A list earns `.tx-list--clickable` by having a row handler, and
   `tests/guards/test_clickable_rows_are_clickable.py` fails if a list carries the class without one
   — or if either of these rules drifts back onto bare `.tx-list li`. Both rules move together: the
   hover highlight alone is still the affordance half of the same lie. */
.tx-list--clickable li { cursor: pointer; }
.tx-list--clickable li:hover { background-color: var(--code-bg); }

/* ---- matter deletion (#508) ---------------------------------------------
   Built from the existing palette on purpose: there is no danger/warning token, and adding one
   means editing `site/style.css`, `static/css/tokens.css` AND the guard's contrast pairs — a
   colour-system change riding inside a feature diff. The weight here is carried by the rule, the
   spacing and the words instead. */
.tx-delete-bar {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  margin: var(--space-2) 0;
}

.tx-delete-confirm {
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: var(--space-2);
  margin: var(--space-2) 0;
  background-color: var(--code-bg);
}

.tx-delete-confirm h2 { margin-top: 0; }

.tx-delete-notice { max-width: 60ch; }

.tx-delete-actions {
  display: flex;
  gap: 0.6rem;
  align-items: center;
}

/* The row's checkbox. `flex: 0 0 auto` so it never absorbs the space `.tx-file-name` needs. */
.tx-matter-select { flex: 0 0 auto; }

.tx-file-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--step-mono);
}

progress { width: 130px; }

/* ---- upload queue card (#339) -------------------------------------------
   One card per queued file: name · size · kind · Remove on the first line, a thin
   progress track under it, then the status line and — once the recording exists —
   the cost line on the file's own card. `.tx-queue li` supplies the hairline rule;
   the card overrides its single-line flex layout with a stacked grid. */

.tx-queue li.tx-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.45rem;
  align-items: stretch;
  padding: 0.7rem 0.9rem;
}

.tx-card-main,
.tx-card-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  min-width: 0;
}

/* The filename is read in full. The index rows truncate with an ellipsis because they
   are one line each; a card has the height, and a filename a firm is about to pay to
   transcribe is not a string to cut short. */
.tx-card .tx-file-name {
  flex: 1 1 14rem;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  overflow-wrap: anywhere;
  color: var(--ink);
}

.tx-file-size { flex: 0 0 auto; }

.tx-card-remove {
  font-size: var(--step-mono);
  font-weight: 500;
  padding: 0.25rem 0.6rem;
}

/* The progress track, in the system's own vocabulary (the coverage strip's thin
   track) rather than the browser's native widget and its default colour. Neutral ink
   on the code field: progress is not a verdict, so it carries no status colour. */
.tx-card-progress {
  display: block;
  width: 100%;
  height: 4px;
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  border-radius: var(--radius-chip);
  background-color: var(--code-bg);
  color: var(--ink-muted);
  overflow: hidden;
}

.tx-card-progress::-webkit-progress-bar { background-color: var(--code-bg); }
.tx-card-progress::-webkit-progress-value { background-color: var(--ink-muted); }
.tx-card-progress::-moz-progress-bar { background-color: var(--ink-muted); }

/* The cost line. Mono, like every other string the system produces; its ok/err
   colour comes from `.tx-status-ok` / `.tx-status-err`, set by the script. */
.tx-cost {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--step-mono);
  overflow-wrap: anywhere;
}

/* Outcome is carried by a word as well as a colour: the row reads the same in
   greyscale, on paper, and to a colour-blind reader. */
.tx-status-ok { color: var(--ink-muted); font-family: var(--font-mono); font-size: var(--step-mono); }
.tx-status-err { color: var(--accent); font-weight: 650; font-family: var(--font-mono); font-size: var(--step-mono); }

/* #376: the mechanical failure detail — demoted, never deleted. The status line
   carries the human sentence; this block carries the support handle (the TX code,
   the endpoint and status), collapsed until asked for and mono when opened, so it
   can be copied into a support message but never mistaken for the message. */
.tx-fail-detail summary {
  color: var(--ink-muted);
  font-size: var(--step-mono);
  cursor: pointer;
}
.tx-fail-detail code {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--step-mono);
  color: var(--ink-muted);
  overflow-wrap: anywhere;
}

/* ---- upload drop zone -------------------------------------------------- */

.tx-drop {
  position: relative;
  border: 1px dashed var(--rule);
  border-radius: var(--radius-card);
  background-color: var(--surface);
  padding: var(--space-5) var(--space-3);
  text-align: center;
  color: var(--ink-muted);
  margin: var(--space-3) 0;
}

.tx-drop.tx-over { border-color: var(--accent); border-style: solid; color: var(--accent); }

/* Three states: idle, drag-over (above), and has-files — once the queue holds
   something the zone gives the cards the room and stays as the way to add more. */
.tx-drop.tx-has-files { padding: var(--space-3) var(--space-3); }

.tx-drop-lead { margin: 0 0 0.3rem; color: var(--ink); }
.tx-drop-or { margin: 0 0 0.7rem; font-size: var(--step-mono); }

/* #339: the native file input is kept in the document — focusable, labelled, opened by
   Enter/Space — and clipped out of view. NOT `display: none` / `visibility: hidden`,
   which take it out of the tab order and off the accessibility tree; the control a
   keyboard reader reaches is the input, and the focus ring is drawn on its label. */
.tx-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The visible "Choose files" control is the input's <label>, dressed as the page's
   quiet button (the accent fills only the button that commits the action). */
.tx-file-button {
  display: inline-block;
  font: inherit;
  font-size: 1rem;
  font-weight: 650;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius-chip);
  border: 1px solid var(--rule);
  background-color: var(--surface);
  color: var(--ink);
  cursor: pointer;
  margin: 0;
}

.tx-file-button:hover { border-color: var(--accent); color: var(--accent-lift); }

#tx-file-input:focus-visible + .tx-file-button {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---- evidence artifacts ------------------------------------------------
   The coverage strip and the chain-of-custody certificate are the product's own
   artifacts, sharing their vocabulary with the marketing site's transcript figure.
   Kept here as the reviewed shapes; the surfaces that render them are wired
   separately. On any marketing surface the coverage figure ships BLANK — a published
   coverage number is a docs/MARKETING_SITE_SPEC.md §4 claim. */

.coverage {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.7rem 1.25rem;
  border-top: 1px solid var(--rule);
  font-size: 0.75rem;
  color: var(--ink-muted);
}

.coverage-label { letter-spacing: 0.08em; text-transform: uppercase; }
.coverage-track { flex: 1; height: 6px; background-color: var(--code-bg); border-radius: 3px; overflow: hidden; display: block; }
.coverage-fill { display: block; height: 100%; background-color: var(--accent); }
.coverage-value { font-variant-numeric: tabular-nums; font-family: var(--font-mono); }

.cert {
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-top: 3px solid var(--accent);
  border-radius: 0 0 var(--radius-card) var(--radius-card);
  padding: var(--space-3) var(--space-3) var(--space-4);
  max-width: 44rem;
}

.cert-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--rule-soft);
  font-weight: 650;
  letter-spacing: -0.008em;
}

.cert-head img { width: 18px; height: 18px; display: block; }

.cert-fields {
  display: grid;
  grid-template-columns: 9.5rem 1fr;
  row-gap: 0.75rem;
  column-gap: var(--space-3);
  padding-top: var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.78125rem;
  line-height: 1.55;
}

.cert-fields dt { color: var(--gutter); text-transform: uppercase; letter-spacing: 0.08em; font-size: var(--step-label); }
.cert-fields dd { margin: 0; word-break: break-all; }
.cert-fields dd.num { font-variant-numeric: tabular-nums; word-break: normal; }

/* ---- small screens ----------------------------------------------------- */

@media (max-width: 40rem) {
  .tx-row label { min-width: 0; flex-basis: 100%; }
  .cert-fields { grid-template-columns: 1fr; row-gap: 0.35rem; }
  .cert-fields dd { margin-bottom: 0.5rem; }
}

@media (max-width: 30rem) {
  main { padding-left: 1.25rem; padding-right: 1.25rem; }
  .app-head { padding-left: 1.25rem; padding-right: 1.25rem; }
}

/* ---- print -------------------------------------------------------------- */

@media print {
  body { font-size: 11pt; line-height: 1.45; }
  main { max-width: none; padding: 0; }
  button, .tx-drop, .coverage-track { display: none; }
  .card, .callout, .cert { break-inside: avoid; }
  h1, h2, h3 { break-after: avoid; }
}

/* ---- index rows ----------------------------------------------------------
   The nav itself now lives in shell.css, shared with the transcript surfaces that
   cannot link this file. */

/* An index row: name first, machine facts trailing. `.tx-list li` supplies the
   hairline rule and the flex row; these only govern what sits inside it. */
.tx-list li > .tx-meta { flex: 0 0 auto; }
.tx-list li > .coverage { flex: 0 0 auto; padding: 0; border-top: none; min-width: 9rem; }

/* A quarantined recording. The row states that the recording exists and carries
   nothing else — no filename, no coverage, no link. It is set in the accent so it
   reads as a disclosure rather than as a row someone forgot to fill in. */
.tx-privileged-row {
  color: var(--accent);
  font-weight: 650;
}

/* #467: the whole visible content of a quarantined row. Deliberately NOT `.tx-file-name` — that is
   the machine-strings group near the top of this sheet, and this cell holds a sentence from the
   legal registry, not a filename. (The rule this replaces existed only to undo that group's mono
   for this one cell, which is the sheet already fighting the borrowing.) The face now comes from
   `body`, the size from `.tx-table`, and the colour and weight from `.tx-privileged-row` above —
   this sentence is the most prominent thing in the table and is never demoted.

   `white-space` is declared HERE rather than left to the `.tx-privileged-row td` rule further
   down. That rule is about the CELL; this is about the sentence, and a rule that is deleted adds
   nothing for a guard to forbid, so only a positive declaration on this class keeps the sentence
   wrapping if that one goes. */
.tx-privileged-cell { white-space: normal; }

/* ---- the matter page's recording table (#336) ----------------------------
   A row used to be `filename · kind · state · coverage · Review` in a flex list,
   with no duration and no date. The questions a reader brings here are columnar —
   is it ready, how long is it, when did it arrive — so it is a table, styled in
   the same vocabulary as `.tx-list`: one hairline per row, machine facts in mono,
   the filename first and given the width. Same surface border as the list so the
   page does not change texture between an empty matter and a full one. */

.tx-table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--space-3) 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  background-color: var(--surface);
  font-size: var(--step-mono);
}

.tx-table th,
.tx-table td {
  padding: 0.45rem 0.75rem;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--rule-soft);
  white-space: nowrap;
}

.tx-table tbody tr:last-child td { border-bottom: none; }
.tx-table tbody tr:hover { background-color: var(--code-bg); }

/* Column labels: the same eyebrow the form labels use, so they read as labels and
   not as a row of data. */
.tx-table th {
  font-family: var(--font-sans);
  font-size: var(--step-label);
  font-weight: 650;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gutter);
  border-bottom: 1px solid var(--rule);
}

/* The filename column takes the slack; every other column is as wide as its content.
   `.tx-file-name` is a flex-item rule above (`flex: 1`); in a table cell the link is
   a block with an ellipsis instead. */
.tx-table td:first-child { width: 100%; max-width: 0; }
.tx-table td:first-child .tx-file-name { display: block; }

/* A `.tx-privileged-row` spans the table with one cell, which holds a sentence rather than a
   machine string, so it wraps. Belt and braces with `.tx-privileged-cell` above: this sets the
   CELL, that sets the sentence's own span, and #467's guard pins the latter. */
.tx-table .tx-privileged-row td { white-space: normal; }

/* #361: the cell now carries verdict + review load + demoted percent; let it wrap rather than
   force the row wide. */
.tx-table .coverage { padding: 0; border-top: none; min-width: 9rem; flex-wrap: wrap; }

/* The state chip. The raw token stays on `data-state` (matters.js::enumSpan), which is
   what the accent keys on: `needs_attention` and `failed` are the two outcomes that most
   need to reach a person (#331), and everything else — including a clean
   `ready_unverified` — is quiet, because the system has one accent and it means "the
   problem", never success (review.css says the same of the verdict chip). */
.tx-state-chip {
  display: inline-block;
  padding: 0.05rem 0.5rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background-color: var(--panel);
  color: var(--ink-muted);
  font-size: 0.75rem;
  font-weight: 650;
  font-family: var(--font-sans);
  line-height: 1.5;
}

/* Decision 23: `ready_accepted` is deliberately NOT accented — a delivered artifact a person
   accepted reads as delivered, not as a defect. */
.tx-state-chip[data-state="needs_attention"],
.tx-state-chip[data-state="failed"] {
  color: var(--accent);
  border-color: var(--accent);
  background-color: var(--code-bg);
}

/* #361: the completeness column's verdict chip — the banner's status_chip vocabulary in the
   table's own pill, severity on `data-severity`. Same palette logic as review.css's banner chip:
   `ok` stays the pill's quiet base, `warn` is the raised tone, and only `bad` spends the accent —
   the system has one accent and it means "the problem", never success. */
.tx-verdict-chip[data-severity="warn"] {
  background-color: var(--code-bg);
  color: var(--ink);
}

.tx-verdict-chip[data-severity="bad"] {
  color: var(--accent);
  border-color: var(--accent);
  background-color: var(--code-bg);
}

/* #400: the pre-terminal row. `pending` is not a verdict at all — the pipeline has not finished
   with this recording — so the pill stays on its quiet base and is dashed to read as provisional.
   No accent, exactly as `ok`: waiting is not the problem the accent is reserved for. */
.tx-verdict-chip[data-severity="pending"] {
  background-color: var(--panel);
  color: var(--ink-muted);
  border-style: dashed;
}

/* #361: the row's review load ("N words to check" / "confidence not returned") and the demoted
   percent. The verdict is the headline; these two are the quiet lines under it, and the percent
   only travels with its denominator sentence (composed server-side). #378 S2: the sentence is a
   native <details> body now — click/keyboard-openable, not hover-only — with the percent as its
   summary; the open row simply grows, and the sentence wraps to a readable width. */
.tx-review-load { white-space: nowrap; }

/* #398: the located-loss unit sentence, under the verdict chip. Same quiet register as the
   denominator sentence below it — the figure is in the chip, this says what the figure counts.
   #405 S9: behind a caret now, in the same <details> shape as the speech pair and the percent
   below it, because expanded it repeated itself once per flagged row down the matter. */
.tx-loss-detail { font-size: 0.7rem; }
.tx-loss-detail > summary { cursor: pointer; white-space: nowrap; }
.tx-loss-sentence {
  margin: 0.35rem 0 0;
  max-width: min(26rem, 70vw);
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--ink-muted);
  white-space: normal;
}

/* #421: WHERE the loss is, under the sentence that says what the figure counts. Links, because
   each one opens the review pane at that gap; they wrap, because a recording that dropped a
   channel on every part carries a dozen of them and a row must never widen the page. Quiet — the
   verdict chip above is the finding, these are its coordinates. */
.tx-loss-positions {
  margin: 0.35rem 0 0;
  max-width: min(26rem, 70vw);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  white-space: normal;
}

.tx-loss-position { margin-right: var(--space-1); white-space: nowrap; }

.tx-loss-positions-more {
  font-family: var(--font-sans);
  color: var(--ink-muted);
  white-space: nowrap;
}

/* #401: the non-silent-audio pair, in the same <details> shape as the percent below it and the
   same quiet register. The pair is the SUMMARY (one glance: how much non-silent audio the source
   carried, how much reached the page, and on which channel) and the caveat is the body, which
   elaborates what the loudness threshold does and does not count — #435 moved every clause that
   CORRECTS the figure into the summary, since the body is closed on first paint. No accent at any
   value — it is arithmetic under a verdict, not a second verdict. */
.tx-speech-detail { font-size: 0.7rem; }
/* #435: the summary WRAPS. It carries the instrument, the contamination clause and (where
   there is more than one) the channel, because those correct the reading and may not be
   collapsed — which makes it far too long for the one-line `nowrap` the bare pair used. */
.tx-speech-detail > summary {
  cursor: pointer;
  white-space: normal;
  max-width: min(26rem, 70vw);
}
.tx-speech-caveat {
  margin: 0.35rem 0 0;
  max-width: min(26rem, 70vw);
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--ink-muted);
  white-space: normal;
}

/* #401: the audio-class chips, beside the recording's kind. A FORMAT fact ("8 kHz phone audio",
   "two sides") and never a quality one, so it takes the quiet border every neutral token on this
   page takes and never the accent, which app.css spends on the outcomes that must reach a
   person. */
.tx-audio-chip {
  display: inline-block;
  margin-left: var(--space-1);
  padding: 0 0.4rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-chip);
  font-family: var(--font-sans);
  font-size: 0.7rem;
  color: var(--ink-muted);
  white-space: nowrap;
}

/* #510: the declared-kind question, in the same cell as the kind and its audio chips. NOT a chip
   and deliberately not styled as one: a chip is a FORMAT FACT in the quiet neutral register above,
   and this is a CORRECTION of the chip beside it — it tells the reader that label may not fit the
   audio. So it is a block sentence in ordinary ink, wrapping, never `--ink-muted` and never
   `nowrap`. `matters.js` took the same decision in as many words for the privileged disclosure:
   "this is a sentence, not a filename". It is absent from every ordinary row, so it costs the table
   nothing until it has something to say.

   NO `font-size`, deliberately: it INHERITS `.tx-table`'s. The first draft set `0.75rem` while the
   table is `--step-mono` (0.8125rem), which rendered a sentence saying the label may be wrong one
   step smaller than the label — the demotion `.tx-privileged-cell` above refuses in as many words
   ("this sentence is the most prominent thing in the table and is never demoted"). */
.tx-kind-band-note,
.tx-citation-band-note {
  display: block;
  margin-top: var(--space-1);
  max-width: var(--measure);
  font-family: var(--font-sans);
  color: var(--ink);
  white-space: normal;
}

/* #546: the kind control and the change note, in the kind cell under the two notes. The select is
   the page's own `select` (the picker on the upload page), narrowed to the cell; the note takes
   the band notes' treatment above — a visible text node in the open cell, inheriting the table's
   size, never demoted. */
.tx-kind-select {
  display: block;
  margin-top: var(--space-1);
  max-width: 14rem;
  font-size: inherit;
}

.tx-kind-change-note {
  display: block;
  margin-top: var(--space-1);
  max-width: var(--measure);
  font-family: var(--font-sans);
  color: var(--ink-muted);
  white-space: normal;
}

.tx-coverage-detail { font-size: 0.7rem; }
/* #512: the summary is a labelled SENTENCE now ("94% of this recording's duration is not accounted
   as missing non-silent audio"), not a bare percent — so it wraps instead of overflowing the
   column. The wrapping is the treatment `.tx-coverage-no-reading` below already needed for the
   same reason ("No percentage stated here" is a phrase too); widening it to the base selector is
   deliberate and it reaches exactly the two coverage carets — the speech pair styles its own
   summary at `.tx-speech-detail > summary`. */
.tx-coverage-detail > summary {
  cursor: pointer;
  white-space: normal;
  max-width: min(26rem, 70vw);
}
/* The PROSE face, and ONLY on the caret whose summary is prose. #512 first set `font-family` on the
   base selector above, which at specificity (0,1,1) outranks `.coverage-value` (0,1,0) — and the
   #436 no-reading `<details>` carries BOTH classes, so the sans face reached "No percentage stated
   here" and split it from its two absence siblings ("Not measured", "Speech not measured"), which
   are plain `.coverage-value` spans. The `:not()` restores the #436 invariant below rather than
   leaving its comment false. `:not()` adds no specificity of its own, so this rule is (0,2,1) and
   still beats `.coverage-value` where it does apply. */
.tx-coverage-detail:not(.tx-coverage-no-reading) > summary { font-family: var(--font-sans); }
/* #436: the third absence value sits in the percent's own slot, and it is a PHRASE rather than a
   figure ("No percentage stated here"), so it wraps instead of overflowing the column the way the
   `nowrap` this rule was written against would have it. Same slot, same mono value styling as its
   two absence siblings; only the wrapping differs. (The base rule above no longer sets `nowrap` —
   #512 made both coverage summaries wrap — so this rule's `white-space` is now a restatement
   rather than an override. It is kept: the invariant it records is "this phrase wraps", and a
   reader who deletes it should have to notice that the base rule is what is carrying it.) */
.tx-coverage-no-reading > summary { white-space: normal; max-width: min(26rem, 70vw); }
.tx-coverage-sentence {
  margin: 0.35rem 0 0;
  max-width: min(26rem, 70vw);
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--ink-muted);
  white-space: normal;
}

/* #512: the subtraction sentence, VISIBLE beside the percentage rather than behind its caret — it
   relates the verdict's seconds to the percentage, which is a correction and not an elaboration.
   Prose styling, like the caret bodies above it, so the cell still reads figures-first. */
.tx-coverage-subtraction {
  max-width: min(26rem, 70vw);
  font-family: var(--font-sans);
  font-size: 0.7rem;
  color: var(--ink-muted);
  white-space: normal;
}

/* The row menu: secondary actions behind one control, so the row keeps exactly one
   primary click target (the filename). A native `<details>`; the list is positioned
   under the toggle and closes itself when the toggle is clicked again. */
.tx-row-menu-cell { width: 1%; text-align: right; }

.tx-row-menu { position: relative; display: inline-block; }

.tx-row-menu-toggle {
  list-style: none;
  cursor: pointer;
  padding: 0 0.45rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-chip);
  background-color: var(--surface);
  color: var(--ink-muted);
  font-weight: 650;
  line-height: 1.5;
  user-select: none;
}

.tx-row-menu-toggle::-webkit-details-marker { display: none; }
.tx-row-menu-toggle::marker { content: ""; }
.tx-row-menu-toggle:hover { border-color: var(--accent); color: var(--accent-lift); }
.tx-row-menu-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.tx-row-menu-list {
  position: absolute;
  right: 0;
  top: calc(100% + 0.25rem);
  z-index: 2;
  list-style: none;
  margin: 0;
  padding: 0.25rem 0;
  min-width: 11rem;
  max-width: none;
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  text-align: left;
}

.tx-row-menu-list li { margin: 0; }

.tx-row-menu-list a {
  display: block;
  padding: 0.35rem 0.85rem;
  color: var(--ink);
  text-decoration: none;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  white-space: nowrap;
}

.tx-row-menu-list a:hover { background-color: var(--code-bg); color: var(--accent-lift); }

/* The title row: the matter's name, and its id as a copy chip beside it (#336). The chip
   is the #334 context-header control; `.tx-copy-id` is re-declared here because the
   transcript stylesheets do not load on this page. */
.tx-title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}

.tx-title-row h1 { margin-bottom: 0; }
.tx-context-id { margin: 0; }

.tx-copy-id {
  margin-left: 0.25rem;
  padding: 0 0.45rem;
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 650;
  color: var(--ink-muted);
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-chip);
  cursor: pointer;
}

.tx-copy-id:hover { border-color: var(--accent); color: var(--accent-lift); }
.tx-copy-id:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Confirmation is a border, never a green field (review.css says the same of its copy button). */
.tx-copy-id.tx-copied { border-color: var(--accent); color: var(--accent); }

/* A column label that exists for a screen reader and not for the eye. */
.tx-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
/* ---- login + the one-time-link pages --------------------------------- */
/* The credential surfaces: `templates/login.html`, and `set_password.html` /
   `link_status.html` (#G5/#G6), which reuse these rules verbatim — a password form is a
   password form, and a second private style for the reset page is how two entrances to the
   same product start looking like two products. Tokens only — test_design_tokens.py forbids a
   colour literal anywhere in this file outside a :root declaration. */

main.login-card {
  max-width: var(--measure);
}

.login-card form {
  margin-top: var(--space-3);
}

.login-card input {
  width: 100%;
}

.login-card button {
  margin-top: var(--space-3);
}

.login-notice,
.login-error {
  border-left: 2px solid var(--accent);
  padding-left: var(--space-2);
  color: var(--ink-muted);
}

/* ---- review entry point --------------------------------------------------
   The review pane had NO link anywhere in the product: the matter list linked
   only to the read-only view, _nav.html does not mention it, and review.js
   fetches the data endpoints but never the shell. Everything that makes a
   transcript workable sat behind a URL a user could only reach by typing it. */

.tx-review-link {
  margin-left: var(--space-2);
  font-size: 0.9rem;
  white-space: nowrap;
}

/* ---- page actions (#190) -------------------------------------------------
   PDF export and search-within-matter both worked and neither had a link
   anywhere in the product: the only way to reach either was to type the URL.
   This is the row those affordances sit in — a row of links, not a second nav,
   because they act on the thing the page is about rather than moving between
   pages. */

.tx-page-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
  font-size: var(--step-mono);
}

/* ---- speakers in this matter (#422, #433) --------------------------------
   The roster reuses the access panel's shapes exactly — `.tx-list` rows, the
   `.tx-access-name` treatment for a person, `.tx-meta` for the machine strings —
   so it introduces no new colour and no new geometry. The only thing it adds is
   the wrap behaviour a person with several appearances needs.

   #433 splits it into two blocks under one heading; both `<ul>`s are `hidden`
   until their own rows arrive, because `.tx-list` carries a border and a surface
   colour and an empty one paints as a card saying "a list, and nothing in it". */

.tx-roster { margin-top: var(--space-5); }

/* One person's appearances, wrapping under the name rather than pushing the row
   wide: a witness on six recordings is the case this list exists for. */
.tx-roster-where {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: baseline;
}

.tx-roster-appearance {
  display: inline-flex;
  gap: var(--space-2);
  align-items: baseline;
}

/* #509: the row is a RECORDING now — filename, count, control — so `.tx-file-name`
   keeps its `flex: 1` and takes the row, and the two machine facts trail it. The
   `flex: 0 1 auto` override that used to sit here belonged to the old shape, where
   the leading item was a speaker designation and the recording link trailed.

   The whole row is no longer a click target and must stop looking like one: it
   carries two real controls instead. `#tx-results li` is the precedent and it is
   TWO rules — the cursor and the hover field — because a row that lights up under
   the pointer promises the same click the cursor does. */
.tx-roster-unnamed { justify-content: space-between; }
#tx-roster-unnamed-list li { cursor: default; }
#tx-roster-unnamed-list li:hover { background-color: var(--surface); }

/* The folded rows. `[hidden]` is a UA declaration; `.tx-list li { display: flex }` above
   is an AUTHOR one and beats it, so `li.hidden = true` would set the attribute and
   change nothing on screen — the cap would fold no row, and the reveal button would
   sit under a list that was already whole. That was #509's own shipped defect and the
   FIFTH per-element `[hidden]` patch for one trap; #558 retired all five for the single
   `!important` reset in `tokens.css`, which every page loads, and a guard that models
   the cascade rule rather than the spelling
   (tests/guards/test_hidden_wins_the_cascade.py). Nothing here needs to say `[hidden]`. */

/* The row's own control. Sits at the row's end at its natural width, beside the
   count, so the filename keeps the flexible column. No new colour and no new
   geometry: it is a link, and the sheet's link rules already carry the accent. */
.tx-roster-name { flex: 0 0 auto; font-size: var(--step-mono); }

/* The reveal. A quiet default button — the accent fills exactly one button per page
   and it is never this one — separated from the list it opens. */
.tx-roster-more { margin-top: var(--space-3); }

/* ---- sharing a matter (#84) ----------------------------------------------
   The access panel. Built entirely from the shapes already in this file —
   `.tx-list` rows, the shared form controls, `.tx-error` for a refusal — so it
   introduces no new colour and no new geometry, only the spacing that separates
   it from the recording list above it. */

.tx-access { margin-top: var(--space-5); }

/* A colleague's name on an access row (#337). Sans, because it is a person and not a machine
   string — the same distinction that keeps `.tx-privileged-cell` OUT of the machine-strings group
   above (#467) — and it takes the row's flexible width so the trailing user id, level and button
   stay put. */
.tx-access-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tx-form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  margin: var(--space-3) 0;
}

.tx-form label {
  margin: 0;
  font-size: var(--step-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gutter);
}

/* Withdrawing access is a destructive act with no undo beyond re-granting, so it
   is never the accent-filled button on the page — it stays the quiet default and
   is set apart by position, not by colour. */
.tx-unshare {
  margin-left: auto;
  font-size: 0.9rem;
  padding: 0.2rem 0.6rem;
  white-space: nowrap;
}

/* #477: the signup honeypot. Same rule, same class name, as the marketing site's
   (site/style.css) — a real browser never sees or fills it. display:none rather than an
   inline style attribute because the app CSP's style-src is 'self' with no 'unsafe-inline'. */
.hp-field { display: none; }
