/* ===========================================================================
   Review & file — the triage queue, the reconciliation bar, the item picker,
   the settled list and the manual-line composer.

   Linked by index.html, render-blocking, with the data-review-styles marker
   that receipts/review.js and transfers/view.js guard on; either module links
   it as the fallback, so a screen still cannot ship without its styling.

   Every value here comes from the token set in styles.css. No literal colour,
   no raw z-index, no fixed width on anything holding a translated string: every
   card cell is content-sized or minmax(0, 1fr), so a French label that runs 30
   percent longer wraps or pushes down instead of clipping.
   =========================================================================== */

/* ── 1. Regions ─────────────────────────────────────────────────────────── */
/* The workspace (REDESIGN 4.3): the paper on the left, the receipt on the
   right, the batch strip over both; the list of receipts opens under it. */
.review-split.rvq-shell { grid-template-columns: minmax(0, 45fr) minmax(0, 55fr); grid-template-areas: "list list" "paper work"; }
.rvq-work { --rvq-gap: var(--space-4); grid-area: work; display: flex; flex-direction: column; gap: var(--rvq-gap); }

/* ── 2. The reconciliation bar ──────────────────────────────────────────── */
/* It never scrolls away: it is the number the operator is working toward, and
   it is the reason the total is now checked live instead of at the moment of
   the irreversible write. The horizontal bleed lets it sit edge to edge in the
   padded work column while it is stuck. */
.recbar {
  position: sticky; top: var(--sticky-top); z-index: var(--layer-sticky);
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-3);
  min-height: var(--recbar-h);
  margin: 0 calc(var(--space-4) * -1) var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-raised);
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
}
/* When the bar is stuck it floats over the queue; the shadow says so and only
   then — a resting bar is part of the panel. receipts/stuckBar.js adds the
   class, from one IntersectionObserver on the mark below. */
.recbar.is-stuck { box-shadow: var(--elev-1); }
/* The zero-height mark barHtml draws immediately before the bar: when it has
   gone above the line the bar sticks to, the bar is stuck. In the work column
   it is a flex item like any other, so it takes the column's gap back rather
   than opening a second one between the receipt's name and the bar. */
.recbar-sentinel { height: 0; }
.rvq-work > .recbar-sentinel { margin-bottom: calc(var(--rvq-gap) * -1); }
/* What lies under the bar clears it. Once the bar sticks, a focus that scrolled a
   control to the top edge put it behind the bar (WCAG 2.4.11), so --recbar-clear adds
   the bar to what the wizard's scroll margin in styles.css clears. The composer opens
   directly under the bar and keeps a margin of its own: receipts/review.js measures
   the bar as it opens it and sets --rv-manual-clear, since a bar carrying a note or a
   wrapped figure is taller than --recbar-h, the floor every other control clears. */
.recbar ~ * { --recbar-clear: var(--rv-manual-clear, var(--rv-head-h, var(--recbar-h))); }
.rv-manual { scroll-margin-top: calc(var(--sticky-top) + var(--rv-manual-clear, var(--recbar-h)) + var(--space-2)); }
/* The figures are the kit's summary tiles (.ui-summary in styles.css); a
   nonzero gap is its attention tile. What stays here is the one constraint of
   a receipt's own data: an unbroken value wraps inside its tile rather than
   running into the next one. A filed receipt's POS reference, a reference and
   not an amount, is said in the received pill beside the supplier, which may
   wrap anywhere for the same reason (UX_POLISH_BACKLOG 8.9). */
.recbar-fig { min-width: 0; }
.recbar-fig-v { overflow-wrap: anywhere; transition: color var(--dur-2) var(--ease-out); }
.rv-ident-state { min-width: 0; max-width: 100%; }
.rv-ident-state .state { white-space: normal; overflow-wrap: anywhere; font-variant-numeric: tabular-nums lining-nums; }

.recbar-actions { margin-left: auto; display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
.recbar-note { flex: 1 0 100%; display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.recbar-note:empty { display: none; }
.recbar-note-line { margin: 0; font-size: var(--text-xs); color: var(--ink); max-width: var(--prose-max); }
.recbar-diag { min-width: 0; }
.skeleton--bar { display: inline-block; width: 88px; height: 1em; vertical-align: middle; }

@media (max-width: 620px) {
  .recbar { flex-direction: column; }
  .recbar-actions { margin-left: 0; width: 100%; }
  .recbar-actions > .btn--commit { flex: 1 1 100%; }
}

/* ── 3. The question bar (REDESIGN 4.3.2 item 2) ───────────────────────── */
.rvq-bar { display: flex; align-items: center; gap: var(--space-2); min-height: var(--control-h); padding-top: var(--space-2); border-top: 1px solid var(--rule); }
/* One line, whatever the question: the bar never grows and pushes the lines. */
.rvq-bar-text {
  flex: 1 1 auto; min-width: 0; margin: 0;
  font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--ink-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rvq-icon-prev { transform: rotate(180deg); }
.rvq-bar[data-open="0"] .rvq-bar-text { color: var(--ink); font-weight: var(--weight-regular); }
/* The receipt's identity, editable in place: the supplier, then the number and
   the date as fields with their chrome showing (REDESIGN 4.3, B6). */
.rv-ident { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); min-width: 0; }
.rv-supplier {
  min-width: 0; max-width: 100%; font-size: var(--text-md); font-weight: var(--weight-semibold); color: var(--ink-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rv-ident-field { display: inline-flex; align-items: center; gap: var(--space-2); }
.rv-ident-k {
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-muted);
}
.rv-ident-v { font-size: var(--text-sm); color: var(--ink-strong); font-variant-numeric: tabular-nums lining-nums; }
.rv-ident-dup { display: inline-flex; }
/* Compact: one header line carries the flag, the receipt line's name and the
   figures; the sentence, the controls and the alternatives stack under it. */
.tri-card {
  display: flex; flex-direction: column;
  gap: var(--space-2);
  padding: var(--row-pad-y) var(--row-pad-x);
  background: var(--surface-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  --tri-edge: var(--rule-strong); box-shadow: var(--edge) var(--tri-edge);
  min-width: 0;
  transition: box-shadow var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out);
}
.tri-card:focus-within { border-color: var(--rule-strong); }
/* The ring rides over the severity edge (--tri-edge, set below), so focus never erases the flag. :focus too:
   review.js moves focus here after a mouse click resolves the card before, and Chrome draws no :focus-visible. */
.tri-card:focus, .tri-card:focus-visible { outline: 2px solid transparent; outline-offset: 2px; box-shadow: var(--focus-shadow), var(--edge) var(--tri-edge); }
.tri-head { display: flex; align-items: flex-start; gap: var(--space-3); flex-wrap: wrap; min-width: 0; }
.tri-heading { flex: 1 1 220px; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
/* The pill family's box, the same as .state and .badge in styles.css: symmetric
   --space-2 padding, the icon --space-1 from its word. */
.tri-flag {
  display: inline-flex; align-items: center; gap: var(--space-1);
  flex: none; margin-top: 1px;
  padding: 2px var(--space-2);
  border: 1px solid var(--rule); border-radius: var(--radius-pill);
  font-size: var(--text-xs); font-weight: var(--weight-medium);
  white-space: normal;
}
.tri-flag svg { width: 14px; height: 14px; }
.tri-hint { display: flex; align-items: flex-start; gap: var(--space-2); color: var(--ink); }
.tri-hint svg { flex: none; margin-top: 2px; color: var(--accent); }
/* The catalogue has what the line reported: said in the done colour, above
   the picker, with the match one press away. */
.tri-hint--found { color: var(--state-done-fg); font-weight: var(--weight-medium); }
.tri-hint--found svg { color: var(--state-done-fg); }
.tri-flag svg { flex: none; }
/* Severity is carried by the flag WORD and the icon shape first; the colour only
   repeats them, so the meaning survives a monochrome screen. */
.tri-card[data-severity="block"] { --tri-edge: var(--sev-block); }
.tri-card[data-severity="block"] .tri-flag { color: var(--sev-block); background: var(--sev-block-soft); border-color: var(--state-blocked-rule); }
.tri-card[data-severity="money"] { --tri-edge: var(--sev-money); }
.tri-card[data-severity="money"] .tri-flag { color: var(--sev-money); background: var(--sev-money-soft); border-color: var(--state-attention-rule); }
.tri-card[data-severity="notice"] { --tri-edge: var(--sev-notice); }
.tri-card[data-severity="notice"] .tri-flag { color: var(--sev-notice); background: var(--sev-notice-soft); border-color: var(--accent-rule); }

.tri-figs { margin-left: auto; display: flex; gap: var(--space-4); flex-wrap: wrap; flex: none; }
.tri-fig { display: flex; flex-direction: column; gap: 1px; text-align: right; }
.tri-fig-k {
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-muted);
}
.tri-fig-v { font-size: var(--text-sm); color: var(--ink-strong); font-variant-numeric: tabular-nums lining-nums; }

.tri-body { min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
/* One section per open question on the line. With two or more, each carries
   its own small flag and a dashed rule separates them; alone, the header's
   flag is the question's. */
.tri-issue { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.tri-issue + .tri-issue { padding-top: var(--space-2); border-top: 1px dashed var(--rule); }
/* The one member of the pill family that is not a pill, on purpose. It is the
   caption of a section inside a card that already carries a bordered flag in its
   header, and a second bordered pill two lines under the first read as a second
   severity rather than a subheading. So it keeps the family's --space-1 gap and
   drops the box: no padding, no border, no fill, an uppercase label in the
   severity's ink. */
.tri-issue-flag {
  display: inline-flex; align-items: center; gap: var(--space-1); align-self: flex-start;
  font-size: var(--text-2xs); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-caps); text-transform: uppercase;
  color: var(--ink-muted);
}
.tri-issue-flag svg { width: 12px; height: 12px; }
.tri-issue-flag[data-severity="block"] { color: var(--sev-block); }
.tri-issue-flag[data-severity="money"] { color: var(--sev-money); }
.tri-flag-n { align-self: center; font-size: var(--text-xs); color: var(--ink-muted); }
/* What hidden says stays hidden, whatever display a class gives the element. */
.tri-alt[hidden], .rv-answer-row[hidden], .rvq-shell .review-list[hidden] { display: none; }
.tri-body:empty { display: none; }
.tri-title {
  margin: 0; font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--ink-strong); word-break: break-word;
}
.tri-sku { margin: 0; display: flex; gap: var(--space-2); font-size: var(--text-xs); color: var(--ink-muted); }
.tri-line-no { font-variant-numeric: tabular-nums lining-nums; }
.tri-why { margin: 0; font-size: var(--text-xs); color: var(--ink-muted); line-height: var(--leading-loose); max-width: var(--prose-max); }
/* The figures inside a card's sentence, told apart by role and not by
   position: what the receipt prints (new), what it is checked against (ref),
   what the app computed (calc), and a name. The same three tones colour the
   figure block above the sentence, so the eye reads one thing twice. */
.tri-why .v { font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums lining-nums; white-space: nowrap; }
.tri-why .v--new, .tri-fig--new .tri-fig-v { color: var(--state-attention-fg); }
.tri-why .v--ref, .tri-fig--ref .tri-fig-v { color: var(--accent); }
.tri-why .v--calc, .tri-fig--calc .tri-fig-v { color: var(--ink-strong); }
.tri-why .v--name { color: var(--ink-strong); font-weight: var(--weight-medium); }
.tri-fig--new .tri-fig-v, .tri-fig--ref .tri-fig-v { font-weight: var(--weight-semibold); }

/* Top-aligned, so a refused figure's sentence grows its own field downward
   without lifting the buttons beside it. The inline field's label is hidden, so
   the field starts with its control, like the buttons do; the lines that are
   text and not a control (the hint under the controls, the catalogue's "found"
   note on a reported card) take a whole line of their own below, never a slot
   beside a control, where no alignment would put them level with it. */
.tri-fix { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-2); min-width: 0; margin-top: var(--space-1); }
/* A card button is small, a field is not: beside a 36px field a 32px button
   would leave a step along the bottom of the line. Only on a line that holds a
   field do the buttons stand at the field's height, so a card of buttons alone
   keeps the compact size. */
.tri-fix:has(> .field--inline) > .btn { min-height: var(--control-h); }
/* The "found" note sits above the button that uses the match, as the sentence
   the press answers, rather than beside it at the top of a taller button. */
.tri-fix > .tri-hint--found { flex: 1 0 100%; }
.tri-fix .field { flex: 0 1 220px; min-width: 0; }
/* On a card the field sits in the line of controls: its label is for screen
   readers (the placeholder, or the select's first option, says it on screen). */
.tri-fix .field--inline { flex: 0 1 auto; margin: 0; }
.tri-fix .field--inline .field-label { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; clip-path: inset(50%); overflow: hidden; white-space: nowrap; }
.tri-fix .field--inline .input, .tri-fix .field--inline .select { margin-top: 0; }
.tri-fix .input--amount { width: 130px; }
.tri-fix-hint { flex: 1 0 100%; margin: 0; font-size: var(--text-xs); color: var(--ink-muted); }
/* The "on the report" note: a plain state chip, never louder than the card's
   own question - reporting settles nothing, it only tells the supplier. */
.tri-body .tri-reported { align-self: flex-start; margin-top: var(--space-1); }
.stl-main .tri-reported { margin-top: var(--space-1); }
.tri-alt {
  display: flex; flex-wrap: wrap; gap: var(--space-1);
  margin-top: var(--space-1); padding-top: var(--space-2); border-top: 1px solid var(--rule);
}
/* The alternatives are quiet; the destructive one only turns red when it is
   the thing under the pointer. Fifty red "Remove" links is a wall of alarm. */
.tri-alt .btn--danger:not(:hover):not(:focus-visible):not(:disabled):not([aria-disabled="true"]), .stl-actions .btn--danger:not(:hover):not(:focus-visible):not(:disabled):not([aria-disabled="true"]) { color: var(--ink-muted); }
.tri-picker { flex: 1 0 100%; min-width: 0; }
.tri-notice-lines { flex: 1 0 100%; min-width: 0; }

.tri-empty {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-6) var(--space-4);
  background: var(--surface-sunken); border: 1px solid var(--rule); border-radius: var(--radius-md);
}
/* Ready: green, an icon, and the act itself - the good news reads as a card
   like every question did, not as leftover text. */
.tri-empty--ready {
  flex-direction: row; align-items: flex-start; gap: var(--space-3);
  background: var(--state-done-bg); border: 1px solid var(--state-done-rule); border-radius: var(--radius-md);
  box-shadow: var(--edge) var(--state-done-edge);
  padding: var(--space-3) var(--space-4);
}
.tri-empty--ready .tri-empty-icon { background: var(--surface-raised); color: var(--state-done-fg); }
.tri-empty--ready .tri-empty-text { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.tri-empty--ready .tri-empty-title { color: var(--state-done-fg); }
.tri-empty--ready .tri-empty-body { color: var(--ink); }
.tri-empty-title { margin: 0; font-size: var(--text-md); font-weight: var(--weight-medium); color: var(--ink-strong); }
.tri-empty-body { margin: 0; font-size: var(--text-xs); color: var(--ink-muted); line-height: var(--leading-loose); max-width: var(--prose-max); }
.tri-empty-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-2); }

/* ── 4. The item picker ─────────────────────────────────────────────────── */
.pick { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.pick-label {
  font-size: var(--text-xs); font-weight: var(--weight-medium); color: var(--ink-muted);
}
.pick-input { width: 100%; }
.pick-chips { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.pick-chips:empty { display: none; }
.pick-chip {
  padding: 1px var(--space-2);
  border: 1px solid var(--rule); border-radius: var(--radius-pill);
  background: var(--surface-sunken); color: var(--ink);
  font: inherit; font-size: var(--text-xs); cursor: pointer;
}
.pick-chip:hover { background: var(--surface-hover); }
/* A chip is a button and takes the shared ring of styles.css, offset and all. */
.pick-scope { margin: 0; }
.pick-scope-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; }
.pick-scope-row:has(.sr-only:only-child) { display: none; }
.pick-reload { margin-left: auto; }
.pick-reload svg { width: 13px; height: 13px; }
/* The vertical pitch here is --pick-row-h rather than padding, because what
   matters in a suggestion list is how many candidates are in view; the compact
   rung re-pitches that token with the rest, so the list follows the density
   without this rule knowing about it. */
.pick-head, .pick-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  gap: var(--space-4);
  align-items: baseline;
  padding: 0 var(--row-pad-x-dense);
}
.pick-head {
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-muted);
}
.pick-list {
  margin: 0; padding: 0; list-style: none;
  max-height: var(--pick-max-h); overflow: hidden auto;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--surface-raised);
  scrollbar-gutter: stable;
}
.pick-row { min-height: var(--pick-row-h); align-content: center; cursor: pointer; }
.pick-row:hover { background: var(--surface-hover); }
/* The active option is where ArrowDown has landed and the row Enter will choose,
   and matching lines against the catalogue is the task, so the keyboard position
   has to survive an older laptop in daylight. The tint is 1.15:1 light and 1.11:1
   dark against --surface-raised; the edge sits on the tint, where the old
   --accent-rule measured 1.32:1 and 1.52:1, effectively invisible. The edge is
   now --accent, 4.71:1 light and 6.83:1 dark on that same tint, the colour the
   selected queue row (.rv-item.sel) wears, at the one --edge width (DESIGN_SYSTEM
   2.8); a 1px --accent ring and a semibold name say it again as a shape and a
   weight, so it holds in a greyscale screenshot (SC 1.4.1, 1.4.11, 2.4.7).
   The ring is an inset shadow and not a border, so the row keeps its pitch and
   the list does not jump as the cursor moves. */
.pick-row[aria-selected="true"] {
  background: var(--surface-selected);
  box-shadow: var(--edge) var(--accent), inset 0 0 0 1px var(--accent);
}
.pick-row[aria-selected="true"] .pick-name { font-weight: var(--weight-semibold); color: var(--ink-strong); }
.pick-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-sku, .pick-unit, .pick-last { font-size: var(--text-xs); color: var(--ink-muted); white-space: nowrap; }
.pick-last-src {
  margin-left: var(--space-1);
  font-size: var(--text-2xs); letter-spacing: var(--tracking-caps); text-transform: uppercase;
}
/* The full catalogue stays reachable, but nothing below this line is a
   suggestion: a zero-similarity row was being labelled "suggested" before. */
.pick-sep {
  padding: var(--space-2) var(--space-2) var(--space-1);
  border-top: 1px solid var(--rule);
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-muted);
}
.pick-more { padding: var(--space-2); font-size: var(--text-xs); color: var(--ink-muted); }
.pick-empty {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-4);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  font-size: var(--text-xs);
}
.pick-empty p { margin: 0; }
.pick-empty-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.pick-chosen {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--rule-strong); border-radius: var(--radius-sm);
  background: var(--surface-sunken);
}
.pick-chosen-line { margin: 0; display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.pick-chosen-k {
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-muted);
}
.pick-chosen-v { font-weight: var(--weight-semibold); color: var(--ink-strong); min-width: 0; }
.pick-unit-label { font-size: var(--text-xs); color: var(--ink-muted); }
.pick-unit-fixed { margin: 0; display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.pick-why { margin: 0; font-size: var(--text-xs); color: var(--ink-muted); }
.pick-chosen-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
/* The quantity beside the unit when the picker adds to a transfer: one row,
   the quantity first because it is what the operator has in mind. Every field
   carries a label of the same height, so aligning the tops lines the controls
   up and a sentence under one field no longer raises the other. */
.pick-chosen-fields { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: flex-start; }
.pick-chosen-fields .field { margin: 0; }
.pick-chosen-fields .pick-qty { width: 96px; text-align: right; font-variant-numeric: tabular-nums lining-nums; }
.pick-chosen-fields .select { min-width: 160px; }
/* An item with one unit: the unit is said at the height of the field beside
   it, so the two read as one row of settings. */
.pick-unit-value { display: inline-flex; align-items: center; min-height: var(--control-h); font-weight: var(--weight-semibold); color: var(--ink-strong); }

@media (max-width: 620px) {
  .pick-list { max-height: 240px; }
  .pick-head { display: none; }
  .pick-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); padding: var(--row-pad-y-dense) var(--row-pad-x-dense); }
}

/* Forced colours drop the tint and both shadows, which would leave the active
   option marked by its weight alone. A real outline in Highlight stands in for
   them, offset inwards because the list scrolls and would clip it otherwise. */
@media (forced-colors: active) {
  .pick-row[aria-selected="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
}

/* ── 5. Settled lines ───────────────────────────────────────────────────── */
.stl {
  border: 1px solid var(--rule); border-radius: var(--radius-md);
  background: var(--surface-raised);
}
.stl-summary {
  display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
}
/* The summary is a heading row, not a <summary>, so nothing focuses it and its ring rule is gone. */
.stl-summary-k { font-weight: var(--weight-semibold); color: var(--ink-strong); }
.stl-summary-n { font-size: var(--text-xs); color: var(--ink-muted); }
.stl-summary-v { margin-left: auto; font-weight: var(--weight-semibold); color: var(--ink-strong); }
.stl-body { border-top: 1px solid var(--rule); }
/* The settled lines are a table, one receipt line per row, with the columns an
   operator checks the paper against: the receipt's own words, the POS item it
   becomes, quantity, unit, price, amount. Rows alternate so the eye can hold
   one; nothing is truncated, a long name wraps in its cell. */
.stl-scroll { border: 0; border-radius: 0; background: transparent; overflow: visible; }
/* Fits the pane: no minimum width and no sideways scroll. The item cell takes
   what is left once the numbers and the one action column have their share. */
/* FIXED layout: the table is exactly as wide as its pane, whatever a cell
   holds - a long name wraps, the picker row fits inside its colspan - instead
   of the widest content pushing the whole page sideways. */
.stl-table { width: 100%; table-layout: fixed; border-collapse: collapse; }
/* Transfer compose draws this table as .stl-table.tf-table, wider; transfers.css
   sets the --stl-* properties on .tf-table alone, so the widths, the quantity
   field, the heading wrap and the stacked row's spacing resolve per element,
   never by which of the two sheets was linked last. */
.stl-table col.c-no { width: 40px; }
.stl-table col.c-qty { width: var(--stl-c-qty, 72px); }
/* The unit column holds a word ("sac", "caisse") or the select.unit-inline
   dropdown capped at the cell's width, and the unit is a figure the operator
   checks against the paper, so the column keeps the width that shows it with
   its arrow. The product name's room comes from the pane collapse below, which
   stacks the row once the pane is too narrow for seven columns beside a name,
   not from narrowing a figure until it clips. */
.stl-table col.c-unit { width: var(--stl-c-unit, 112px); }
.stl-table col.c-price { width: var(--stl-c-price, 104px); }
.stl-table col.c-amount { width: var(--stl-c-amount, 104px); }
.stl-table col.c-actions { width: var(--stl-c-actions, 76px); }
/* Not sticky: the pane's own bar is what stays put, and a second sticky row
   under it sat on top of the bar's buttons. */
.stl .stl-table thead th { position: static; top: auto; }
.stl-table th {
  padding: var(--row-pad-y-dense) var(--row-pad-x-dense);
  font-size: var(--text-2xs); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-caps);
  text-transform: uppercase; color: var(--ink-muted); text-align: left; white-space: var(--stl-th-wrap, nowrap);
  border-bottom: 1px solid var(--rule);
}
.stl-table th.num, .stl-table td.num { text-align: right; }
.stl-table td { padding: var(--row-pad-y-dense) var(--row-pad-x-dense); vertical-align: middle; border-bottom: 1px solid var(--rule); }
.stl-row:nth-child(even) > td { background: var(--surface-nav); }
.stl-row:hover > td { background: var(--surface-hover); }
/* The keyboard's half. The field chrome inside this row already appears under
   :focus-within (below); the row itself did not, so an operator tabbing down a
   settled table saw a boundary draw itself around one input with nothing saying
   which line it belonged to - and the line is what they are checking. */
.stl-row:focus-within > td { background: var(--surface-hover); }
.stl-row:last-child > td { border-bottom: none; }
/* A row that will not be filed says so in words. It used to say it a second time
   with opacity .7, which took the line's SKU to 3.06:1 over the zebra - and the
   SKU is how the operator identifies the line they deliberately excluded. It
   recedes onto the sunken surface instead, the pattern styles/filing.css already
   uses for a row nothing was sent to. The zebra and the hover rules above are
   single-class, so the exclusion carries :nth-child to outrank the stripe and
   :not(:hover):not(:focus-within) so the pointer and the keyboard both still
   answer - the recession is what a resting excluded row says, not what it says
   while the operator is standing on it.

   Both variants also carry .stl-table. Without it the first one is (0,2,1) and
   loses to the narrow-pane reset further down, which is (0,2,3) and paints every
   cell transparent - so in a squeezed pane an ODD excluded row showed no
   recession at all, which is half of them, exactly where the layout is tightest.
   With the table class both variants outrank it. */
.stl-table .stl-row--out:not(:hover):not(:focus-within) > td,
.stl-table .stl-row--out:nth-child(even):not(:hover):not(:focus-within) > td { background: var(--surface-sunken); }
.stl-no { width: 36px; color: var(--ink-muted); font-size: var(--text-xs); text-align: right; }
.stl-main { min-width: 0; }
.stl-actions { width: auto; }
.stl-name { display: block; font-weight: var(--weight-medium); color: var(--ink-strong); overflow-wrap: anywhere; }
.stl-name .stl-sku { font-weight: var(--weight-regular); color: var(--ink-muted); font-size: var(--text-xs); }
/* What the line became, as badges under it: the POS item (with its code) and
   the way it was matched. A badge, not a column - the receipt line is the row. */
.stl-badges { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-top: 2px; }
.stl-combined { display: block; font-size: var(--text-2xs); color: var(--ink-muted); }
/* The quantity field narrows or widens with its column: the transfer table sets
   --stl-qty-input on .tf-table, for the same reason as the column widths above. */
.stl-qty .qty-input { width: var(--stl-qty-input, 64px); }
.stl-price .price-input { width: 92px; }
/* The numbers read as numbers. A row is a line of the receipt, not a form: the
   field chrome (border, fill) appears when the pointer is on the row or the
   field has focus, and the value itself never moves - same box, same place.

   Every rule below excludes a field the parser refused. That exclusion, and not
   a second copy of the red paint, is how the recession and the refusal live
   together: styles.css owns one definition of what a refused value looks like,
   and it was losing the cascade here because .stl-row .qty-input is two classes
   and .qty-input[aria-invalid] was one. With :not() these rules simply do not
   apply to a refused field, so the shared paint reaches it unopposed and there
   is nothing to keep in step. :where() holds the three field names so the
   selectors stay readable and add no specificity of their own.

   It matters: review.js marks these fields on every keystroke, and the settled
   table's markup is what Transfers composes a transfer from, where only one of
   the nine refusal codes toasts. A quantity of 12.555 was refused, dropped out
   of the total, and looked exactly like an accepted number. */
.stl-row :where(.qty-input, .price-input, .unit-inline):not([aria-invalid="true"]) {
  border-color: transparent; background-color: transparent;
  transition: border-color var(--dur-1) linear, background var(--dur-1) linear;
}
.stl-row:hover :where(.qty-input, .price-input, .unit-inline):not([aria-invalid="true"]),
.stl-row:focus-within :where(.qty-input, .price-input, .unit-inline):not([aria-invalid="true"]),
.stl-row :where(.qty-input, .price-input, .unit-inline):focus:not([aria-invalid="true"]) {
  border-color: var(--rule-control); background-color: var(--surface-raised);
}
/* The focused field's ring is the shared one of styles.css, refused or not: nothing here blanks a
   box-shadow any more, so the two restatements that stood here went with the halo they fought. What the
   row still owes the field is the focus border, on plain :focus as in styles.css, which the reveal above outranks. */
.stl-row :where(.qty-input, .price-input, .unit-inline):focus:not([aria-invalid="true"]) { border-color: var(--focus-ring); }
/* Where there is no pointer there is no reveal, so the chrome is simply there -
   and once it is there it is the field's only boundary, which makes it a
   user-interface component under SC 1.4.11. --rule-strong is 1.61:1 on the
   raised fill and cannot serve; --rule-control is 4.05:1. */
@media (hover: none) {
  .stl-row :where(.qty-input, .price-input, .unit-inline):not([aria-invalid="true"]) { border-color: var(--rule-control); background-color: var(--surface-raised); }
}
.stl-qty, .stl-unit, .stl-price { white-space: nowrap; }
.stl-amount { font-weight: var(--weight-medium); color: var(--ink-strong); white-space: nowrap; }
/* ONE column for every action on the line. review.js pushes up to four icon
   buttons into it and joins them with nothing, and the cell does not wrap, so
   they sit side by side on one line - which is what the column was collapsed to
   ONE for: spread across several columns the last of them was only reachable by
   scrolling the table sideways. */
.stl-actions { white-space: nowrap; text-align: right; vertical-align: middle; }
/* The square itself is .btn--icon in styles.css, where every surface can reach
   it; all this cell owns is a 2px left inset on each of those squares, which is
   placement and not a box. The first one carries it too, against the cell's own
   padding, which is a pixel-and-a-bit and not worth a :not(:first-child). */
.stl-actions .btn--icon { margin: 0 0 0 2px; }
/* The one cell in this table that does not follow the row rhythm, and the reason
   it is allowed not to: the action column is a fixed 76px and holds two icon
   squares with a 2px inset each, which the dense inset of 12 a side would push
   past the column's width. At 8 a side the two squares fit; at the compact rung
   the dense inset is 8 as well, so this cell and its neighbours agree there and
   only differ when the density is comfortable. Section 5.8 of the design system
   names it as the exception rather than leaving a reader to find it. */
.stl-table td.stl-actions { padding-left: var(--space-2); padding-right: var(--space-2); }
/* The picker under the row whose item is being changed. It takes the standard
   pair rather than the dense one its host row takes, because it is a panel
   inside a cell and not another line of the list; the extra bottom step keeps
   the chosen-item actions off the rule below. */
.stl-pick-row > td { padding: var(--row-pad-y) var(--row-pad-x) var(--row-pad-x); background: var(--surface-selected); border-bottom: 1px solid var(--rule); }
.stl-pick-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-2); font-size: var(--text-xs); font-weight: var(--weight-semibold); color: var(--ink); }
.stl-foot {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
}



/* The filed receipt's line table (receipts/lines.js) is this table too, with
   one column fewer and one of its own. It used to be a grid of divs whose header
   row was only drawn, so a screen reader read the lines with no column name on
   any cell (backlog 5.6). The widths are the ones that grid had - the name takes
   what is left, the state column holds a chip and the remove button side by side
   - and they reach the shared colgroup rules through the same --stl-* properties
   the transfer table sets. The wrapper names itself a pane, because the filed
   receipt is not inside #review-detail and would otherwise never get the narrow
   layout below. */
.ln-lines { container-type: inline-size; container-name: pane; }
.ln-table { --stl-c-qty: 88px; --stl-c-unit: 132px; --stl-c-price: 116px; --ln-c-status: 216px; }
.ln-table col.c-status { width: var(--ln-c-status); }
/* A line with matched-item badges under its name is several rows tall; its
   figures stand at the top beside the name, as they did in the grid. */
.ln-table td { vertical-align: top; }

/* ── 6. Receipt details ─────────────────────────────────────────────────── */
.tri-doc {
  border: 1px solid var(--rule); border-radius: var(--radius-md);
  background: var(--surface-raised);
}
.tri-doc > summary { padding: var(--space-3) var(--space-4); cursor: pointer; font-weight: var(--weight-medium); }
.tri-doc-body { padding: var(--space-4); border-top: 1px solid var(--rule); }

/* ── 7. A line added by hand ────────────────────────────────────────────── */
.man {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--rule-strong); border-radius: var(--radius-md);
  background: var(--surface-sunken);
}
.man-lead { margin: 0; font-size: var(--text-xs); color: var(--ink-muted); max-width: var(--prose-max); }
.man-form { display: flex; flex-direction: column; gap: var(--space-3); }
/* Top-aligned for the same reason as the picker's row: the help under the
   price, or a refusal under the quantity, grows its own field downward and
   leaves the other controls where they were. */
.man-row { display: flex; gap: var(--space-4); flex-wrap: wrap; align-items: flex-start; }
.man-unit { margin: 0; display: flex; flex-direction: column; gap: var(--space-1); }
/* The unit is a fact, not a field, but it stands in the row of fields: its
   caption takes the line box of a field label and its value the height of a
   control, so under top alignment the value reads level with the boxes beside
   it instead of floating at the top of their band. */
.man-unit .man-k { line-height: calc(var(--text-xs) * var(--leading)); }
.man-unit .man-v { display: flex; align-items: center; min-height: var(--control-h); }
.man-k {
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-muted);
}
.man-v { font-size: var(--text-base); color: var(--ink-strong); }
.man-total { margin: 0; display: flex; align-items: baseline; gap: var(--space-2); }
.man-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* ── 8. The shortcuts panel ─────────────────────────────────────────────── */
/* Not a modal: it has to stay readable while the operator keeps working.
   It stands above the receipt's footer (its height is --rv-foot-h, measured
   by receipts/review.js on the work column this panel lives in), so the
   receive press under it stays visible and pressable: at 1280 the panel lay
   over the footer's buttons. It is never taller than the window leaves
   between the top bar and the footer and scrolls inside itself, since at
   488px it ran off a phone held sideways; and never wider than the window
   leaves, for a 320 phone. It opens as a popover (receipts/review.js
   setShortcutsPanel), in the top layer, since the pane around it is a size
   container and would otherwise be what "fixed" is fixed to; inset and margin
   undo the popover's centring. */
.kb-help {
  position: fixed; inset: auto; margin: 0; right: var(--space-5); bottom: calc(var(--rv-foot-h, 0px) + var(--space-3)); z-index: var(--layer-drawer);
  color: var(--ink);
  display: flex; flex-direction: column; gap: var(--space-2);
  max-width: min(320px, calc(100vw - var(--space-5) * 2));
  max-height: calc(100dvh - var(--rv-foot-h, 0px) - var(--sticky-top) - var(--space-3) * 2);
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--space-4);
  background: var(--surface-raised);
  border: 1px solid var(--rule-strong); border-radius: var(--radius-md);
  box-shadow: var(--elev-3);
}
.kb-help-head { margin: 0; font-weight: var(--weight-semibold); color: var(--ink-strong); }
.kb-help-row { display: flex; align-items: baseline; gap: var(--space-3); font-size: var(--text-xs); }
.kb-keys { display: flex; gap: var(--space-1); flex: none; }
.kb-key {
  padding: 0 var(--space-2);
  border: 1px solid var(--rule-strong); border-radius: var(--radius-xs);
  background: var(--surface-sunken); color: var(--ink);
  font-family: var(--font-mono); font-size: var(--text-2xs);
}
.kb-key--icon { display: inline-flex; align-items: center; padding: 0 var(--space-1); }
.kb-icon-up { transform: rotate(180deg); }
.kb-help-foot { margin: 0; font-size: var(--text-2xs); color: var(--ink-muted); }

/* ── 9. Narrow layouts ──────────────────────────────────────────────────── */
/* Below the icon-rail width the paper and the receipt still stand side by
   side, the receipt taking the larger share, since its table holds the work. */
@media (max-width: 1100px) {
  .review-split.rvq-shell { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); gap: var(--space-4); }
  .tri-figs { justify-self: start; }
}
/* Below bp-mid the panes stack: the strip, the paper as a short pane with its
   own bar, then the receipt. Its header and footer go back into the flow: in a
   stacked layout there is no room to keep both stuck over the lines. */
@media (max-width: 900px) {
  .review-split.rvq-shell { grid-template-columns: minmax(0, 1fr); grid-template-areas: "list" "paper" "work"; }
  .rvq-shell .pdf-preview { position: static; height: var(--rv-paper-h-narrow); }
  /* The stuck state is named too: it outranks .recbar alone, and a bar back in
     the flow floats over nothing; a bar in the flow has nothing to clear. */
  .recbar, .recbar.is-stuck, .rv-foot { position: static; box-shadow: none; }
  .recbar ~ * { --recbar-clear: 0px; }
  .rv-foot { margin-bottom: 0; }
}

/* ── The item table on a narrow pane ──────────────────────────────────────
   Below 760px the columns no longer fit beside a product name, so each row
   becomes a block: the receipt line on top, the four numbers under it with
   their own labels, the actions at the end. Nothing is hidden and nothing
   scrolls sideways. The fixed columns take 508px (40 + 72 + 112 + 104 + 104 +
   76), so at 760 the name keeps about 230px inside its cell's inset, one line
   for an ordinary product name. At 640, the width the other panes collapse at,
   a 1366px window left this pane at 664px and the name about 130px, and each
   row ran to four or five lines: the ordinary shop laptop got the worst layout
   of any width. A 1280 and a 1366 window now both stack; the columns come back
   once the pane passes 760, from a window of about 1460px, or below 1100px,
   where the rail moves above the pane and gives it the width. The filed receipt's own table, in its
   .ln-lines pane, has wider figure columns still and stacks at the same width. */
/* The detail pane names itself a container: the item table and the cards
   size to the pane they are in, not to the window, so a pane squeezed by the
   rail at 1000px wide gets the same compact layout as a phone. */
#review-detail { container-type: inline-size; container-name: pane; }
@container pane (max-width: 760px) {
  .stl-table, .stl-table tbody, .stl-table tr, .stl-table td { display: block; }
  .stl-table thead, .stl-table colgroup { display: none; }
  /* height: auto - the base table cell has a fixed row height, which as a flex
     item would clip the badges under the name into the next row. */
  .stl .stl-table tbody tr td { border: 0; padding: 0; background: transparent; height: auto; }
  /* tr.stl-row outranks the block display set on every tr above. The gap and
     padding are read through --stl-row-gap and --stl-row-pad, which the transfer
     table sets on .tf-table (styles/transfers.css): its own collapse at 620px
     states the same values at this specificity, and reading them here is what
     keeps the two rules from being settled by the order of the sheets. */
  .stl-table tr.stl-row { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--stl-row-gap, var(--space-1) var(--space-3));
             padding: var(--stl-row-pad, var(--space-3)); border-bottom: 1px solid var(--rule); }
  .stl-row:nth-child(even) { background: var(--surface-nav); }
  /* Stacked, the row carries its own background and the cells stay transparent:
     the cells are flex items here, so a fill on each of them would draw bands
     with the row's padding and its gaps showing through between them. The
     exclusion and the hover therefore move up to the row, and both carry the
     table class so they outrank the cell reset above and the stripe. */
  .stl-table tr.stl-row:hover, .stl-table tr.stl-row:focus-within { background: var(--surface-hover); }
  .stl-table tr.stl-row--out:not(:hover):not(:focus-within) { background: var(--surface-sunken); }
  .stl-table tr.stl-row--out:not(:hover):not(:focus-within) > td { background: transparent; }
  /* First line: number, name (takes the rest), actions. The figures wrap onto
     the lines below, left to right, each with its label. Selectors carry the
     table class so they outrank the block display set on every cell above. */
  .stl-table td.stl-no { order: 0; width: auto; flex: 0 0 auto; text-align: left; padding-top: 2px; }
  /* The name owns its line: all but the number and the actions, so the figures
     always start on the line below. */
  .stl-table td.stl-main { order: 1; flex: 0 0 calc(100% - 150px); min-width: 0; }
  .stl-table td.stl-actions { order: 2; flex: var(--stl-actions-flex, 0 0 auto); margin-left: auto; }
  .stl-table td.stl-qty, .stl-table td.stl-unit, .stl-table td.stl-price, .stl-table td.stl-amount {
    order: 3; flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--space-1);
    width: auto; margin-right: var(--space-3); font-size: var(--text-sm); text-align: left;
  }
  .stl-qty::before, .stl-unit::before, .stl-price::before, .stl-amount::before {
    content: attr(data-label); font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--ink-muted);
  }
  .stl-row :where(.qty-input, .price-input, .unit-inline):not([aria-invalid="true"]) { border-color: var(--rule-control); background-color: var(--surface-raised); }
  .stl-pick-row { display: block; }
  .stl .stl-table .stl-pick-row > td { padding: var(--row-pad-y); border-bottom: 1px solid var(--rule); background: var(--surface-selected); }
  /* The filed receipt's table stacks the same way. Its cells are reset through
     its own wrapper, at the depth of the settled table's .stl reset above, and
     with no action column the name and the state each take a whole line: the
     name first, the four figures under it with their labels, the state last. */
  .ln-lines .ln-table tbody tr td { border: 0; padding: 0; background: transparent; height: auto; }
  .ln-table td.stl-main, .ln-table td.ln-status { flex: 1 0 100%; }
  .ln-table td.ln-status { order: 4; }
  .ln-table .cell-status { justify-content: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  .rv-row { transition: none; }
}

/* ── Containment and motion (backlog 10.9) ──────────────────────────────────
   Kept at the end of the sheet for the same reason as the rule above: these
   declarations join rules DESIGN_SYSTEM.md cites by line number, and written in
   place they moved every number below them. Each adds to its base rule rather
   than restating it, so the cascade reads the same as if it stood there. */

/* The one motion left in the review is a row's paint settling as its question
   closes, on --dur-leave and --ease-standard, the timing every other closing
   uses. Nothing in the list moves: rows have fixed heights (section 11). */

/* Its figures repaint on every keystroke in the settled table; contained, that
   repaint stays inside the bar. The bleed and the stuck shadow are the bar's own
   box, which paint containment does not clip. */
.recbar { contain: content; }
/* Every write into an answer area is one innerHTML, and without a boundary its new
   contents invalidate layout and paint for the whole document. contain keeps each
   card's work inside it; content-visibility lets the browser skip a card below the
   fold until it scrolls near, holding its place at the height it last had (auto),
   or at a compact card's height before it has ever been drawn. Only the block size
   is held: the card's width is the queue's, and a placeholder width would reach
   any min-content sizing of the queue while the card is skipped. Nothing in a card
   is positioned outside it: the picker opens in the flow, so the clip that paint
   containment brings takes nothing away. */
.tri-card {
  contain: content;
  content-visibility: auto;
  contain-intrinsic-block-size: auto calc(var(--row-h) * 2.5);
}
/* Each search keystroke rewrites the list; it already scrolls inside itself, so
   containing it costs nothing and keeps the rewrite out of the card. */
.pick-list { contain: content; }
/* A receipt of two hundred lines is one innerHTML, and the boundary keeps its
   layout from invalidating the rest of the pane. */
.stl-table { contain: content; }
/* A row far below the fold is skipped until it scrolls near, holding the height it
   last had or one row's floor before it has been drawn, and only the height, so
   the stacked row keeps the pane's width. A browser applies this to a row only
   where the row is a box of its own - the stacked layout of the narrow pane above,
   where a long receipt is longest - and leaves a table row in table layout as it
   was, which is also correct. */
.stl-row { content-visibility: auto; contain-intrinsic-block-size: auto var(--row-h); }

/* ── redesign 2026-10, review lane (appended so the line numbers DESIGN_SYSTEM.md
   cites above stay where they are) ─────────────────────────────────────────── */
/* The three answers that take a line out of the order (a deposit, a fee, a line
   printed twice) sit as one labelled run in the card's foot: nothing opens or
   folds when the operator reaches them, so the card keeps its height. */
.tri-notprod { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.tri-notprod-k { font-size: var(--text-xs); color: var(--ink-muted); }
/* On the card itself (a 0 $ or fee-like line, review.js notProductUpFront):
   under the item answers, apart from them. */
.tri-issue > .tri-notprod { margin-top: var(--space-3); }
/* The lines one unit answer reaches (redesign C3): one row each, a box and the
   line's words, with what it would file beside them. Each row holds one line
   of text whatever the result says, so patching the result as the unit list
   changes never moves the rows below. */
.tri-unit-also { flex: 1 0 100%; min-width: 0; }
.tri-unit-also-head { margin: 0; }
.tri-unit-also-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.tri-unit-also-row label { display: flex; align-items: baseline; gap: var(--space-2); min-width: 0; }
.tri-unit-also-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tri-unit-also-result { color: var(--ink-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* The correction form's cells look editable at rest (redesign B6): the
   settled table's quiet fields reveal their chrome only on hover or focus,
   which in a form read as text nobody could change. Refused fields keep the
   shared refusal paint, exactly as in the settled table. */
.stl-row--edit :where(.qty-input, .price-input):not([aria-invalid="true"]) { border-color: var(--rule-control); background-color: var(--surface-raised); }
.stl-row--edit :where(.qty-input, .price-input):focus:not([aria-invalid="true"]) { border-color: var(--focus-ring); }
/* A question card, a line and a line opened for editing are brought into view
   inside the band these margins leave: clear of the header stuck above and the
   footer stuck below (review.js measures both into --rv-head-h, --rv-foot-h;
   centerCard reads the margins as the band). */
.tri-card, .rv-table tr.rv-row, .rv-table tr.rv-edit-row {
  scroll-margin-top: calc(var(--sticky-top) + var(--rv-head-h, var(--recbar-h)) + var(--space-2));
  scroll-margin-bottom: calc(var(--rv-foot-h, var(--recbar-h)) + var(--space-2));
}

/* ── 11. The review workspace (REDESIGN 4.3, 4.3.2) ──────────────────────── */
/* The rail's wrapper steps aside so its two children take their own places in
   the workspace grid: the receipt list under the strip, the paper on the left. */
.review-left.rvq-rail { display: contents; }
.rvq-shell { --rv-paper-h-narrow: 60vh; }
.rvq-shell .review-list { grid-area: list; justify-self: start; width: min(100%, calc(var(--tri-rail-w) * 2)); max-height: min(46vh, 420px); }
/* The paper: the full height of the window under the header, beside the
   receipt, with its own bar. Readable and zoomable; nothing lies over it. */
.rvq-shell .pdf-preview {
  grid-area: paper; position: sticky; top: var(--review-rail-top);
  display: flex; flex-direction: column;
  height: calc(100dvh - var(--review-rail-top) - var(--space-4));
  aspect-ratio: auto; max-height: none; cursor: auto;
}
.pdf-bar { display: flex; align-items: center; gap: var(--space-1); flex-wrap: wrap; padding: var(--space-1) var(--space-2); border-bottom: 1px solid var(--rule); background: var(--surface-raised); }
.pdf-bar-line { margin-right: auto; min-width: 0; font-size: var(--text-xs); color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pdf-stage { position: relative; flex: 1 1 auto; min-height: 0; --pdf-mark-at: 0px; }
.pdf-stage .pdf-frame { position: absolute; inset: 0; }
/* Where the line in front is printed, once the viewer has scrolled it to the
   top of the pane: an outline only, so the paper's own words show through. */
/* The mark stands where the line is printed on the page the viewer opened at
   its top (pdf.js markOffset writes --pdf-mark-at). */
.pdf-mark { position: absolute; left: 0; right: 0; top: var(--pdf-mark-at); height: var(--space-6); pointer-events: none; outline: 2px solid var(--accent); outline-offset: -2px; }
.pdf-mark[hidden] { display: none; }
/* The paper drawn by pdf.js (receipts/pdfCanvas.js): the pane scrolls, each
   page is a white sheet sized by the custom properties the viewer sets, and the
   band lives INSIDE the line's page, so it moves with the paper. */
.pdf-stage--canvas { overflow: hidden; }
/* The gutter is reserved whether the paper overflows or not: a scrollbar that
   came and went changed the pane's width, and at "fit" that re-scaled the page
   into and out of overflowing, endlessly (receipts/pdfCanvas.js). */
.pdf-scroll { position: absolute; inset: 0; overflow: auto; scrollbar-gutter: stable; padding: var(--space-3); background: var(--surface-sunken); overscroll-behavior: contain; }
.pdf-scroll:focus-visible { outline: var(--kit-focus-width) solid var(--focus-ring); outline-offset: calc(-1 * var(--kit-focus-width)); }
.pdf-page {
  position: relative; width: var(--pdf-page-w, 100%); height: var(--pdf-page-h, auto);
  margin: 0 auto var(--space-3); background: var(--paper-sheet); box-shadow: var(--elev-1);
}
.pdf-page:last-child { margin-bottom: 0; }
.pdf-page-canvas { display: block; width: 100%; height: 100%; }
.pdf-page .pdf-mark { top: var(--pdf-mark-at, 0px); }
.pdf-loading { margin: var(--space-4); color: var(--ink-muted); font-size: var(--text-sm); }
.pdf-attach { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); padding: var(--space-4); }
.pdf-attach-line { margin: 0; font-size: var(--text-sm); color: var(--ink); max-width: var(--prose-max); }
.pdf-attach-said { margin: 0; }
.pdf-attach-said:empty { display: none; }

/* The batch strip, in the review bar: "3 sur 6 - Primeurs PJT-31880". */
.rv-strip { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; width: 100%; min-width: 0; }
.rv-strip-pos { min-width: 0; font-weight: var(--weight-semibold); color: var(--ink-strong); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-strip-tallies { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-left: auto; }

/* The header holds three bands: the receipt's identity, its money, and the
   question in front. */
.rv-work { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
/* The header's stuck-shadow mark takes no room: the gap it would open above
   the header is taken back, and the header brings no margin of its own. */
.rv-work > .recbar-sentinel { margin-bottom: calc(var(--space-4) * -1); }
.rv-head { flex-direction: column; align-items: stretch; gap: var(--space-2); margin-bottom: 0; }
/* A search not yet opened takes no line in its answer area. */
.tri-picker:empty { display: none; }
.recbar-diag[hidden], .rv-manual[hidden] { display: none; }

/* The one stable list. Every closed row has the same height whatever it holds
   (a question mark, a check, a report), so answering never moves the rows below
   it; the answer area under the row in front is the only thing that opens. */
.rv-table tr.rv-row > td { height: var(--row-h); white-space: nowrap; transition: background-color var(--dur-leave) var(--ease-standard); }
.rv-table td.stl-main { overflow: hidden; }
.rv-main { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.rv-name { display: flex; align-items: center; gap: var(--space-1); flex: 1 1 auto; min-width: 0; }
.rv-name .stl-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-name .stl-sku { flex: none; font-size: var(--text-xs); color: var(--ink-muted); }
.rv-how { display: inline-flex; flex: none; color: var(--ink-faint); }
.rv-table tr.rv-row[data-state="question"] { cursor: pointer; }
.rv-table tr.rv-row.is-current > td { background: var(--surface-selected); }
.rv-table tr.rv-row.is-current > td:first-child { box-shadow: var(--edge) var(--accent); }
.rv-table tr.rv-row[data-state="waiting"] > td, .rv-table tr.rv-row[data-state="out"] > td { color: var(--ink-muted); }
/* Every figure that can be changed looks like a field at rest, not only under
   the pointer (B6): a row of quiet numbers read as text nobody could change. */
.rv-table .stl-row :where(.qty-input, .price-input, .unit-inline):not([aria-invalid="true"]) { border-color: var(--rule-control); background-color: var(--surface-raised); }
/* The row's mark: what the line needs, in the severity's colours, repeated as a
   shape so it holds on a monochrome screen. One line, ending in an ellipsis. */
.rv-mark {
  display: inline-flex; align-items: center; gap: var(--space-1); flex: 0 1 auto; min-width: 0;
  padding: 0 var(--space-2); border: 1px solid var(--rule); border-radius: var(--radius-pill);
  background: var(--surface-sunken); color: var(--ink); font-size: var(--text-xs); white-space: nowrap; overflow: hidden;
}
.rv-mark > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.rv-mark svg { flex: none; }
.rv-mark--q { min-height: var(--control-h-sm); font: inherit; font-size: var(--text-xs); cursor: pointer; }
.rv-mark--q:hover { border-color: var(--rule-strong); }
.rv-mark--q[data-severity="block"] { color: var(--sev-block); background: var(--sev-block-soft); border-color: var(--state-blocked-rule); }
.rv-mark--q[data-severity="money"] { color: var(--sev-money); background: var(--sev-money-soft); border-color: var(--state-attention-rule); }
.rv-mark--q[data-severity="notice"] { color: var(--sev-notice); background: var(--sev-notice-soft); border-color: var(--accent-rule); }
.rv-mark--done { color: var(--state-done-fg); background: var(--state-done-bg); border-color: var(--state-done-rule); }
.rv-mark--wait { color: var(--state-attention-fg); background: var(--state-attention-bg); border-color: var(--state-attention-rule); }
.rv-mark--out { color: var(--ink-muted); background: transparent; }
/* The answer area: under its row, the full width of the table. */
.rv-table .rv-answer-row > td { padding: 0 var(--row-pad-x-dense) var(--space-3); background: var(--surface-selected); border-bottom: 1px solid var(--rule); white-space: normal; }
.rv-edit { display: flex; flex-direction: column; gap: var(--space-2); padding-top: var(--space-2); }
.rv-more { flex: 1 0 100%; display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1); }
.rv-more .tri-alt { align-self: stretch; }
/* The unusual prices, confirmed together (4.3.2 item 9). */
.rv-prices { flex: 1 0 100%; display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.rv-prices-head { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--ink-strong); }
.rv-prices-table { width: 100%; border-collapse: collapse; font-size: var(--text-xs); }
.rv-prices-table th { padding: var(--row-pad-y-dense) var(--row-pad-x-dense); text-align: left; font-size: var(--text-2xs); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-muted); border-bottom: 1px solid var(--rule); }
.rv-prices-table td { padding: var(--row-pad-y-dense) var(--row-pad-x-dense); border-bottom: 1px solid var(--rule); vertical-align: middle; }
.rv-prices-table td.num, .rv-prices-table th.num { text-align: right; }
.rv-prices-name { max-width: 0; width: 35%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-prices-act { white-space: nowrap; }
.rv-prices-act > * + * { margin-left: var(--space-2); }
.rv-prices-act .field--inline { display: inline-flex; }

/* The footer: what will be filed, then the one press. Stuck to the bottom of
   the window, at the receipt's edges, so the receive button is always in view. */
.rv-foot {
  position: sticky; bottom: 0; z-index: var(--layer-sticky);
  display: flex; flex-direction: column; gap: var(--space-2);
  margin: 0 calc(var(--space-4) * -1) calc(var(--space-4) * -1);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-raised); border-top: 1px solid var(--rule); box-shadow: var(--elev-1);
}
.rv-foot-main { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.rv-foot-sum { margin: 0; font-size: var(--text-sm); color: var(--ink-strong); font-variant-numeric: tabular-nums lining-nums; }
.rv-foot-wait { margin: 0; font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--state-attention-fg); }
.rv-foot-lead:empty { display: none; }
.rv-foot-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); }
.rv-foot-keys { margin-right: auto; }
.kb-help-row.is-off { color: var(--ink-muted); }
@media (max-width: 620px) {
  .rv-foot-actions > .btn--commit { flex: 1 1 100%; }
}

/* ── 12. The receipt's tools (GAP_REGISTER Part 3, receiving) ────────────── */
/* One row of quiet tools under the header, and the one panel they open, in the
   flow: nothing here sticks, so the lines keep the screen. */
.rv-tools { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.rv-tools-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.rv-tools-bar .rv-undo { margin-left: auto; }
.rv-undo[aria-disabled="true"] { color: var(--ink-faint); }
.rv-tools-marks { margin: 0; color: var(--ink-muted); }
.rv-tools-marks:empty { display: none; }
.rv-tool-panel {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2);
  padding: var(--space-3) var(--space-4); border: 1px solid var(--rule); border-radius: var(--radius-md);
  background: var(--surface-raised);
}
.rv-tool-panel[hidden] { display: none; }
.rv-tool-line { margin: 0; max-width: var(--prose-max); font-size: var(--text-sm); color: var(--ink); }
.rv-tool-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.rv-tool-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.rv-note { width: min(100%, var(--prose-max)); }
.rv-preview { width: 100%; font-size: var(--text-sm); }
.rv-preview td.num, .rv-preview th.num { text-align: right; }

/* The bar over the lines: always one control's height, whether a line is
   ticked or not, so ticking the first line never moves the table. */
.rv-bulk {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  min-height: var(--control-h); padding: 0 var(--row-pad-x-dense);
}
.rv-bulk-hint { margin: 0; }
.rv-bulk-count { font-weight: var(--weight-semibold); color: var(--ink-strong); }
.rv-bulk-group { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
/* The box at the head of a row sits beside its number, in the same cell. */
.rv-sel { margin: 0 var(--space-1) 0 0; vertical-align: middle; }
.rv-short { flex: none; }

/* A line's own tools, in its edit area under the picker. */
.rv-line-tools { display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--tri-rail-w), 1fr)); gap: var(--space-2) var(--space-4); padding-top: var(--space-2); border-top: 1px solid var(--rule); }
.rv-line-tools-k { grid-column: 1 / -1; margin: 0; color: var(--ink-muted); }
.rv-line-tool { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); }
.rv-line-tool .field-label { flex: 1 0 100%; margin: 0; }
.rv-line-tool .input { flex: 1 1 auto; min-width: 0; }
.rv-line-tool--row { grid-column: 1 / -1; }
.rv-line-said { margin: 0; }
/* "Seulement cette facture", beside the picker's reload. */
.pick-once { display: inline-flex; align-items: center; gap: var(--space-1); }

/* The supplier in the header is a button that opens "Changer de fournisseur"
   under it (REDESIGN 4.3: the supplier editable in place). */
.rv-supplier-edit { display: inline-flex; align-items: center; gap: var(--space-2); }
.rv-supplier-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── The invoice reader (GAP_REGISTER Part 4, T2 and T3) ─────────────────── */
/* An unproven reader, said in the header. The slot is always in the header
   and takes no room when the reader is proven, so the notice is never what
   moves the header around it. */
.rv-reader-slot:empty { display: none; }
.rv-reader {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--state-attention-rule); border-radius: var(--radius-sm);
  background: var(--state-attention-bg); color: var(--state-attention-fg);
}
.rv-reader-text {
  display: flex; align-items: flex-start; gap: var(--space-2);
  flex: 1 1 var(--prose-max); min-width: 0; margin: 0;
  font-size: var(--text-sm);
}
.rv-reader-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* "Réparer le lecteur", the last section of "Trouver le problème". */
.rr {
  display: flex; flex-direction: column; gap: var(--space-2);
  margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 1px solid var(--rule);
}
.rr[hidden] { display: none; }
.rr-body { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.rr-why, .rr-sub { margin: 0; max-width: var(--prose-max); font-size: var(--text-sm); color: var(--ink); }
.rr-sub { color: var(--ink-muted); }
/* What a repaired reader would lose against the active one, one line per
   reading (receipts/readerRepairPanel.js): said in the attention tone, as a
   list the operator can read through, never folded into one sentence. */
.rr-lost { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-2) var(--space-3);
  border: 1px solid var(--state-attention-rule); border-radius: var(--radius-sm); background: var(--state-attention-bg); }
.rr-losses { margin: 0; padding-left: var(--space-5); font-size: var(--text-sm); color: var(--ink); max-width: var(--prose-max); }
.rr-losses li + li { margin-top: var(--space-1); }
/* The actions row keeps one control's height in every state, the busy button
   included, so pressing "Réparer" swaps a label and moves nothing below. */
.rr-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); min-height: var(--control-h-sm); }
.rr-elapsed { font-variant-numeric: tabular-nums lining-nums; font-size: var(--text-sm); color: var(--ink-muted); white-space: nowrap; }
.rr-verdict {
  display: flex; align-items: flex-start; gap: var(--space-2);
  margin: 0; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
}
.rr-verdict[data-tone="done"] { border-color: var(--state-done-rule); background: var(--state-done-bg); color: var(--state-done-fg); }
.rr-verdict[data-tone="attention"] { border-color: var(--state-attention-rule); background: var(--state-attention-bg); color: var(--state-attention-fg); }
.rr-verdict[data-tone="blocked"] { border-color: var(--state-blocked-rule); background: var(--state-blocked-bg); color: var(--state-blocked-fg); }
/* The lines before and after: fixed columns, so a long product name wraps in
   its cell instead of widening the panel (DESIGN_SYSTEM 5.8). */
.rr-table { table-layout: fixed; width: 100%; font-size: var(--text-sm); }
.rr-col-n { width: var(--space-12); }
.rr-table td { overflow-wrap: anywhere; }
.rr-table tr[data-changed="1"] > td:last-child { font-weight: var(--weight-semibold); color: var(--ink-strong); }
.rr-checks { display: flex; flex-direction: column; gap: var(--space-1); margin: 0; padding: 0; list-style: none; }
.rr-check { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); font-size: var(--text-sm); }
.rr-check[data-ok="1"] { color: var(--state-done-fg); }
.rr-check[data-ok="0"] { color: var(--state-blocked-fg); }
.rr-evidence {
  flex: 1 0 100%; min-width: 0; overflow-wrap: anywhere;
  padding: var(--space-1) var(--space-2); border-radius: var(--radius-xs);
  background: var(--surface-raised); color: var(--ink);
  font-family: var(--font-mono); font-size: var(--text-2xs);
}

/* The product's price history, opened under its button in the price question
   and in the line's edit area. */
.rv-ph { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); margin-top: var(--space-2); }
.rv-ph-panel { width: 100%; padding: var(--space-2) var(--space-3); border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--surface-sunken); }
.rv-ph-panel[hidden] { display: none; }
.rv-ph-list { display: flex; flex-direction: column; gap: var(--space-1); margin: 0; padding: 0; list-style: none; }
.rv-ph-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3); font-size: var(--text-sm); }
.rv-ph-row[data-mine="1"] { font-weight: var(--weight-semibold); color: var(--ink-strong); }
.rv-ph-unit { min-width: var(--space-12); }
.rv-ph-mine { font-weight: var(--weight-regular); color: var(--ink-muted); }
.rv-ph-prices { overflow-wrap: anywhere; }
.rv-ph-note { margin: var(--space-2) 0 0; font-size: var(--text-xs); color: var(--ink-muted); }

/* ── 13. The approved receipt screen (docs/design/mockups/receipt-upgrade-v1.png)
   Appended, so the lines DESIGN_SYSTEM.md cites above stay where they are.
   Calm cool-grey ground, white working panels, one blue for the primary press
   and the selection, amber only for what needs a decision, red only for a real
   error. Every rule here adds to or restates a rule above at the same depth,
   so the cascade reads in order. ─────────────────────────────────────────── */

/* The page head: the title, the breadcrumb under it, the save state at the
   right. The two-step rail stays in the markup for the import sheet; on the
   review the page is one screen and its breadcrumb says where it sits. */
.wiz-title { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
#view-new.is-review .wiz-title h1 { font-size: var(--text-xl); line-height: var(--leading-tight); }
#view-new.is-review #wiz-steps { display: none; }
#view-new.is-review > .wiz-head { align-items: center; margin-bottom: var(--space-3); }
.wiz-crumbs { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--ink-muted); }
.wiz-crumbs[hidden] { display: none; }
.wiz-crumb {
  appearance: none; padding: 0; border: 0; border-radius: var(--radius-xs); background: none;
  font: inherit; font-size: var(--text-sm); color: var(--ink-muted); cursor: pointer;
}
.wiz-crumb:hover { color: var(--ink-strong); text-decoration: underline; }
.wiz-crumb-sep { color: var(--ink-faint); }
.wiz-crumb-here { color: var(--ink-strong); font-weight: var(--weight-medium); }
.wiz-save { margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-1); min-width: 0; font-size: var(--text-sm); }
.wiz-save:empty { display: none; }
.wiz-head > .wiz-save:not(:empty) + .btn { margin-left: 0; }
.wiz-save .save-state.saved { color: var(--ink-muted); }

/* The review uses the whole window: the paper and the receipt side by side
   need the width more than any other screen. */
.content:has(#view-new.is-review:not(.hidden)) { max-width: none; }

/* The batch strip: "3 factures", one pill per state, the batch's press. */
#wb-review .review-bar { margin-bottom: var(--space-4); padding: 0; background: transparent; border: 0; border-radius: 0; }
.rv-strip { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; width: auto; min-width: 0; }
.rv-strip-count { margin: 0; font-size: var(--text-section); font-weight: var(--weight-bold); letter-spacing: var(--kit-header-tracking); color: var(--ink-strong); white-space: nowrap; }
.rv-strip-tallies { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-left: 0; }
#wb-review .review-bar-actions { margin-left: auto; }
/* The state pills are the kit's .state capsule (core/ui.js statusPill). */

/* The workspace grid: the cards over both columns, then the paper, the
   divider and the receipt. The paper's share is the divider's, kept per
   browser (--rv-paper-w, set by receipts/review.js). */
.review-split.rvq-shell {
  grid-template-columns: minmax(0, var(--rv-paper-w, 44%)) var(--space-4) minmax(0, 1fr);
  grid-template-areas: "list list list" "paper divider work";
  column-gap: 0; row-gap: var(--space-4);
}
.rvq-shell .pdf-missing { display: none; }
/* The batch's cards: one per invoice, scrolling sideways when the batch is
   long, the open one outlined in the accent. */
.rvq-shell .review-list {
  grid-area: list; justify-self: stretch; width: auto;
  display: flex; gap: var(--space-3); align-items: stretch;
  max-height: none; min-height: 0; overflow: auto hidden;
  padding: var(--space-1) var(--space-1) var(--space-2);
  background: transparent; border: 0; border-radius: 0;
  scrollbar-gutter: auto;
}
.rvq-shell .review-list[hidden] { display: none; }
/* Each card stands in a slot (receipts/review.js paintReviewList): the slot
   takes the card's place in the strip, and the card fills it. A receipt found
   already received carries a second control in the slot, the trash that
   removes it without opening it, laid over the card's right edge and centred
   on it; the card keeps that edge clear, so a long supplier name ends before
   the trash rather than under it. The trash is the kit's small icon button:
   --control-h-sm square, which is the tap floor. */
.rv-bcard-slot { position: relative; display: flex; flex: 0 1 400px; min-width: 260px; }
.rv-bcard-slot.has-remove > .rv-bcard { padding-right: var(--space-12); }
.rv-bcard-remove { position: absolute; top: 50%; right: var(--space-2); transform: translateY(-50%); }
.rv-bcard {
  appearance: none; flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; align-items: stretch; gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-raised); color: inherit;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  font: inherit; font-size: var(--text-sm); text-align: left; cursor: pointer;
  transition: border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}
.rv-bcard:hover { border-color: var(--rule-strong); }
.rv-bcard.sel { background: var(--surface-selected); border-color: var(--accent-rule); box-shadow: var(--edge) var(--accent); }
.rv-bcard-top { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); min-width: 0; }
.rv-bcard-name { min-width: 0; font-size: var(--text-base); font-weight: var(--weight-medium); color: var(--ink-strong); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-bcard-sub { font-size: var(--text-xs); color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The supplier has the card's whole first line and the state pill moves down
   beside the invoice number: on one line the pill ("2 à vérifier", "Prête à
   recevoir") kept its width and the name was left 3 to 64px. The top line's
   wrapper steps aside (display: contents) so its two children take places in
   the card's own grid. */
.rv-bcard {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "name name" "sub pill"; align-items: center; column-gap: var(--space-3);
}
.rv-bcard-top { display: contents; }
.rv-bcard-name { grid-area: name; }
.rv-bcard-top > :not(.rv-bcard-name) { grid-area: pill; justify-self: end; }
.rv-bcard-sub { grid-area: sub; min-width: 0; }
/* A batch wider than the strip says so at the edge it continues past: the
   strip fades out over its last 32px on the side there is more to scroll to,
   and stops fading once the end is reached. The fade follows the strip's own
   scroll (a scroll-driven animation of the two widths), so a strip that does
   not scroll has no timeline and no fade. A browser without scroll timelines
   keeps the plain strip. */
@property --rv-fade-start { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --rv-fade-end { syntax: "<length>"; inherits: false; initial-value: 0px; }
@keyframes rv-strip-fade {
  0% { --rv-fade-start: 0px; --rv-fade-end: var(--space-8); }
  10% { --rv-fade-start: var(--space-8); }
  90% { --rv-fade-end: var(--space-8); }
  100% { --rv-fade-start: var(--space-8); --rv-fade-end: 0px; }
}
@supports (animation-timeline: scroll()) {
  .rvq-shell .review-list {
    animation: rv-strip-fade linear both; animation-timeline: scroll(self inline);
    mask-image: linear-gradient(to right, transparent, currentColor var(--rv-fade-start), currentColor calc(100% - var(--rv-fade-end)), transparent);
  }
}

/* The divider: a quiet rule in the gutter, a handle that darkens under the
   pointer and the keyboard, sticky beside the paper. */
.rv-divider {
  grid-area: divider; position: sticky; top: var(--review-rail-top); align-self: start;
  height: calc(100dvh - var(--review-rail-top) - var(--space-4));
  display: flex; align-items: center; justify-content: center;
  cursor: col-resize; touch-action: none; border-radius: var(--radius-sm);
}
.rv-divider::before { content: ""; width: 1px; height: 100%; background: var(--rule); }
.rv-divider::after {
  content: ""; position: absolute; top: 50%; left: 50%; width: 4px; height: var(--space-10);
  margin: calc(var(--space-5) * -1) 0 0 -2px; border-radius: var(--radius-pill); background: var(--rule-strong);
  transition: background var(--dur-1) var(--ease-out);
}
.rv-divider:hover::after, .rv-divider:focus-visible::after, .is-resizing .rv-divider::after { background: var(--accent); }
/* While dragging, the paper's viewer must not swallow the pointer. */
.review-split.is-resizing { cursor: col-resize; user-select: none; }
.review-split.is-resizing .pdf-frame { pointer-events: none; }

/* The paper: "Facture originale", the page counter, the zoom with its step,
   the full screen, then the page on the sunken ground. */
.rvq-shell .pdf-preview { background: var(--surface-raised); border-color: var(--rule); }
.rvq-shell .pdf-preview:fullscreen { height: 100dvh; border-radius: 0; }
.rvq-shell .pdf-bar { gap: var(--space-2); flex-wrap: nowrap; padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4); border-bottom: 0; }
/* The paper pane is a container of its own (rvpaper), so its bar reads the
   pane's width and not the window's. Under 520px of pane (a phone, and the
   paper beside the receipt on a 1024 to 1440 window) one line held the title,
   the line in front, the pages, the zoom and the full screen: the line's
   caption measured 0px, the full-screen button sat off the pane at 390 and
   9px under its edge at 1024. There the bar takes two lines: the title and
   the line in front, then the page, the zoom and the full screen, the last at
   the right. The break is the bar's own ::before, a zero-height item the whole
   line wide, ordered between the two. */
.rvq-shell .pdf-preview, .fi-paper { container-type: inline-size; container-name: rvpaper; }
@container rvpaper (max-width: 520px) {
  .pdf-bar, .rvq-shell .pdf-bar { flex-wrap: wrap; row-gap: var(--space-1); }
  .pdf-bar::before { content: ""; order: 2; flex: 0 0 100%; height: 0; }
  .pdf-bar > .pdf-bar-title, .pdf-bar > .pdf-bar-line { order: 1; }
  .pdf-bar > .pdf-bar-line, .rvq-shell .pdf-bar > .pdf-bar-line { flex: 1 1 0; margin-right: 0; }
  .pdf-bar > .pdf-pages, .pdf-bar > .pdf-zoom, .pdf-bar > .pdf-open { order: 3; }
  .pdf-bar > .pdf-zoom { margin-left: var(--space-1); }
  .pdf-bar > .pdf-open { margin-left: auto; }
}
.pdf-bar-title { margin: 0; font-size: var(--text-md); font-weight: var(--weight-semibold); color: var(--ink-strong); white-space: nowrap; }
.rvq-shell .pdf-bar-line { margin-right: auto; }
.pdf-pages { display: inline-flex; align-items: center; gap: var(--space-1); }
.pdf-pages-v {
  display: inline-flex; align-items: center; justify-content: center; min-width: var(--space-12); min-height: var(--control-h-sm);
  padding: 0 var(--space-2); border: 1px solid var(--rule-strong); border-radius: var(--radius-sm);
  font-size: var(--text-sm); color: var(--ink-strong); white-space: nowrap;
}
.pdf-pages .btn[hidden] { display: none; }
.pdf-pages [data-pdf-page="prev"] svg { transform: rotate(180deg); }
.pdf-zoom { display: inline-flex; align-items: center; gap: var(--space-1); margin-left: var(--space-2); }
.pdf-zoom-v { min-width: var(--space-12); color: var(--ink-strong); }
.rvq-shell .pdf-stage { margin: 0 var(--space-3) var(--space-3); border-radius: var(--radius-sm); background: var(--surface-sunken); overflow: hidden; }
/* The line in front, once the viewer has turned to it: a tinted band where it
   is printed, outlined in the decision's amber, the paper's words showing
   through. */
.rvq-shell .pdf-mark {
  height: var(--space-6); outline: 0;
  background: color-mix(in srgb, var(--state-attention-edge) 22%, transparent);
  box-shadow: inset 0 0 0 1px var(--state-attention-edge);
}
/* No PDF in this browser: a compact slot over the receipt, which takes the
   width, never a tall empty panel. */
.review-split.rvq-shell.rv-nopdf { grid-template-columns: minmax(0, 1fr); grid-template-areas: "list" "paper" "work"; }
.rv-nopdf .rv-divider { display: none; }
.rvq-shell.rv-nopdf .pdf-preview { position: static; height: auto; max-height: none; }
.rvq-shell .pdf-preview.hidden { display: none; }
.pdf-attach--slot { flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); padding: var(--space-2) var(--space-4); }
.pdf-attach--slot .pdf-attach-line { flex: 1 1 auto; max-width: none; font-size: var(--text-sm); color: var(--ink-muted); }
.pdf-attach-icon { display: inline-flex; color: var(--ink-faint); }
.pdf-attach--slot .pdf-attach-said { flex: 1 0 100%; }

/* The receipt: a white panel; its head carries the supplier, the number and
   the date under it, and the three figure tiles at the right. Nothing in it
   sticks but the footer. */
.rvq-shell .review-detail { padding: 0 var(--space-4) var(--space-4); }
.rv-work { gap: var(--space-4); }
.rv-head, .rv-head.is-stuck { position: static; box-shadow: none; }
.rv-head {
  flex-direction: column; align-items: stretch; gap: var(--space-2);
  margin: 0 calc(var(--space-4) * -1); padding: var(--space-4) var(--space-4) 0; border-bottom: 0; min-height: 0;
}
.rv-head-main { display: flex; align-items: flex-start; gap: var(--space-4); min-width: 0; }
.rv-head-main { flex-wrap: nowrap; }
.rv-head .rv-ident { flex: 1 1 240px; flex-direction: column; flex-wrap: nowrap; align-items: flex-start; gap: var(--space-1); min-width: 0; }
.rv-head .rv-supplier-name { white-space: normal; overflow: visible; }
.rv-head .rv-supplier, .review-detail--single .rv-supplier { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-semibold); line-height: var(--leading-tight); color: var(--ink-strong); white-space: normal; }
.rv-ident-title { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-3); min-width: 0; }
.rv-ident-meta { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin: 0; min-height: var(--control-h-sm); }
.rv-ident-meta .rv-ident-v { white-space: nowrap; }
.rv-ident-meta .rv-ident-v { font-size: var(--text-base); color: var(--ink-muted); }
.rv-ident-pen { color: var(--ink-faint); }
.rv-head-tools { flex: none; }
.rv-head .rv-head-tools[aria-expanded="true"]:not(:disabled) { background: var(--surface-hover); color: var(--ink-strong); }
.rv-ident-meta .facture-input { width: 128px; }
/* Three kit summary tiles: Commande, Facture, Écart (a filed receipt's third
   is its receipt number). Placement only. */
.rv-head .recbar-figs, .review-detail--single .recbar-figs { flex: none; }
.rv-head .recbar-fig, .review-detail--single .recbar-fig { min-width: var(--summary-min); }
/* A receipt pane under 760px (the paper open beside it at 1280 and 1440, or
   a tablet) cannot hold the name, the tools and three 120px tiles on one
   line: the line measured 670px in a 514px pane, the gap tile was cut and
   "Outils" sat outside the pane. There the tiles take a line of their own
   under the name, sharing it equally. */
@container rvwork (max-width: 760px) {
  .rv-head-main { flex-wrap: wrap; }
  .rv-head .recbar-figs { flex: 1 1 100%; min-width: 0; }
  .rv-head .recbar-fig { min-width: 0; }
}
.rv-tools-wrap[hidden] { display: none; }
.rv-tools-wrap { padding: var(--space-3) var(--space-4); border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--surface-nav); }
.rv-work > .recbar-sentinel { margin-bottom: calc(var(--space-4) * -1); }

/* The questions panel: amber border, its head and the line on the tint, the
   answers on white. Its floor is the tallest it has been on this receipt
   (--rv-q-min), so a shorter next question never lifts the table. */
.rv-q { display: flex; flex-direction: column; min-height: var(--rv-q-min, 0); overflow: visible; }
.rv-q[hidden] { display: none; }
/* On a narrow pane the floor is capped at 40% of the window: a picker
   question at 390 is about 760px tall, and the price question after it kept
   that height with some 300px of empty amber, pushing the lines a screen
   further down. Below the cap the panel still never shrinks. */
@container rvwork (max-width: 520px) {
  .rv-q { min-height: min(var(--rv-q-min, 0px), 40vh); }
}
.rv-q-head { justify-content: flex-start; gap: var(--space-2); }
.rv-q-icon { display: inline-flex; color: var(--state-attention-edge); }
.rv-q-icon svg { width: var(--icon); height: var(--icon); }
.rv-q-title { margin: 0; }
.rv-q .rv-q-flag { margin-left: auto; margin-top: 0; }
.rv-q-nav { display: inline-flex; align-items: center; gap: var(--space-1); }
.rv-q-flag + .rv-q-nav { margin-left: var(--space-2); }
.rv-q-pos { font-size: var(--text-xs); color: var(--state-attention-fg); white-space: nowrap; }
.rv-q-card { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
/* The card inside the panel draws no box of its own: the panel is the box. */
.rv-q .tri-card {
  flex: 1 1 auto; gap: 0; padding: 0; border: 0; border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  --tri-edge: transparent; background: transparent; contain: none; content-visibility: visible;
}
/* The question box as a whole is what has the keyboard: its ring goes round
   the entire amber box, title included, not round the card inside it, which
   would draw a second frame cut off under the title. The card's own ring is
   switched off by emptying the payload it draws, and the box draws it. */
.rv-q .tri-card { --focus-shadow: none; }
/* The box's ring is the kit's shared focus rule (styles.css, the :where list
   of tabs and panel buttons), which names .rv-q:has(.tri-card:focus-visible). */
/* The source line's facts stand above the choices, parted by a hairline. */
.rv-q .tri-head {
  align-items: flex-start; justify-content: space-between; flex-wrap: nowrap; gap: var(--space-4);
  margin-top: var(--space-3); padding: 0 0 var(--space-3); background: transparent;
  border-bottom: var(--kit-hairline) solid var(--rule);
}
.rv-q .tri-heading { flex: 1 1 auto; gap: 0; }
.rv-q .tri-title { font-size: var(--text-base); font-weight: var(--weight-semibold); color: var(--ink-strong); }
.rv-q .tri-sku { font-size: var(--text-xs); color: var(--ink-muted); }
.tri-amount { flex: none; margin: 0; font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--ink-strong); white-space: nowrap; }
/* "Pourquoi ?" (the stepped onboarding, plan 6.3): the info button at the end
   of every question card's header. Its slot is painted on every card, guides
   on or off, and never grows or shrinks, so a tip opening beside the card
   moves nothing. Last in the header, after the amount, so the line's figures
   keep their place under the eye. */
.tri-why-slot { flex: none; display: inline-flex; align-self: flex-start; }
.tri-why-btn { color: var(--ink-muted); }
.rv-q .tri-body { gap: var(--space-3); padding: var(--space-3) 0 0; }
.rv-q .tri-issue { gap: var(--space-3); }
.rv-q .tri-why { font-size: var(--text-sm); color: var(--ink); line-height: var(--leading); }
.rv-q .tri-figs { margin-left: 0; justify-content: flex-start; }
.rv-q .tri-fig { text-align: left; }
.rv-q .tri-fix { gap: var(--space-2) var(--space-3); align-items: center; margin-top: 0; }
.rv-q .tri-fix > .btn { min-height: var(--control-h); }
/* Every severity reads as a decision to make: the flag is amber. */
.rv-q .tri-card .tri-flag { color: var(--state-attention-fg); border-color: transparent; }
.rv-q .tri-issue-flag { color: var(--state-attention-fg); }
/* The quiet menu at the end of the answers opens over the answers above it,
   clear of the footer stuck at the bottom of the window. */
.rv-q .rv-more { position: relative; flex: 0 0 auto; margin-left: auto; flex-direction: row; align-items: center; }
.rv-more-btn { color: var(--accent); }
.rv-q .rv-more .tri-alt {
  position: absolute; right: 0; bottom: calc(100% + var(--space-1)); z-index: var(--layer-drawer);
  display: flex; flex-direction: column; align-items: stretch; gap: var(--space-1);
  min-width: 260px; margin: 0; padding: var(--space-2);
  background: var(--surface-raised); border: 1px solid var(--rule-strong); border-radius: var(--radius-sm); box-shadow: var(--elev-2);
}
.rv-q .rv-more .tri-alt[hidden] { display: none; }
.rv-q .tri-alt > .btn, .rv-q .tri-alt .tri-notprod .btn { justify-content: flex-start; text-align: left; }
.rv-q .tri-alt .tri-notprod { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-1); padding-top: var(--space-1); }
.rv-q .tri-alt .tri-notprod-k { padding: 0 var(--space-3); font-size: var(--text-2xs); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-muted); }
.rv-q .tri-alt > .btn + .tri-notprod { margin-top: var(--space-1); border-top: 1px solid var(--rule); padding-top: var(--space-2); }

/* The search in the question (picker.js radio mode): the field with its
   glyph and its clear, then the closest articles as radio rows, at a fixed
   height so typing never moves the buttons under it. */
.tri-picker--radio { flex: 1 0 100%; }
.pick-search { position: relative; display: flex; align-items: center; }
.pick-search-icon { position: absolute; left: var(--space-3); color: var(--ink-faint); pointer-events: none; }
.pick-search .pick-input { padding-left: var(--space-10); padding-right: var(--space-10); }
.pick-clearq { position: absolute; right: var(--space-1); color: var(--ink-faint); }
.pick-clearq[hidden] { display: none; }
/* The unit's track is as wide as the unit and no wider than 160px. It was
   minmax(48px, 160px), a fixed track the grid fills before the name's
   flexible one, so on a phone the unit kept 160px for "un" and the name had
   about 90px; now the name has everything the unit does not use. */
.pick-head--radio, .pick-row--radio { grid-template-columns: var(--space-6) minmax(0, 1fr) auto; gap: var(--space-3); align-items: center; }
.pick-head--radio > :last-child, .pick-row--radio .pick-unit { max-width: 160px; }
.pick-head--radio { padding: var(--space-1) var(--row-pad-x-dense); text-transform: none; letter-spacing: 0; font-size: var(--text-xs); font-weight: var(--weight-regular); }
.pick-list--radio { position: relative; height: calc(var(--row-h) * 3.5); max-height: none; border-color: var(--rule); }
.pick-row--radio { min-height: var(--row-h); padding: var(--space-1) var(--row-pad-x-dense); border-bottom: 1px solid var(--rule); }
.pick-row--radio:last-child { border-bottom: 0; }
.pick-row--radio[aria-selected="true"] { background: var(--surface-selected); box-shadow: inset 0 0 0 1px var(--accent); }
.pick-row--radio[aria-selected="true"] .pick-name { font-weight: var(--weight-semibold); }
.pick-radio { width: 18px; height: 18px; border: 1.5px solid var(--rule-control); border-radius: var(--radius-pill); background: var(--surface-raised); justify-self: center; }
.pick-row--radio[aria-selected="true"] .pick-radio { border-color: var(--accent); box-shadow: inset 0 0 0 4px var(--surface-raised); background: var(--accent); }
.pick-main { display: flex; flex-direction: column; min-width: 0; }
.pick-row--radio .pick-name { font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--ink-strong); }
.pick-row--radio .pick-sku { font-size: var(--text-xs); overflow: hidden; text-overflow: ellipsis; }
.pick-row--radio .pick-unit { font-size: var(--text-sm); color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pick-unit-select { max-width: 100%; }
.pick-scope-row--radio { justify-content: flex-start; gap: var(--space-3); }
.pick-scope-row--radio .pick-reload { margin-left: auto; }
/* On a pane under 520px (a phone, the receipt beside the paper) the unit
   leaves its column for the line under the article's code, and the name
   wraps: the name is what the operator is choosing by, and at a phone's
   width an ellipsis took "Farine boulangère T65 20 kg" down to its first
   word. The radio stands beside both lines. */
@container rvwork (max-width: 520px) {
  .pick-head--radio { grid-template-columns: var(--space-6) minmax(0, 1fr); }
  .pick-head--radio > :last-child { display: none; }
  .pick-row--radio { grid-template-columns: var(--space-6) minmax(0, 1fr); row-gap: 0; }
  .pick-row--radio .pick-radio { grid-row: 1 / span 2; }
  .pick-row--radio .pick-name { white-space: normal; overflow-wrap: anywhere; }
  .pick-row--radio .pick-unit { grid-column: 2; max-width: 100%; font-size: var(--text-xs); color: var(--ink-muted); }
  .pick-row--radio .pick-unit-select { margin-top: var(--space-1); }
}

/* The verified lines: the head with its count and "Ajouter une ligne", then a
   real table. No zebra and no per-row labels: hairlines between rows, the
   figures right-aligned in tabular numbers, the check at the start, the
   pencil at the end. */
.rvq-work .stl.rv-lines { border: 0; border-radius: 0; background: transparent; }
.rv-lines .stl-summary { align-items: center; gap: var(--space-2); padding: 0 0 var(--space-3); cursor: auto; }
.rv-lines-add { margin-left: auto; }
/* The frame clips the rows' ground to its corners. It scrolls rather than
   cuts: a table wider than its pane (the correction's fields on a pane between
   its two layouts) is reached sideways instead of losing its last columns,
   which is how the amount and the remove button went missing at 390. */
.rv-lines .stl-body { border: 1px solid var(--rule); border-radius: var(--radius-sm); overflow-x: auto; }
.rv-lines .stl-table col.c-no { width: 44px; }
.rv-lines .stl-table col.c-qty { width: 64px; }
.rv-lines .stl-table col.c-unit { width: 104px; }
.rv-lines .stl-table col.c-price { width: 96px; }
.rv-lines .stl-table col.c-amount { width: 100px; }
.rv-lines .stl-table col.c-actions { width: 56px; }
.rv-table tr.rv-row > td { height: var(--row-h); white-space: normal; }
.rv-table .stl-row:nth-child(even) > td { background: transparent; }
.rv-table .stl-row:hover > td, .rv-table .stl-row:focus-within > td { background: var(--surface-hover); }
.rv-table td.stl-main { overflow: hidden; }
.rv-table .rv-name { flex-direction: column; align-items: flex-start; gap: 0; }
.rv-table .rv-name .stl-name { max-width: 100%; font-size: var(--text-sm); font-weight: var(--weight-medium); white-space: normal; overflow-wrap: anywhere; }
.rv-table .rv-name .stl-sku { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-xs); }
.rv-table td.stl-qty, .rv-table td.stl-price, .rv-table td.stl-amount, .rv-table td.stl-unit { font-size: var(--text-sm); }
.rv-table td.stl-amount { font-weight: var(--weight-medium); }
.rv-table td.rv-stat-cell { text-align: center; }
.rv-stat { display: inline-flex; vertical-align: middle; }
.rv-stat svg { width: var(--icon); height: var(--icon); }
.rv-stat--ok { color: var(--state-done-fg); }
.rv-stat--wait { color: var(--state-attention-fg); }
.rv-stat--out { color: var(--ink-faint); }
.rv-table tr.rv-row[data-state="waiting"] .stl-name, .rv-table tr.rv-row[data-state="out"] .stl-name { color: var(--ink-muted); }
.rv-table tr.stl-row--edit > td { background: var(--surface-selected); }
.rv-table .stl-row--edit :where(.qty-input, .price-input):not([aria-invalid="true"]) { border-color: var(--rule-control); background-color: var(--surface-raised); }
.rv-table .stl-row--edit .qty-input, .rv-table .stl-row--edit .price-input, .rv-table .stl-row--edit select.unit-inline { width: 100%; min-width: 0; }
.rv-table .rv-edit-row > td { padding: var(--row-pad-y) var(--row-pad-x); background: var(--surface-selected); border-bottom: 1px solid var(--rule); white-space: normal; }
.rv-edit-acts { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.rv-table .rv-empty-row > td { padding: var(--space-5) var(--row-pad-x); text-align: center; font-size: var(--text-sm); color: var(--ink-muted); }
.rvq-work .rv-manual { margin-bottom: var(--space-3); }

/* The verified table stays a table with its column heads: the receipt pane
   is named a container of its own here, so the stacked, labelled rows the
   "pane" query above draws for the settled table never apply to it.

   Below 640px of pane (a phone, or the receipt beside an open paper on a
   1024 to 1440 window) the six fixed columns took 464px and left the name 28
   to 58px on a phone, one letter a line, and 138px beside the paper at 1440,
   four lines a row. 640 is the pane width the batch rows stack at too
   (CONTAINER_WIDTHS in tests/css-rules.test.mjs). Before, at
   520, the unit and the price gave way, which gave the name back too little
   and took the edit fields with them (their inputs measured 0x0). There
   each line reads in two: the mark, the name across the row and
   the pencil; under the name, the figures as the paper prints them, "6 sac x
   7,45 $ = 44,70 $". Nothing is hidden. The amount takes the pencil's column
   on the second line. The free space is a track of its own between the price
   and the amount, never part of what the amount spans: an item spanning a
   flexible track gives that track no floor, and the amount ran back over the
   price. With fixed tracks under it the amount keeps its width and the unit
   is what gives way: "sac 20 kg" wraps between its words, never to an
   ellipsis, since the unit is a figure checked against the paper. The
   figures need about 210px, which a 320 window still gives. The row carries
   the ground (hover, current, excluded) and its own rule; the cells are
   transparent grid items. */
.rvq-shell #review-detail { container-name: rvwork; }
@container rvwork (max-width: 640px) {
  /* :not([hidden]): the correction hides the printed rows' body while its
     own is open (receipts/correct.js), and a display here would show it. */
  .rv-table, .rv-table > tbody:not([hidden]) { display: block; }
  .rv-table > colgroup, .rv-table > thead { display: none; }
  .rv-table tr.rv-row {
    display: grid; grid-template-columns: auto auto minmax(min-content, auto) auto 1fr auto auto;
    grid-template-areas: "stat main main main main main act" ". qty unit price . amount amount";
    align-items: center; gap: var(--space-1) var(--space-2);
    padding: var(--row-pad-y-dense) var(--space-3); border-bottom: 1px solid var(--rule);
  }
  .rv-table tr.rv-row:last-child { border-bottom: 0; }
  .rv-table tr.rv-row > td, .rv-table tr.rv-row.is-current > td, .rv-table tr.rv-row.is-current > td:first-child,
  .rv-table .stl-row:hover > td, .rv-table .stl-row:focus-within > td, .rv-table tr.stl-row--edit > td,
  .rv-table .stl-row--out:not(:hover):not(:focus-within) > td, .rv-table .stl-row--out:nth-child(even):not(:hover):not(:focus-within) > td {
    display: block; height: auto; padding: 0; border: 0; background: transparent; text-align: left; box-shadow: none;
  }
  .rv-table tr.rv-row:hover, .rv-table tr.rv-row:focus-within { background: var(--surface-hover); }
  .rv-table tr.rv-row.stl-row--out:not(:hover):not(:focus-within) { background: var(--surface-sunken); }
  .rv-table tr.rv-row.is-current, .rv-table tr.rv-row.stl-row--edit { background: var(--surface-selected); }
  .rv-table tr.rv-row.is-current { box-shadow: var(--edge) var(--accent); }
  /* The cells' places carry the row in their selector, to outrank the reset
     above and the table's own white-space: normal on every cell. */
  .rv-table tr.rv-row > td.rv-stat-cell { grid-area: stat; align-self: start; width: auto; }
  .rv-table tr.rv-row > td.stl-main { grid-area: main; }
  /* The line's code and its state ("Signalée au siège") have the row's width
     here, so they wrap instead of ending in an ellipsis. */
  .rv-table .rv-name .stl-sku { white-space: normal; overflow: visible; }
  .rv-table tr.rv-row > td.stl-actions { grid-area: act; align-self: start; }
  .rv-table tr.rv-row > td.stl-qty { grid-area: qty; white-space: nowrap; }
  .rv-table tr.rv-row > td.stl-unit { grid-area: unit; white-space: normal; }
  .rv-table tr.rv-row > td.stl-price { grid-area: price; white-space: nowrap; }
  .rv-table tr.rv-row > td.stl-amount { grid-area: amount; justify-self: end; text-align: right; white-space: nowrap; }
  /* The signs between the figures are what the paper prints; a screen reader
     skips them (the empty alternative text) and reads each cell by its name. */
  .rv-table tr.rv-row:not(.stl-row--edit) td.stl-price::before,
  .rv-table tr.rv-row:not(.stl-row--edit) td.stl-amount::before { margin-right: var(--space-1); color: var(--ink-muted); font-weight: var(--weight-regular); }
  .rv-table tr.rv-row:not(.stl-row--edit) td.stl-price::before { content: "\00D7"; content: "\00D7" / ""; }
  .rv-table tr.rv-row:not(.stl-row--edit) td.stl-amount::before { content: "="; content: "=" / ""; }
  /* The line being changed: its three fields under the name, two to a line,
     each with its label over it, and the amount beside the price. Each field
     takes half the row, about 130px on a 360 window, where the unit list
     reads its whole choice. */
  .rv-table tr.rv-row.stl-row--edit {
    grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr) auto;
    grid-template-areas: "stat main main act" "qty qty unit unit" "price price amount amount";
    row-gap: var(--space-2);
  }
  .rv-table tr.stl-row--edit td.stl-qty::before, .rv-table tr.stl-row--edit td.stl-unit::before,
  .rv-table tr.stl-row--edit td.stl-price::before, .rv-table tr.stl-row--edit td.stl-amount::before {
    content: attr(data-label); display: block; margin-bottom: var(--space-1);
    font-size: var(--text-2xs); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-caps);
    text-transform: uppercase; color: var(--ink-muted);
  }
  .rv-table tr.stl-row--edit > td.stl-amount { align-self: end; }
  .rv-table tr.stl-row--edit td.stl-amount::before { text-align: right; }
  /* The edit area and the empty row under the lines: one block the row's width. */
  .rv-table tr.rv-edit-row, .rv-table tr.rv-edit-row > td, .rv-table tr.rv-empty-row, .rv-table tr.rv-empty-row > td { display: block; }
  .rv-table .rv-edit-row > td { padding: var(--row-pad-y) var(--space-3); }
}

/* The footer: the status sentence at the left, the receive press, "Suivante"
   and the keyboard hint, stuck to the bottom of the window at the panel's
   edges. Where they fit, the row reads as one line: the status, the presses,
   the keyboard hint at the far right. It is a wrapping row, not columns sized
   by a pane width: the presses and the hint go to a line of their own, at the
   right, as soon as they no longer leave the status 16rem beside them, however
   many presses the receipt has and however long a translation makes them. The
   grid it replaces gave the presses all the width they asked for from 780px of
   pane, sized for three; a duplicate's "Supprimer ce doublon" made four, about
   690px, and left the status 0 to 17px: "Les totaux ne concordent pas" one
   letter a line, in a stuck footer 800px tall that covered the question cards
   and the lines (Barista, 16 Oct). */
.rvq-work .rv-foot { gap: var(--space-2); padding: var(--space-3) var(--space-4); box-shadow: none; }
.rv-foot-row {
  display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center;
  gap: var(--space-2) var(--space-3); min-width: 0;
}
/* The status says its whole sentence: it wraps, in its pill and after it,
   rather than ending in an ellipsis. Beside the paper at 1024 it read "2
   lignes à vérifi...", and on a phone the pill was its icon alone. It is the
   row's left slot, not prose: the paragraph measure would stop it short of
   the row's start once the presses wrap, and the row's flex-end would push it
   to the middle. */
.rv-foot-status { flex: 1 1 16rem; display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); min-width: 0; max-width: none; margin: 0; }
.rv-foot-status > .state { min-width: 0; max-width: 100%; white-space: normal; }
.rv-foot-status-text { min-width: 0; overflow-wrap: anywhere; }
.rv-foot-sum { min-width: 0; font-weight: var(--weight-regular); color: var(--ink-muted); overflow-wrap: anywhere; }
.rv-foot-sum::before { content: "·"; margin: 0 var(--space-2); }
/* On a line of their own the presses still wrap, at the right, when even that
   line is too short for them all. */
.rvq-work .rv-foot-actions { flex: 0 1 auto; min-width: 0; flex-wrap: wrap; justify-content: flex-end; }
.rv-foot-next svg, .rv-foot-check svg { width: var(--icon-sm); height: var(--icon-sm); }
.rvq-work .rv-foot-keys { flex: 0 1 auto; max-width: 100%; min-width: 0; margin: 0; padding-left: 0; padding-right: 0; color: var(--ink-muted); font-weight: var(--weight-regular); white-space: nowrap; }
.rvq-work .rv-foot-keys:hover { background: transparent; color: var(--ink); }
.rv-foot-hint { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.rv-foot .recbar-note:empty { display: none; }
/* A receipt pane under 520px (a phone, or the paper open beside the receipt
   at 1024): the presses had the row and the status column 0 to 17px. There
   the status and the keyboard hint share the first line, the hint at most two
   fifths of it, and the presses take the second at full width, wrapping if
   a translation needs it. Where there is no keyboard (a touch screen) the
   hint says nothing usable and gives its room to the status. */
@container rvwork (max-width: 520px) {
  .rv-foot-row { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); grid-template-areas: "status hint" "actions actions"; row-gap: var(--space-2); }
  .rv-foot-status { grid-area: status; }
  .rvq-work .rv-foot-actions { grid-area: actions; justify-content: stretch; }
  .rvq-work .rv-foot-actions > .btn { flex: 1 1 auto; }
  .rvq-work .rv-foot-keys { grid-area: hint; justify-self: end; }
  @media (hover: none) and (pointer: coarse) {
    .rv-foot-row { grid-template-areas: "status status" "actions actions"; }
    .rvq-work .rv-foot-keys { display: none; }
  }
}

/* Narrower windows: the columns keep their order and the divider its job
   until bp-mid, where the panes stack (section 9 above). */
@media (max-width: 1100px) {
  .review-split.rvq-shell { grid-template-columns: minmax(0, var(--rv-paper-w, 42%)) var(--space-4) minmax(0, 1fr); gap: var(--space-4) 0; }
  .rv-head-main { flex-wrap: wrap; }
  .rv-head .recbar-fig, .review-detail--single .recbar-fig { min-width: var(--space-12); }
}
@media (max-width: 900px) {
  .review-split.rvq-shell, .review-split.rvq-shell.rv-nopdf { grid-template-columns: minmax(0, 1fr); grid-template-areas: "list" "paper" "work"; }
  .rv-divider { display: none; }
  .rvq-shell .pdf-preview { position: static; height: var(--rv-paper-h-narrow); }
  .rvq-shell.rv-nopdf .pdf-preview { height: auto; }
  /* The footer stays stuck where the panes stack (the header goes back into
     the flow): static, the receive press was two to six screens under the
     question on a phone. It is the last thing in the receipt's column, so it
     covers nothing that a scroll to the end does not bring out from under it,
     and a focused line clears it through --rv-foot-h (scroll-margin above). */
  .rvq-work .rv-foot { position: sticky; bottom: 0; }
}
@media (max-width: 620px) {
  .rv-bcard { min-width: 220px; }
  .rv-head .recbar-figs { grid-auto-flow: row; width: 100%; }
  .rv-head .recbar-fig + .recbar-fig { border-left: 0; border-top: 1px solid var(--rule); }
  .rv-foot-actions > .btn { flex: 1 1 auto; }
}

/* ── 14. The import sheet's own layout (#import-sheet, REDESIGN 4.2) ─────────
   The sheet's frame (its edge, width, top and the full-screen phone) is the
   shell's, in styles.css; what lies inside it is the import's, and every rule
   here names the import's own ids, so it reaches no other sheet. */

/* Once files are listed the drop zone stands aside: one line, the sentence
   and the button, instead of a 260px zone above the rows the operator came to
   read (at 844x390 the rows started under the fold). The window stays a drop
   target throughout, so nothing is lost by the zone being small. */
#wb-import:has(#import-results:not(.hidden)) > #batch-dropzone {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3);
  padding: var(--space-3); text-align: left;
}
#wb-import:has(#import-results:not(.hidden)) > #batch-dropzone :is(.dropzone-icon, .dropzone-help) { display: none; }
#wb-import:has(#import-results:not(.hidden)) > #batch-dropzone .dropzone-title {
  flex: 1 1 10rem; margin: 0; font-size: var(--text-sm); font-weight: var(--weight-medium);
}
#wb-import:has(#import-results:not(.hidden)) > #batch-dropzone .dropzone-browse { flex: none; }

/* The foot reads as one line where it can: the count at the left, the presses
   at the right, wrapping under the count only when the two do not fit. The
   kit's phone rule gave the presses a line of their own at full width and the
   sheet's narrow column then broke both labels onto two lines (117px of foot
   at 390, 139px at 320). */
#import-foot { flex-wrap: wrap; row-gap: var(--space-2); }
#import-foot > #import-progress { flex: 1 1 10rem; min-width: 0; }
#import-foot > .ui-action-buttons { width: auto; margin-left: auto; }
#import-foot > .ui-action-buttons > .btn { flex: 0 1 auto; white-space: nowrap; }
/* On a phone, and on a window too short for the list, "Ajouter des fichiers"
   (also the drop zone's press above) is its plus sign alone, its words kept
   for a screen reader, so the receive press keeps its one line. */
@media (max-width: 620px), (max-height: 31.25em) {
  #import-foot #btn-add-more { flex: none; width: var(--control-h); padding-inline: 0; justify-content: center; }
  #import-foot #btn-add-more > span {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  #import-foot > .ui-action-buttons { flex: 1 1 auto; }
  #import-foot #btn-to-review { flex: 1 1 auto; }
}
/* A window under 500px high (a phone held sideways) gave the sheet's body
   158px under the app bar: the sheet takes the whole height there, its own
   Fermer at the top being the way back, and the head and foot draw closer. */
@media (max-height: 31.25em) {
  #import-sheet { top: 0; }
  #import-sheet > .sheet-head { padding-block: var(--space-2); }
  #import-sheet > .sheet-foot { padding-block: var(--space-2); }
}
