/* ══════════════════════════════════════════════════════════════════════════
   DATA QUALITY — Missing in POS (products, units) and provider freshness

   Loaded after styles.css, which owns every token used below. Nothing here
   invents a colour, a spacing step or a radius: the two --freshness-* aliases
   already live in styles.css so that "stale means red" is a single decision,
   and this file only says which element wears them. There is no third alias
   for fresh, because fresh is the absence of a warning and .freshness-age
   leaves it on the ordinary ink.

   index.html links this sheet, marked data-if-sheet="data", and
   receipts/missing.js links it as the fallback before it appends the tab's
   markup. Keeping the rules in a file rather than in the module is what lets
   the theme, the media queries and the forced-colors block reach them at all.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Freshness ─────────────────────────────────────────────────────────────
   An age is a word, an icon AND a colour. The colour is never alone: a
   staff member who cannot separate the greens from the reds still reads
   "Stale" beside a warning triangle. */
.freshness {
  display: flex; align-items: center; gap: var(--space-2);
  flex-wrap: wrap; min-width: 0;
}
.freshness-when { color: var(--ink-muted); font-variant-numeric: tabular-nums lining-nums; }
.freshness-age { background: transparent; }
.freshness-age--ageing {
  color: var(--freshness-ageing);
  background: var(--state-attention-bg);
  border-color: var(--state-attention-rule);
}
.freshness-age--stale {
  color: var(--freshness-stale);
  background: var(--state-blocked-bg);
  border-color: var(--state-blocked-rule);
}

/* ══ Providers ═════════════════════════════════════════════════════════════ */
/* One group of header actions, not two controls drifting to opposite ends of
   the row: `.view-head > .btn` gives each direct child button its own auto
   margin. */
.view-head-actions {
  display: flex; align-items: center; gap: var(--space-2);
  flex-wrap: wrap; margin-left: auto;
}
/* ══ The Catalogue page (REDESIGN 4.7) ═══════════════════════════════════
   One sentence under the title, one row of subtabs, and the panel they choose
   (providers/catalogueTabs.js). The suppliers panel opens on the line that
   says how many suppliers there are and when their list was read, with the
   two list-wide syncs beside it. */
.cat-toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.cat-listdate { color: var(--ink-muted); font-size: var(--text-sm); }
.prov-reader { gap: var(--space-1); }

/* One supplier's catalogue, read-only, under its row (providers/browse.js). */
.cat-browse-row > td { padding: var(--space-4); background: var(--surface-sunken); }
.cat-browse { display: flex; flex-direction: column; gap: var(--space-3); }
.cat-browse-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.cat-browse-title { margin: 0; }
.cat-browse-field { max-width: var(--prose-max); }
.cat-browse-count { margin: 0; }
.cat-browse-table { background: var(--surface-raised); }
.cat-price > summary { color: var(--accent); cursor: pointer; }
.cat-price-list { margin: var(--space-2) 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--space-1); }
.cat-price-list li { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.cat-price-unit { font-weight: var(--weight-medium); color: var(--ink-strong); }
.prov-name { font-weight: var(--weight-medium); color: var(--ink-strong); }
.prov-supplier { color: var(--ink-muted); }
.prov-items { gap: var(--space-1); }
.prov-items-count {
  display: flex; align-items: baseline; gap: var(--space-2);
  color: var(--ink-strong); font-variant-numeric: tabular-nums lining-nums;
}
.prov-list-sync { color: var(--ink-muted); white-space: nowrap; }
/* The payoff of creating a product in the POS: the count moved, and by how
   much. It fades on its own after a few seconds — see DELTA_VISIBLE_MS. */
.prov-delta {
  padding: 0 var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--state-done-bg); color: var(--state-done-fg);
  font-size: var(--text-2xs); font-weight: var(--weight-medium);
  animation: if-wash var(--dur-4) var(--ease-out) both;
}
.prov-actions {
  display: flex; align-items: center; justify-content: flex-end;
  gap: var(--space-1); flex-wrap: wrap;
}
.prov-row-error { display: block; margin-top: var(--space-1); }

/* ══ Missing in POS ════════════════════════════════════════════════════════ */
#data-subtitle { margin: calc(-1 * var(--space-3)) 0 var(--space-4); max-width: var(--prose-max); }

.mi-toolbar {
  display: flex; align-items: center; gap: var(--space-2);
  flex-wrap: wrap; margin-bottom: var(--space-4);
}
.mi-toolbar-spacer { flex: 1 1 var(--space-4); }
/* The bulk "Marquer comme créés" and its reason sit together after the tally;
   the reason is the visible sentence a refused press points at. */
.mi-bulk { display: inline-flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); }
.mi-tick-cell { width: var(--tap-min); text-align: center; }
.mi-tick { margin: 0; }
/* The receipts a missing product holds, each a link that opens it. */
.mi-holds { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); margin-top: var(--space-1); }
/* Dismissing with a reason: four answers, the sentence that says what it does
   not do, and the press. A fieldset so the question names the group. */
.mi-dismiss { margin: 0; padding: 0; border: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.mi-dismiss > legend { padding: 0; }
.mi-dismiss-reasons { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.mi-dismiss-reasons .choice { display: inline-flex; align-items: center; gap: var(--space-2); min-height: var(--tap-min); }
/* The learned matches' search, with the count it answers in. */
.ml-tools { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3); margin-bottom: var(--space-3); }
.ml-field { flex: 1 1 var(--queue-w); min-width: 0; max-width: var(--prose-max); }
.ml-count { margin: 0; }

:root {
  --catalogue-fact-w: 140px;
  --catalogue-table-w: 720px;
  --catalogue-picker-w: 760px;
  --catalogue-unit-w: 260px;
}
.mi-group { margin-bottom: var(--space-4); }
.mi-group-head { align-items: center; }
.mi-group-title { margin: 0; }
.mi-group-fresh { font-size: var(--text-xs); }
.mi-group-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-left: auto; }
/* The supplier's name keeps a line's worth of width before its actions do.
   The kit gives a panel heading `flex: 1` (a zero basis), so on a phone the
   "..." menu and "Synchroniser les articles" kept their width and the name
   was squeezed to 100px, four lines for "Les Primeurs Jean-Talon". With a
   basis of its own the heading holds the first line and the actions wrap
   under it, starting at the left edge like the heading. */
.mi-group-head .ui-panel-heading { flex: 1 1 16rem; }
.mi-table { width: 100%; }
.mu-table, .mu-learned { min-width: var(--catalogue-table-w); }
.mi-product-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-4); }
.mi-product-select { display: inline-flex; align-items: center; gap: var(--space-2); }
.mi-product-identity { flex: 1; min-width: 0; }
.mi-product-qty, .mi-product-price { flex-basis: var(--catalogue-fact-w); }
.mi-product-seen { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin: var(--space-3) 0; }
.mi-product-decision { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); }
.mi-product-decision .cell-actions { flex-wrap: wrap; }
.mi-product-decision p { margin: var(--space-2) 0; }
.mi-product-decision + .banner { margin-top: var(--space-3); }
#providers-detail { margin-top: var(--space-4); }
.cat-detail-reader { margin-bottom: var(--space-4); }
#provider-pane-articles > .btn { margin-bottom: var(--space-3); }
.mi-table td.col-actions, .mu-table td.col-actions, .mu-learned td.col-actions {
  white-space: normal;
  vertical-align: middle;
}
/* The flex lives on a wrapper INSIDE the cell. A td displayed as flex stops
   being a table cell: the row loses a column, the header widths no longer
   apply to it, and the buttons spill past the table's edge - which is what the
   report looked like. */
.mi-table td.col-actions .cell-actions, .mu-table td.col-actions .cell-actions, .mu-learned td.col-actions .cell-actions {
  display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center;
  gap: var(--space-1);
}
.mi-table td.col-actions .btn, .mu-table td.col-actions .btn, .mu-learned td.col-actions .btn { margin-left: 0; }
.mi-table .mi-sku { font-family: var(--font-numeric); white-space: nowrap; }
.mi-table .mi-name { min-width: 0; }
.mi-col-note { display: block; font-weight: var(--weight-regular); text-transform: none; letter-spacing: 0; }

.mi-seen { display: block; }
.mi-seen-more > summary {
  display: inline-flex; align-items: center; gap: var(--space-1);
  min-height: var(--control-h-sm);
  cursor: pointer; list-style: none;
  color: var(--accent); font-size: var(--text-xs); font-weight: var(--weight-medium);
  /* The focus ring is the shared one of styles.css; the corner is this link's own, so it stays tight. */
  border-radius: var(--radius-xs);
}
.mi-seen-more > summary::-webkit-details-marker { display: none; }
.mi-seen-list {
  margin: var(--space-1) 0 0; padding-left: var(--space-4);
  color: var(--ink-muted); font-size: var(--text-xs); line-height: var(--leading-loose);
}

/* A note or a picker belongs to the row above it, so it sits inside the table
   as a full-width cell rather than under the whole group where the reader
   would have to work out which row it answers. */
.mi-note-row > td {
  padding: var(--row-pad-y) var(--row-pad-x);
  background: var(--surface-sunken);
}
.mi-note-row .inline-note { display: inline-block; margin-right: var(--space-2); }
.mi-resolve { margin: 0; }

/* Created rows stay readable — they are evidence, not noise — but they step
   back from the open ones, which are the work. */
.mi-row-created > td { color: var(--ink-muted); }

/* ── Units ─────────────────────────────────────────────────────────────────*/
.mu-block { padding: var(--space-4); border-bottom: 1px solid var(--rule); }
.mu-block:last-child { border-bottom: 0; }
.mu-block-head {
  margin: 0 0 var(--space-3);
  font-size: var(--text-xs); font-weight: var(--weight-semibold); color: var(--ink-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
.mu-block .table-wrap, .mu-block .table-scroll {
  border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--surface-raised);
}
/* An empty block is one line, not a 200px illustration: a supplier with
   nothing to answer drew two of them, one under the other, for zero items.
   Its icon, its sentence and its note sit on one row (wrapping on a phone),
   left-aligned with the block's heading. */
.mu-block .empty, .mu-block .empty-state {
  flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: flex-start;
  gap: var(--space-1) var(--space-3); padding: 0; margin: 0; max-width: none; text-align: left;
}
.mu-block .empty-state-icon { --disc-size: var(--disc); --disc-icon: var(--icon-sm); margin: 0; }
.mu-block .empty-state-line { margin: 0; font-size: var(--text-sm); }
.mu-block .empty-state-sub { margin: 0; flex: 1 1 16rem; }
/* The unit the resolver chose on its own. Shown as a fact, not as an answer:
   this is exactly the value the operator is being asked to confirm. */
.mu-fallback {
  padding: 0 var(--space-2);
  border: 1px dashed var(--rule-strong); border-radius: var(--radius-xs);
  color: var(--ink); font-size: var(--text-xs);
}
.mu-set + .mu-set { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--rule); }
.unit-chooser .field-label { margin-bottom: 0; align-self: center; }
.unit-chooser select { max-width: var(--catalogue-unit-w); }
.unit-chooser .chooser-row { align-items: center; }
.mu-learned-row td { vertical-align: middle; }

/* ── Narrow screens ────────────────────────────────────────────────────────
   The tables keep their columns and scroll inside their own container; only
   the chrome around them collapses. */
@media (max-width: 900px) {
  .mi-group-actions, .prov-actions { margin-left: 0; }
  .mi-group-head { gap: var(--space-2); }
  .unit-chooser select { max-width: none; }
  .unit-chooser .chooser-row { align-items: stretch; flex-direction: column; }
}

/* Print and forced-colors: the dashed unit box and the delta pill are shapes,
   not colours, so they survive a palette this file does not control. */
@media (forced-colors: active) {
  .freshness-age, .mu-fallback, .prov-delta { border: 1px solid CanvasText; }
}

/* The learned-match chooser: the list takes the row's width, so an item name,
   its code, its units and its last price all fit on one option line. */
.mi-resolve .choose-list, .mi-resolve .choose-search { width: 100%; max-width: var(--catalogue-picker-w); }

/* Catalogue > Correspondances apprises (providers/learned.js). A row's answers
   ("Changer", "Oublier", "Oublier pour toutes"), its scope and used-by chips,
   and the day and the time it was learned are one phrase each: they broke
   over three and four lines, and a pill mid-word, to share a column. Below
   720px every row is a card (.table-stack, styles.css); from there to the
   two-column desktop the cells close in on the compact rung of the row
   padding tokens, which is what keeps the action column on the panel at 1024
   rather than past its edge. The other shops' section is a section of its own,
   a step below the table it follows instead of touching it. */
.ml-table td.col-actions .btn, .ml-table .ml-when .cell-2line > span { white-space: nowrap; }
.ml-table .state, .ml-table .mchip { white-space: normal; overflow-wrap: break-word; max-width: none; }
.ml-admin { margin-top: var(--space-6); }
@media (max-width: 1100px) {
  /* The cells' own padding, not the row tokens: those are :root's alone (the
     density rule), and a table that re-pitched them locally would bring back
     the six row heights. */
  table.grid.ml-table td { padding-inline: var(--space-2); }
  table.grid.ml-table tr > td:first-child { padding-inline-start: var(--space-3); }
  table.grid.ml-table tr > td:last-child { padding-inline-end: var(--space-3); }
  table.grid.ml-table thead th { padding-inline: var(--space-2); }
}

/* ══ The proof of an invoice reader (GAP_REGISTER Part 4, T5) ═══════════════
   One checklist, drawn in two places: the reader editor's test panel
   (editor.js) and a reader proposal in Catalogue (providers/proposals.js).
   It lives here, with the rest of the reader's rules, because this sheet is
   linked on every page and the editor is a dialog any page can open. Each row
   is a state chip (word, icon and colour), the check in words, and under a
   failed check the invoice lines it is about, in the mono face so a column of
   figures reads as the paper printed it. */
.reader-proof {
  display: flex; flex-direction: column; gap: var(--space-2);
  margin-top: var(--space-4); padding-top: var(--space-3);
  border-top: 1px solid var(--rule);
}
.reader-proof-head { margin: 0; font-size: var(--text-sm); color: var(--ink-strong); }
.reader-proof-summary, .reader-proof-saved { margin: 0; color: var(--ink-muted); }
.reader-proof-saved { color: var(--ink); }
.reader-proof-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.reader-proof-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start;
  column-gap: var(--space-2); row-gap: var(--space-1);
}
.reader-proof-label { font-size: var(--text-sm); color: var(--ink); min-width: 0; }
.reader-proof-evidence {
  grid-column: 2; list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-1);
  font-size: var(--text-xs); color: var(--ink-muted);
}
.reader-proof-evidence code {
  font-family: var(--font-mono); font-size: var(--text-xs);
  white-space: pre-wrap; overflow-wrap: anywhere;
}

/* ── Reader proposals waiting for an org admin (providers/proposals.js) ────
   One row per proposal: the supplier and who proposed it, what the proof
   says, and the two answers. The answers' cell keeps one width and one
   height whatever it holds, so the outcome that replaces the two buttons
   moves nothing on the row or below it. */
.reader-proposals {
  margin-bottom: var(--space-4); padding: var(--space-3) var(--space-4);
  border: 1px solid var(--rule); border-radius: var(--radius-md); background: var(--surface-raised);
}
.reader-proposals-head {
  display: flex; align-items: center; gap: var(--space-2);
  margin: 0 0 var(--space-1); font-size: var(--text-md); color: var(--ink-strong);
}
.reader-proposals-list { list-style: none; margin: var(--space-3) 0 0; padding: 0; }
.reader-proposal {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) auto;
  align-items: start; gap: var(--space-3);
  padding: var(--space-3) 0; border-top: 1px solid var(--rule);
}
.reader-proposal-proof { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); min-width: 0; }
.reader-proposal-actions {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2);
  min-height: var(--row-h);
}
.prov-samples { display: inline-flex; align-items: center; gap: var(--space-1); }

@media (max-width: 720px) {
  .reader-proposal { grid-template-columns: minmax(0, 1fr); }
  .reader-proposal-actions { justify-content: flex-start; }
}

@media (max-width: 720px) {
  .mi-product-identity { flex-basis: 100%; }
  .mi-product-qty, .mi-product-price { flex: 1; }
  .mi-product-decision { align-items: stretch; flex-direction: column; }
}

.prov-reader .state { align-self: flex-start; }
.cat-article-tools .cat-browse-field { max-width: none; }
.cat-article-tools [data-browse-sync] { align-self: flex-end; }
