/* NAJM declaration builder — one stylesheet, no build step, no framework.
 *
 * THE IDEA. The product's thesis is verification by exception: most
 * declarations release untouched and the rest raise one answerable question.
 * The interface says that before it says anything else — COLOUR IS SCARCE.
 * A shipment with nothing to answer renders in ink and paper alone, and
 * saturation appears only where a person is actually needed. Four hues, one
 * job each, and none of them decorative:
 *
 *     brand   the one primary action on the screen, and the mark
 *     stop    a blocking finding. Money or a licence is at stake
 *     hold    the attention tier: liability nobody can answer from paperwork
 *     clear   released, agreed, closed
 *
 * TYPE ENCODES TRUTH. Codes and numbers are set in mono, prose in the sans,
 * everywhere and without exception. A customs value, a tariff line and a
 * container number are legally exact strings; a sentence explaining one is
 * not. The reader should never have to work out which they are looking at.
 *
 * THE SIGNATURE is the exception rail — a hairline gutter running down the
 * review column with a marker per finding. Blocks get a filled square in
 * stop; everything else gets a hollow hairline dot. Scanning the page
 * vertically finds the work, which is the whole product in one gesture.
 *
 * Right-to-left is logical properties throughout, never a mirrored sheet:
 * §2.5 makes Arabic a first-class surface rather than a skin.
 */

/* ---------------------------------------------------------------------------
   Tokens
   --------------------------------------------------------------------------- */
:root {
  /* Paper is cool and very slightly green — the colour of the tariff
     schedule's own stock, not the warm cream every generated page uses. */
  --paper:        #F3F5F2;
  --surface:      #FFFFFF;
  /* A surface that sits BELOW `--surface` in both themes.
     `--sunk` does not: it is defined per theme and inverts, so a panel set on
     it recesses in light and becomes the brightest block on the page in dark.
     `color-mix(--ink 4%, --surface)` does not either — `--ink` is light in
     dark mode, so mixing it in can only lighten, which is the same defect
     wearing a formula. Mixed toward `--paper`, which is genuinely darker than
     the surface in dark and quieter than it in light. */
  --recess:       color-mix(in srgb, var(--paper) 70%, var(--surface));
  --sunk:         #ECEFEB;
  --ink:          #14181A;
  --ink-2:        #4B5558;
  /* Darkened from #7B8588, which was 3.4:1 on paper and failed the 4.5:1
     floor everywhere `.quiet` is used — which is every screen. Two sessions
     found this independently and the darker of the two answers wins. */
  --ink-3:        #5C6669;
  --rule:         #E0E4DF;
  --rule-strong:  #C6CCC5;

  --brand:        #1B3A57;   /* harbour navy */
  --brand-ink:    #FFFFFF;
  --brand-soft:   #E9EFF4;

  --stop:         #A62B1C;   /* the stamp that is withheld */
  --stop-ink:     #FFFFFF;   /* text ON a stop surface, never a raw colour:
                                dark mode's --stop is a LIGHT salmon, so a
                                hardcoded white failed contrast on the one
                                surface that uses it as a background — the
                                error toast, which is the message that most
                                needs reading */
  --stop-soft:    #FAEBE8;
  --hold:         #8A6512;
  --hold-ink:     #FFFFFF;   /* text ON a hold surface. Same lesson as
                                --stop-ink, learned twice: dark mode's --hold
                                is a LIGHT amber, so a hardcoded white failed
                                contrast on the testing-mode strip — the one
                                banner whose whole job is being read. */
  --hold-soft:    #F9F2E1;
  --clear:        #14664A;
  --clear-soft:   #E7F1EC;

  --shadow-1: 0 1px 2px rgba(20, 24, 26, .05);
  --shadow-2: 0 4px 16px -4px rgba(20, 24, 26, .12);
  --shadow-3: 0 24px 64px -16px rgba(20, 24, 26, .28);

  --sans: "Avenir Next", Avenir, "Segoe UI Variable Text", "Segoe UI",
          system-ui, -apple-system, sans-serif;
  --mono: "SF Mono", SFMono-Regular, "JetBrains Mono", "Cascadia Mono",
          Consolas, "Liberation Mono", monospace;
  --ar:   "SF Arabic", "Geeza Pro", "Noto Naskh Arabic", "Segoe UI", sans-serif;

  --r-sm: 4px; --r: 8px; --r-lg: 14px; --r-full: 999px;
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;
  --s7: 48px; --s8: 72px;
  --page: 1120px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper:       #0F1213;
    --surface:     #161A1C;
    --sunk:        #1D2224;
    --ink:         #E7EBE8;
    --ink-2:       #A0AAAC;
    --ink-3:       #98A2A5;   /* was #788285, 4.1:1 on the dark surface */
    --rule:        #262C2E;
    --rule-strong: #38403F;

    --brand:       #7FA8CE;
    --brand-ink:   #0F1213;
    --brand-soft:  #1A2733;

    --stop:        #E4816E;
    --stop-ink:    #24100B;
    --stop-soft:   #331E1A;
    --hold:        #D3A64C;
    --hold-ink:    #241B08;
    --hold-soft:   #302914;
    --clear:       #5BB58F;
    --clear-soft:  #112B21;

    --shadow-1: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow-2: 0 4px 16px -4px rgba(0, 0, 0, .5);
    --shadow-3: 0 24px 64px -16px rgba(0, 0, 0, .7);
  }
}

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 15px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* The Latin face first, then the Arabic one. Font fallback is per-glyph, so
   this gives Arabic to SF Arabic and Latin to the product's own face — with
   `var(--ar)` alone, every Latin run in an Arabic page left the product's
   typography, and a client's legal name is often the largest Latin string on
   the screen. */
html[dir="rtl"] body { font-family: var(--sans), var(--ar); }

/* Numbers are data. Tabular figures keep a column of weights and values
   aligned on the decimal, which is how anyone actually checks a total. */
.mono, code, .num, td .num {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: .875em;
  letter-spacing: -.01em;
}

a { color: var(--brand); text-underline-offset: 2px; }
a:hover { text-decoration-thickness: 2px; }

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.014em; }
h1 { font-size: 26px; line-height: 1.2; }
h2 { font-size: 16px; }
h3 { font-size: 13px; }
p  { margin: 0 0 var(--s3); }
p:last-child { margin-bottom: 0; }

main {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--s6) var(--s5) var(--s8);
}

.muted { color: var(--ink-2); }
.quiet { color: var(--ink-3); }
.lede  { font-size: 15px; color: var(--ink-2); max-width: 62ch; }
.small { font-size: 13px; }

/* Spacing utilities on the scale, so a page template never states a raw
   pixel value (token-contract lows in the design review). */
.mt-2 { margin-top: var(--s2); }
.mt-4 { margin-top: var(--s4); }
.mt-5 { margin-top: var(--s5); }
.mb-4 { margin-bottom: var(--s4); }
.mb-5 { margin-bottom: var(--s5); }
td.end, th.end { text-align: end; }
/* `.mono` scales its size down for use INSIDE text; a heading set in mono is
   still a heading. */
h1.mono { font-size: 26px; }

/* LTR DATA. A tariff code, a currency-and-amount pair, a vessel and its
   flag, an XML line: these are data sequences with exactly one true order,
   and the Arabic page's bidi algorithm was reordering them (design review
   C1/H1). The chrome around them stays RTL; the data itself does not bend. */
.ltr-data { direction: ltr; unicode-bidi: isolate; }
/* Order is fixed; ANCHORING follows the page. Inside a table cell the LTR
   isolation also flips what `start` means, so on the Arabic page the value
   tore away from the label it belongs to and sat against the far edge with a
   gutter between them. The cell keeps the page's start edge — the same rule
   `.sadbox .vals.ltr-data` states for the form. */
html[dir="rtl"] td.ltr-data { text-align: end; }

/* The recurring structural device: a small uppercase mono label, the way the
   SAD form labels its own boxes. Used for section eyebrows and field names so
   that a heading is always distinguishable from content at a glance. */
.eyebrow {
  font-family: var(--mono);
  /* 12px, not 10.5px: below 12 is unreadable for a lot of people, and this is
     the label that names every section in the product. */
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  /* On paper the muted token lands at 4.4:1, just under the floor. This label
     sits on paper as often as on a card, so it takes the darker step. */
  color: var(--ink-2);
}
/* Arabic is cursive and joined; letter-spacing pulls the joins apart, and the
   mono face carries no Arabic, so a spaced uppercase label fell back to a
   third font and read as machine-translated. */
html[dir="rtl"] .eyebrow {
  letter-spacing: 0; text-transform: none; font-family: var(--ar);
  /* The cure above removes what made it a LABEL in English — tracking and
     uppercase — and left 12px grey Arabic sitting on top of 12.5px grey
     Arabic prose, reading as the paragraph's first line rather than the
     panel's name. Arabic has no case to carry that job, so weight and ink
     carry it instead. */
  font-weight: 600; color: var(--ink-2);
}

.icon { width: 16px; height: 16px; flex: none; vertical-align: -3px; }
.icon-lg { width: 20px; height: 20px; }

/* ---------------------------------------------------------------------------
   Header
   --------------------------------------------------------------------------- */
.top {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: var(--s5);
  padding: 0 var(--s5);
  height: 58px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid var(--rule);
}
.top-inner {
  display: flex; align-items: center; gap: var(--s5);
  width: 100%; max-width: var(--page); margin: 0 auto;
}
.brand {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-weight: 600; font-size: 15px; letter-spacing: -.02em;
  color: var(--ink); text-decoration: none; white-space: nowrap;
}
.brand { min-height: 44px; }
.brand .mark { width: 22px; height: 22px; color: var(--brand); flex: none; }
.brand small {
  display: block; font-size: 12px; font-weight: 500; color: var(--ink-2);
  letter-spacing: .08em; text-transform: uppercase; margin-top: 1px;
}
.top nav { display: flex; gap: var(--s1); flex: 1; }
.top nav a { min-height: 32px; display: inline-flex; align-items: center;
  font-size: 14px; text-decoration: none; color: var(--ink-2);
  padding: 6px 10px; border-radius: var(--r-sm);
}
.top nav a:hover { background: var(--sunk); color: var(--ink); }
.top nav a[aria-current] { color: var(--ink); font-weight: 600; }
/* The header overflowed the viewport on every page at 390px — the flex row
   could not shrink, so `.who` sat 51px past the edge and the whole document
   scrolled sideways. Splitting Configuration into three screens added two more
   links and made it worse, which is what finally surfaced it. Each part is now
   allowed to shrink or scroll rather than push. */
.top-inner > * { min-width: 0; }
.top nav { overflow-x: auto; scrollbar-width: none; }
.top nav::-webkit-scrollbar { display: none; }
.top nav a { min-height: 32px; display: inline-flex; align-items: center; white-space: nowrap; }
@media (max-width: 720px) {
  /* The nav takes its own row rather than a 28px slot between the wordmark
     and the role picker. Letting it merely scroll was worse than useless: the
     scrollbar is hidden, so four links compressed to a few pixels and every
     screen except the dashboard became unreachable on a phone with no menu
     button to say so. A fixed-height bar cannot hold two rows, so the height
     goes with it. */
  .top { height: auto; padding: var(--s2) var(--s3); gap: var(--s2); }
  .top-inner { flex-wrap: wrap; gap: var(--s2) var(--s3); }
  .top nav { flex-basis: 100%; order: 3; overflow-x: auto; }
  /* The wordmark keeps its name and loses its subtitle, which is the part
     that was 10px and failing contrast anyway. */
  .brand small { display: none; }
  .brand { min-height: 32px; }
  .who { margin-inline-start: auto; }
  .who .lang { padding-inline: 6px; }
}
.who { display: flex; gap: var(--s2); align-items: center; flex: none; }
.who .lang {
  font-size: 13px; text-decoration: none; padding: 6px 10px;
  border-radius: var(--r-sm); color: var(--ink-2);
}
.who .lang { min-height: 32px; display: inline-flex; align-items: center; }
.who .lang:hover { background: var(--sunk); }

/* The network bar. Any request in flight moves it; nothing else does. */
#netbar {
  position: fixed; inset-inline: 0; top: 0; height: 2px; z-index: 60;
  background: var(--brand); transform: scaleX(0); transform-origin: 0 50%;
  transition: transform .3s ease, opacity .3s ease; opacity: 0;
}
#netbar.on { opacity: 1; animation: crawl 12s cubic-bezier(.1,.8,.2,1) forwards; }
#netbar.done { transform: scaleX(1); opacity: 0; transition: opacity .4s .1s; }
@keyframes crawl { 0% { transform: scaleX(.02); } 100% { transform: scaleX(.92); } }

/* ---------------------------------------------------------------------------
   Page head
   --------------------------------------------------------------------------- */
.pagehead {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap; margin-bottom: var(--s5);
}
.pagehead .titles { display: grid; gap: 6px; }
/* The importer, under the job reference. Quiet: the reference is the file's
   name and this says whose it is. Nothing at all while the documents have
   not said — an empty line would read as a client with no name. */
.pagehead .titles .client {
  margin: -2px 0 0; font-size: 14px; font-weight: 500; color: var(--ink-2);
}
.pagehead .titles .client:empty { display: none; }
/* WHAT THIS FILE WAS CHARGED, under the importer (the billing spec §C):
   "This file: 20 lines · $4.00. Balance $196.00". Quieter again than the
   client line: the reference names the file, the client says whose it is,
   and this is context a person glances at rather than works from. No colour
   — this interface spends saturation only where somebody is needed, and a
   bill is not a question. The one clause that is not quiet is a budget stop
   (`#wizard-focus-stop`), said in full ink rather than in a hue the palette
   reserves for a blocked declaration. Empty renders as nothing at all: a
   file billing never reaches has no charge to state.

   ONE LINE AND NO DISCLOSURE since 2026-11-08: the AI spend breakdown that
   opened here (`details.spend`) is the operator's (`bayan spend`), and its
   rules went with it. */
.pagehead .titles .spend {
  margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink-3);
  /* A sentence with figures in it, so the prose measure governs it. */
  max-width: 80ch;
}
.pagehead .titles .spend:empty { display: none; }
.pagehead .titles .spend .mono { font-size: 12.5px; }
.pagehead .titles .spend .over { color: var(--ink); font-weight: 600; }

/* The head's controls travel TOGETHER. `space-between` on two loose children
   pushed the bin to the far corner of the window, a hand's width from the
   button beside it and looking like it belonged to the page rather than to
   this shipment. */
.pagehead .acts { display: flex; align-items: center; gap: var(--s2); }

/* ---------------------------------------------------------------------------
   Cards
   --------------------------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
  margin-bottom: var(--s4);
  overflow: clip;
}
.card > .head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--rule); flex-wrap: wrap;
}
.card > .head .titles { display: grid; gap: 3px; }
.card > .body { padding: var(--s5); }
.card > .body.tight { padding: var(--s3) var(--s5); }
.card > .foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); padding: var(--s4) var(--s5);
  border-top: 1px solid var(--rule); background: var(--sunk);
}
.card.flush > .body { padding: 0; }

/* ---------------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s2); padding: 9px 16px; min-height: 38px;
  border: 1px solid var(--rule-strong); border-radius: var(--r);
  background: var(--surface); color: var(--ink);
  font: 500 14px/1 var(--sans); letter-spacing: -.005em;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background .12s, border-color .12s, transform .06s, box-shadow .12s;
}
html[dir="rtl"] .btn { font-family: var(--ar); }
.btn:hover { background: var(--sunk); border-color: var(--ink-3); }
.btn:active { transform: translateY(.5px); }
.btn.primary {
  background: var(--brand); border-color: var(--brand); color: var(--brand-ink);
  box-shadow: var(--shadow-1);
}
.btn.primary:hover {
  background: color-mix(in srgb, var(--brand) 88%, var(--ink));
  border-color: color-mix(in srgb, var(--brand) 88%, var(--ink));
}
.btn.ghost { background: transparent; border-color: transparent; color: var(--ink-2); }
.btn.ghost:hover { background: var(--sunk); color: var(--ink); }
.btn.sm { padding: 5px 10px; min-height: 30px; font-size: 13px; }
.btn.lg { padding: 12px 22px; min-height: 46px; font-size: 15px; }
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .45; cursor: not-allowed; pointer-events: none;
}
/* A control that refuses AND SAYS WHY is not a dead control. The rule above
   takes hover, focus and the pointer away, which is right for a button that
   is merely unavailable and wrong for one whose whole job is now to explain
   itself. */
.btn[aria-disabled="true"].why { pointer-events: auto; cursor: help; }

/* Busy is a state, not a replacement. The label stays put so the button does
   not change width mid-click; the spinner takes the place of the icon. */
.btn.busy { pointer-events: none; }
.btn.busy .icon { display: none; }
.btn.busy::before {
  content: ""; width: 14px; height: 14px; flex: none;
  border: 2px solid currentColor; border-inline-start-color: transparent;
  border-radius: 50%; animation: spin .7s linear infinite; opacity: .85;
}
@keyframes spin { to { transform: rotate(360deg); } }

.row { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.row.end { justify-content: flex-end; }
.spacer { flex: 1; }

/* ---------------------------------------------------------------------------
   Fields
   --------------------------------------------------------------------------- */
input, select, textarea {
  font: 400 14px/1.4 var(--sans);
  padding: 9px 11px; min-height: 38px; width: 100%;
  border: 1px solid var(--rule-strong); border-radius: var(--r);
  background: var(--surface); color: var(--ink);
  transition: border-color .12s, box-shadow .12s;
}
html[dir="rtl"] input, html[dir="rtl"] select, html[dir="rtl"] textarea {
  font-family: var(--ar);
}
input:hover, select:hover, textarea:hover { border-color: var(--ink-3); }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
input::placeholder, textarea::placeholder { color: var(--ink-3); }
textarea { min-height: 76px; resize: vertical; line-height: 1.5; }
select {
  appearance: none; padding-inline-end: 30px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%237B8588' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-size: 12px;
  background-position: right 11px center;
}
html[dir="rtl"] select { background-position: left 11px center; }
.field { display: grid; gap: 5px; }
.field > label { font-size: 13px; font-weight: 500; color: var(--ink-2); }
.field .hint { font-size: 12.5px; color: var(--ink-3); }
/* Two fields that are one decision — the printed word and the code it means. */
.pair {
  display: grid; gap: var(--s3);
  grid-template-columns: minmax(0, 1fr) minmax(0, 8em);
}
.pair input { text-transform: uppercase; }
/* The two boxes that create a first-time importer. FULL WIDTH AND STACKED,
   which is a fix and not a preference: they were side by side in `.pair`,
   whose second column is capped at 8em because both of ITS fields are short
   codes. A label reading "customs registration" does not fit in 8em, so the
   label, the marker under it and the right edge of the input were all clipped
   by the card — and the clipped one was the box that must be typed. Stacked
   fields have no second column to overflow.

   `.pair` also uppercases its inputs, which is right for a country code and
   wrong for a name: `text-transform` is presentational only, so the box would
   have SHOWN Pharmavet Sal as PHARMAVET SAL while submitting the lower case,
   and a person would confirm one string while the client record stored
   another — on the column every later shipment's consignee is matched on.
   Out of `.pair`, that hazard is gone rather than patched. */
/* THE ANSWER, LEADING. One sentence carrying the printed name once, in the
   card's own message type — the same size and ink `.f-msg` uses, because it
   IS the card's message and only its position in the DOM differs (it has to
   sit inside the form to carry the name in bold, which the escaped message
   slot cannot). */
.says { font-size: 14px; color: var(--ink); max-width: 76ch; margin: 0; }
/* THE EXCEPTION, FOLDED. Closed by default: the matcher has already compared
   the printed block against every importer on file and found none, so the
   list is the path for somebody who thinks it was wrong — and it must not
   compete with the answer above it. Quiet, and no card of its own: a bordered
   panel here would read as a second question. */
/* THE EXCEPTION, AS A LINE OF TEXT (spec C2). It was a disclosure triangle
   under a question that answered itself; it is a link now, saying what it
   does, and it swaps its own words for the way back. A button and not an
   anchor — an anchor with no destination is a wall with a pointer on it —
   so it takes the link's clothes explicitly rather than inheriting them. */
.pick-link { margin: var(--s1) 0 var(--s3); }
button.linkish {
  border: 0; background: none; padding: 0; cursor: pointer;
  font: 500 13px/1.5 var(--sans); color: var(--brand);
  text-decoration: underline; text-underline-offset: 2px; text-align: start;
}
.mk-rtl button.linkish, html[dir="rtl"] button.linkish {
  font-family: var(--ar);
}
button.linkish:hover { text-decoration-thickness: 2px; }

.on-file > summary {
  cursor: pointer; font-size: 13px; color: var(--ink-3);
  padding: 2px 0; list-style-position: inside;
}
.on-file > summary:hover { color: var(--brand); }
.on-file[open] > summary { margin-bottom: var(--s2); }

.newclient {
  display: grid; gap: var(--s3);
  /* ATTACHED TO THE OPTION THAT ASKS FOR THEM. Free-floating, the two boxes
     sat in the same ground as the radios above with nothing saying which one
     they belonged to — and the panel has two options, so "which one" is the
     only question the layout has to answer. The rule is the brand ink the
     chosen option already carries, continuing down from it. */
  margin: 0 0 var(--s3);
  /* LOGICAL, to match the border beside it. This was `padding: … 0 0 var(--s3)`
     — a physical left padding under a `border-inline-start` — so in Arabic the
     rule moved to the right edge and the padding stayed on the left, pushing
     every field out past the card and clipping the registration box and both
     hints. The two properties have to agree about which side is the start. */
  padding-block: var(--s3) 0;
  padding-inline: var(--s3) 0;
  border-inline-start: 2px solid var(--brand);
}
/* NOTHING TO ATTACH TO. The rule and the indent said "these boxes belong to
   the option above them", and on the new-importer card there is no option
   above them: the three values ARE the card's body, directly under the
   sentence. Kept for the picked-from-a-list shape, where the option is still
   there and still needs answering for. */
.newclient.leading {
  padding-inline: 0; border-inline-start: 0; margin-top: var(--s2);
}
/* WHERE THE VALUE CAME FROM, under the box it came into — and in the quiet
   hint the stylesheet already had. Two earlier drafts got this wrong in the
   same direction. First a tracked uppercase chip beside each label, which
   shouted over the label it was explaining and, by suppressing wrapping,
   forced the overflow that clipped the whole second column. Then a red
   SENTENCE under the second box, which reads as an error that has already
   happened rather than a box waiting to be filled.

   Both hints are ink now, and the requirement is the WORD "required" on the
   label (2026-09-05): a marker was a legend to learn on a box nobody had
   reached yet. THE PALETTE IS ALREADY DECIDED and
   this spends none of it: red stays scarce and means the file is stopped;
   amber would have been worse still, because it means the two readers
   disagreed and the name is a value they AGREED on — the exact collision the
   `.sv.unconfirmed` block further down was moved off amber to avoid. */
/* A BOX AS WIDE AS THE VALUE IT WANTS. The registration is eight digits and
   the name is a company's; drawing both full width told a person the two
   fields expect the same kind of answer, and a 600px box for eight digits
   reads as "long free text" — the one affordance a form field has. Capped in
   `ch` so it follows the mono face rather than a guess in pixels, and floored
   by `min()` so a narrow screen still gets the full width. */
.newclient input[name="consignee_code"] { max-width: min(100%, 18ch); }
/* Wraps. Three controls at a 160px floor is 497px and the row could not break,
   so the new-shipment form pushed the dashboard 140px past a phone's viewport
   and dragged the shipments table out with it. */
.inline { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.inline input, .inline select { width: auto; min-width: 160px; max-width: 100%; }

/* The custom file control. The native input draws "No file chosen" in the
   browser's own grey and the browser's own language — it failed the 4.5:1
   floor in light mode and spoke English in Arabic mode, and neither half is
   stylable. The real input stays inside the label (focusable, required,
   opens the picker); only the face it shows is ours, in tokens. */
.filefield {
  position: relative; display: inline-flex; align-items: center;
  gap: var(--s3); min-height: 38px; max-width: 100%;
  padding: var(--s1) var(--s2); cursor: pointer;
  border: 1px dashed var(--rule-strong); border-radius: var(--r);
  background: var(--surface);
}
/* The input covers the whole control, invisible: the entire field is the
   tap target (not a 1px sliver), a click reaches the input itself, and the
   keyboard focuses it — the visible face below is presentation only. */
.filefield input[type="file"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  min-height: 0; padding: 0; border: 0; opacity: 0; cursor: pointer;
}
.filefield:has(input:focus-visible) {
  outline: 2px solid var(--brand); outline-offset: 2px;
}
.filefield:hover { border-color: var(--ink-3); }
.filefield .file-btn {
  font: 500 13px/1.5 var(--sans); padding: 5px 12px; white-space: nowrap;
  border: 1px solid var(--rule-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
}
.filefield .file-name {
  font-size: 13px; color: var(--ink-2); overflow-wrap: anywhere;
  padding-inline-end: var(--s2);
}

/* ---------------------------------------------------------------------------
   Pills and tags
   --------------------------------------------------------------------------- */
.pill {
  display: inline-flex; align-items: center; gap: 5px; flex-wrap: nowrap;
  padding: 3px 9px; border-radius: var(--r-full);
  font: 500 12px/1.5 var(--sans); white-space: nowrap;
  background: var(--sunk); color: var(--ink-2);
}
.pill::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%; flex: none;
  background: currentColor; opacity: .8;
}
.pill.plain::before { display: none; }
/* RED IS THE FILE WAITING (2026-09-06, narrowed and then widened again
   2026-09-07). `.stop` is the count on the bar that refuses to build the
   file while a question is open — and since 2026-09-07 the cells that count
   toward it wear the same colour, so the number and the page point at one
   set. `.rejected` is what customs did with a file that was built. A
   shipment that is HELD on the QUEUE is a shipment with work on it, which is
   not the same sentence: nothing on that screen is waiting on the reader, so
   it stays amber. */
.pill.stop, .pill.rejected, .pill.bad {
  background: var(--stop-soft); color: var(--stop);
}
.pill.clear, .pill.released, .pill.accepted {
  background: var(--clear-soft); color: var(--clear);
}
.pill.hold, .pill.held, .pill.reading, .pill.intake {
  background: var(--hold-soft); color: var(--hold);
}
.pill.brand { background: var(--brand-soft); color: var(--brand); }

/* ---------------------------------------------------------------------------
   Tables
   --------------------------------------------------------------------------- */
/* `min-width: 0` matters as much as the overflow: a flex or grid child
   defaults to min-width:auto and refuses to shrink below its content, so the
   scroller grew to the table's 640px and pushed the whole page sideways
   instead of scrolling inside itself. */
.scroller {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  min-width: 0; max-width: 100%;
}
table { width: 100%; border-collapse: collapse; font-size: 14px;
        min-width: 640px; }
.card .body > table { min-width: 0; }
td.nowrap, th { white-space: nowrap; }
th {
  text-align: start; padding: 10px var(--s5);
  /* Same two corrections as `.eyebrow`: 12px is the readable floor, and the
     muted token is under 4.5:1 on the sunk band these sit on. */
  font: 500 12px/1.6 var(--mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-2);
  background: var(--sunk); border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}
td { padding: 12px var(--s5); border-bottom: 1px solid var(--rule); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background .1s; }
tbody tr:hover { background: var(--sunk); }
td a { font-weight: 600; text-decoration: none; }
td a:hover { text-decoration: underline; }

/* ---------------------------------------------------------------------------
   Dashboard statistics
   --------------------------------------------------------------------------- */
.stats {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--s3); margin-bottom: var(--s5);
}
.stat {
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--r-lg); padding: var(--s4) var(--s5);
  box-shadow: var(--shadow-1); display: grid; gap: 6px; align-content: start;
}
.stat b {
  font: 600 28px/1 var(--mono); font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
}
.stat.headline b { color: var(--clear); }
.stat.headline.alarmed b { color: var(--stop); }
/* Q2: a rate without its denominator is a marketing figure, so the
   denominator is set beside the number and never hidden in a tooltip. */
.stat b .of { color: var(--ink-3); font-size: 18px; font-weight: 500; }
.stat span { font-size: 12.5px; color: var(--ink-2); line-height: 1.4; }

/* ---------------------------------------------------------------------------
   Steps
   --------------------------------------------------------------------------- */
.steps {
  /* Wraps, because the row now carries a note at its end: without this the
     pill and the third chip shrink into each other at 390px, which is the
     one-breakpoint text-over-text defect the audit fails CRITICAL on. */
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  list-style: none; padding: 0; margin: 0 0 var(--s5);
}
.steps li {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 13.5px; font-weight: 500; color: var(--ink-3);
}
/* THE STEP IS A BUTTON, and it has to look like one only where it works.
   A reached step is clickable and says so on hover and focus; a step this
   shipment has not got to is disabled and inert, because a control that
   looks live and does nothing is the wall this product keeps refusing. */
.steps .chip {
  display: flex; align-items: center; gap: var(--s2);
  font: inherit; color: inherit; background: none; border: 0;
  padding: 2px 6px; margin: -2px -6px; border-radius: var(--r-full);
  min-height: 28px; width: auto; cursor: pointer;
}
.steps .chip:disabled { cursor: default; opacity: .65; }
.steps .chip:not(:disabled):hover { background: var(--sunk); }
.steps .chip:not(:disabled):hover .n { border-color: var(--brand); }
.steps .chip:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.steps li .n {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  font: 600 12px/1 var(--mono);
  background: var(--surface); color: var(--ink-3);
  border: 1px solid var(--rule-strong);
  transition: background .2s, color .2s, border-color .2s;
}
/* DONE FIRST, ON SECOND, and the order is the fix. A step can now be both —
   the review step of a released shipment is finished AND the one you are
   looking at — and with `.done` last it won, so the stepper marked the page
   you were on as merely complete and highlighted nothing. Where you ARE
   outranks what you have finished. */
.steps li.done { color: var(--ink-2); }
.steps li.done .n {
  background: var(--clear-soft); border-color: var(--clear); color: var(--clear);
}
.steps li.on { color: var(--ink); }
.steps li.on .n {
  background: var(--brand); border-color: var(--brand); color: var(--brand-ink);
  box-shadow: 0 0 0 4px var(--brand-soft);
}
.steps .bar {
  width: 28px; height: 1px; background: var(--rule-strong); flex: none;
}
@media (max-width: 640px) {
  .steps li span.label { display: none; }
  .steps .bar { width: 16px; }
}

/* A READING NOBODY PAID FOR AGAIN SAYS SO, on the stepper — the one part of
   the wizard a person sees on every step of a shipment that has been read.
   Quiet on purpose: colour is scarce here and it is spent on money and
   licences, so this is ink on the neutral pill and nothing else. Opening it
   names the reader and the day the SERVICE answered, which is the date an
   evidence pack actually rests on. */
.steps .reuse-slot { margin-inline-start: auto; flex: none; }
.steps .reuse-slot:empty { display: none; }

details.reuse { position: relative; }
details.reuse > summary {
  cursor: pointer; list-style: none;
  /* The tap-target floor. `.pill` lands at exactly 24px on its own padding;
     stating it means a font change cannot quietly drop it below. */
  min-height: 24px;
}
details.reuse > summary::-webkit-details-marker { display: none; }
details.reuse > summary:hover { color: var(--ink); }
details.reuse > summary:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}
details.reuse .pop {
  position: absolute; z-index: 5;
  inset-block-start: calc(100% + 6px); inset-inline-end: 0;
  width: max-content; max-width: min(280px, calc(100vw - var(--s6)));
  padding: var(--s3); background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--r);
  box-shadow: var(--shadow-2);
}
details.reuse .hint {
  margin: 0 0 var(--s2); font-size: 12.5px; line-height: 1.5;
  color: var(--ink-2);
}
details.reuse ul { list-style: none; margin: 0; padding: 0; }
/* Beats `.steps li` on specificity, which would otherwise lay these rows out
   as chips in a flex row. */
details.reuse ul li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 0 var(--s2); align-items: baseline; font-size: 12.5px;
  padding-block-start: 4px; border-block-start: 1px solid var(--rule);
}
details.reuse ul li:first-child { border-block-start: 0; padding-block-start: 0; }
details.reuse .what { grid-column: 1 / -1; color: var(--ink-3); font-size: 12px; }
details.reuse .who { color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
details.reuse .when {
  color: var(--ink-2); font: 500 12px/1.6 var(--mono); white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Pages to assign
   --------------------------------------------------------------------------- */
.pages {
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}
.page {
  border: 1px solid var(--rule); border-radius: var(--r);
  padding: var(--s3); background: var(--surface);
  display: grid; gap: var(--s2); align-content: start;
  transition: border-color .12s, background .12s;
}
.page .n {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s2);
}
.page .n b { font: 500 12px/1 var(--mono); color: var(--ink-3);
             letter-spacing: .1em; text-transform: uppercase; }
/* A proposal is visibly not a decision: dashed until somebody confirms it. */
.page.proposed { border-style: dashed; border-color: var(--rule-strong); }
.page.blank { border-color: var(--hold); background: var(--hold-soft); }
.page.set { border-color: var(--rule); }
/* THE BOX WHOSE ANSWER IS IN FLIGHT (2026-09-05). Amber, because amber on
   this product means "nothing has settled this; a person must" and the
   answer is not settled until the server says so. Every other tick is
   DISABLED rather than hidden — a control that vanishes for a second reads
   as a defect, a greyed one reads as "wait" — and the browser's own disabled
   styling is what says so, with the opacity making it visible at a glance. */
.qa.recording {
  background: var(--hold-soft); border-color: var(--hold); color: var(--hold);
  font-size: 12px; padding-inline: var(--s2);
}
[data-qa][disabled], [data-qa-code][disabled] { opacity: .45; cursor: wait; }

/* A PAGE THAT BREAKS A RUN (2026-09-05, shipment 4744). The shorter of the
   two runs a unique document is marked on: the pages most likely to be the
   mistake. Amber, and the same amber as everything else that means "nothing
   has settled this; a person must" — never a third colour for a third
   internal category. It sits over whatever the card already is, because
   proposed-or-assigned is a different question from splits-a-document, and
   the pill beside the page number carries the words so the mark is not
   colour alone. */
.page.split { border-color: var(--hold); background: var(--hold-soft); }
.page .why { font-size: 12px; line-height: 1.4; color: var(--ink-3); }
.page .why b { color: var(--hold); font-weight: 600; }

/* The page itself. The thumbnail is the only thing on this screen that makes
   the confirmation a decision rather than a click, so it leads the card. */
.page .peek {
  position: relative; display: block; width: 100%; padding: 0; border: 0;
  border-radius: var(--r-sm); overflow: hidden; cursor: zoom-in;
  background: #fff; box-shadow: inset 0 0 0 1px var(--rule);
  /* Enough of the page to tell an invoice from a packing list at a glance,
     capped so four cards still fit on one screen. The rest is one click. */
  aspect-ratio: 1 / 1.3; max-height: 280px;
}
.page .peek img {
  width: 100%; height: 100%; object-fit: cover; object-position: top center;
  display: block;
}
.page .peek .zoom {
  position: absolute; inset-block-end: 0; inset-inline: 0;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px; font-size: 12px; font-weight: 500; color: #fff;
  background: rgba(17, 24, 39, .82); opacity: 0;
  transition: opacity .12s;
}
.page .peek:hover .zoom, .page .peek:focus-visible .zoom { opacity: 1; }
/* No renderer on the machine: the card keeps working without a broken image. */
.page .peek.noimage { display: none; }

/* ---------------------------------------------------------------------------
   The enlarged page
   --------------------------------------------------------------------------- */
.lightbox {
  position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center; padding: var(--s4);
  background: rgba(17, 24, 39, .62);
}
.lightbox[hidden] { display: none; }
.lightbox .frame {
  display: flex; flex-direction: column; background: var(--surface);
  border-radius: var(--r); box-shadow: var(--shadow-3);
  width: min(920px, 100%); max-height: 100%; overflow: hidden;
}
.lightbox .bar {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rule);
}
.lightbox .bar .which {
  font: 500 12px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3);
}
.lightbox .bar #pageview-prev { margin-inline-start: auto; }
.lightbox .sheet {
  overflow: auto; padding: var(--s4); background: var(--sunk);
  display: flex; justify-content: center;
}
.lightbox .sheet img {
  max-width: 100%; background: #fff; border: 1px solid var(--rule);
  border-radius: var(--r-sm);
}

/* The delete confirmation borrows the lightbox's CHROME — the frame, the bar,
   the backdrop — and none of its image-viewer furniture. Narrow on purpose: a
   dialog that is read in one glance is the whole point of it. */
.lightbox .frame.narrow { width: min(460px, 100%); }
.lightbox .bar.foot {
  border-bottom: 0; border-top: 1px solid var(--rule);
  justify-content: flex-end;
}
.confirm { padding: var(--s4); display: grid; gap: var(--s2); }
.confirm .job { margin: 0; font-size: 1.05rem; font-weight: 600; }
.confirm .who { margin: 0; }
.confirm .label {
  margin: var(--s2) 0 0; font: 500 12px/1 var(--mono);
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3);
}
.confirm .inventory { margin: 0; padding-inline-start: var(--s4); }
.confirm .inventory li { color: var(--ink-2); }
/* WHAT THE SHIPMENT TAUGHT THE SOFTWARE wears `.note`, the amber notice this
   stylesheet already defines — not a parallel class of its own. It is the only
   line in the dialog about consequences beyond this shipment (a precedent
   answers later ones at confidence 1.0), so it is the only one that is marked.
   `.undone` is true of every deletion and stays quiet: a second amber box
   would spend the colour on the sentence carrying no information. */
.confirm .note { margin: 0; }
/* Each precedent breaks as ONE thing. <bdi> keeps the Latin run in its own
   order, and this stops the line breaking THROUGH it: rendered in Arabic,
   "KD-9002 → 84186900" wrapped after "KD-" and read as two different codes. */
.confirm .note bdi { white-space: nowrap; }
.confirm .undone { font-size: 12px; }

/* ---------------------------------------------------------------------------
   The reading monitor — the one place motion earns its keep
   --------------------------------------------------------------------------- */
/* Named `readlist`, not `reading`: a shipment's STATUS is also "reading", so
   `.pill.reading` and a layout class called `.reading` collide at equal
   specificity and the later one wins. It turned every status pill into a grid
   and broke it onto two lines. Layout classes never share a name with a
   status value. */
.readlist { display: grid; gap: var(--s2); }
.doc {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4); border: 1px solid var(--rule);
  border-radius: var(--r); background: var(--surface);
}
.doc .kind { font-weight: 600; font-size: 14px; }
.doc .pp { font: 400 12px var(--mono); color: var(--ink-3); }
.doc .state { margin-inline-start: auto; }
.doc.working { border-color: var(--brand); background: var(--brand-soft); }
.doc.working .kind { color: var(--brand); }
.dots { display: inline-flex; gap: 3px; }
.dots i {
  width: 4px; height: 4px; border-radius: 50%; background: currentColor;
  animation: pulse 1.1s ease-in-out infinite;
}
.dots i:nth-child(2) { animation-delay: .16s; }
.dots i:nth-child(3) { animation-delay: .32s; }
@keyframes pulse { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }

/* ---------------------------------------------------------------------------
   Findings, on the exception rail
   --------------------------------------------------------------------------- */
.section { margin-bottom: var(--s5); }
.section > .eyebrow { display: block; margin-bottom: var(--s3); }

.rail { display: grid; gap: var(--s3); position: relative; }
.rail::before {
  content: ""; position: absolute; inset-block: 6px;
  inset-inline-start: 5px; width: 1px; background: var(--rule);
}
.finding {
  position: relative; padding-inline-start: var(--s5);
  border-radius: var(--r);
}
.finding::before {
  content: ""; position: absolute; inset-inline-start: 0; top: 15px;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--rule-strong);
}
/* THE CARD AGREES WITH THE CELL THAT OPENED IT (2026-09-07).
   Both markers were amber for a day. ONE COLOUR FOR A QUESTION (Ali,
   2026-09-06: "do we need amber and red? I think not") collapsed them,
   because a card that blocks and a card that asks for attention are both a
   person's work and the screen said so twice — once in the words, once in a
   colour a broker had to learn. What that missed is which of them the file
   is actually WAITING on: Ali, 2026-09-07, "the blocking ones should be in
   red so I can easily relate which ones I need to answer to proceed."
   So the block is red again and attention keeps amber. The SHAPE still
   separates them too — a block is a square, attention is a round dot, which
   is legible without knowing the code. The severity itself is untouched: a
   block still refuses the file, at the foot, where the refusal is. */
.finding.block::before {
  border-radius: 2px; background: var(--stop); border-color: var(--stop);
  box-shadow: 0 0 0 4px var(--stop-soft);
}
.finding.attn::before {
  background: var(--hold); border-color: var(--hold);
  box-shadow: 0 0 0 4px var(--hold-soft);
}
.finding .box {
  border: 1px solid var(--rule); border-radius: var(--r);
  background: var(--surface); overflow: clip;
}
.finding.block .box { border-color: color-mix(in srgb, var(--stop) 34%, var(--rule)); }
.finding.attn  .box { border-color: color-mix(in srgb, var(--hold) 34%, var(--rule)); }
.f-head { padding: var(--s4); display: grid; gap: 5px; }
/* The rule's own name is an IDENTIFIER — `blank_field_affects_duty` — and
   identifiers are for the record. It led every finding card in mono capitals,
   above a title that said the same thing in words, which is a bug report with
   an answer box attached. It lives in the evidence pack, the audit trail and
   the finding row, all of which are read by someone who wants it. */
.f-title { font-weight: 600; font-size: 15px; letter-spacing: -.01em; }
/* A finding is prose and prose has a measure. On a wide screen an uncapped
   line runs past the point where the eye reliably finds the next one, which
   is the opposite of what a page built to be scanned wants. */
.f-msg { font-size: 14px; color: var(--ink); max-width: 76ch; }
.f-detail { font-size: 13px; color: var(--ink-2); line-height: 1.5;
            max-width: 76ch; }
.f-title { max-width: 66ch; }
.f-actions { display: flex; gap: var(--s2); margin-top: 2px; }
/* A held reading sits inside the finding, set apart from the finding's own
   prose: it is evidence, not the software talking. It takes `.proposal`'s box
   — the system already has one inset-evidence panel and a second spelling of
   it would make the card read as two designs. What differs is the accent, and
   only the accent: `--hold` says a reading nothing decided, where `--brand`
   says a value you may accept. Logical properties only; the rail is on the
   reading side in both directions. */
.f-held {
  border: 1px solid var(--rule); border-inline-start: 3px solid var(--hold);
  border-radius: var(--r); padding: var(--s3) var(--s4);
  margin-top: var(--s3); background: var(--sunk);
  display: grid; gap: 6px; max-width: 62ch;
}
/* The eyebrow is the WORD, not the sentence: `HELD` earns the tracked mono
   treatment a rule name gets, and the clause explaining it is prose and is set
   as prose. Uppercase tracking is the slowest way to read a sentence. */
.f-held-label { display: flex; gap: var(--s2); align-items: baseline;
                flex-wrap: wrap; }
.f-held-word {
  font: 500 12px/1.5 var(--mono); letter-spacing: .08em;
  text-transform: uppercase; color: var(--hold);
}
/* Arabic is cursive and joined; letter-spacing pulls the joins apart, and the
   mono face carries no Arabic, so a spaced uppercase label fell back to a
   third font and read as machine-translated. Same cure as `.eyebrow`. */
html[dir="rtl"] .f-held-word {
  letter-spacing: 0; text-transform: none; font-family: var(--ar);
}
.f-held-why { font-size: 12.5px; color: var(--ink-3); }
/* One grid for the whole row, so two readings of one field start on a common
   axis. Comparing them is the panel's only job, and a ragged start makes the
   eye work for a difference that should be free. */
.f-held-row {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: 4px var(--s2); align-items: baseline;
}
.f-held-cand { display: contents; }
.f-held-reader { font: 500 12px/1.5 var(--mono); color: var(--ink-3); }
/* `pre-line`: what the reader printed across three lines was printed across
   three lines, and the line breaks are often WHY the two disagree. A quote
   off the page is the same kind of text and is set the same way — one
   declaration, two consumers, so they cannot drift apart. */
.f-held-text, .f-quote {
  font-size: 14px; color: var(--ink-2); white-space: pre-line;
  overflow-wrap: anywhere; min-width: 0;
}
/* THE PAGE'S OWN WORDS. `grid-column` for the one caller that is a grid (the
   finding card's held row); harmless in the value form, which is not.
   No rule, no tint, no device of its own: the drawer already sets its
   sections apart by their labels, and the exception rail is the one place
   this interface draws a line. Chanel's mirror — the label and the type do
   the work, so the accessory came off. */
.f-quotes { grid-column: 1 / -1; display: grid; gap: 4px;
            margin-block-start: var(--s3); }
/* THE FORM'S OWN LABEL VOICE, the same one `.reason-ask` is set in and for
   the same reason written there: an eyebrow names a section in tracked mono
   capitals, and `The page says` set in it reads as a code string rather than
   as a sentence introducing one. Mono in this interface means the value is
   legally exact; a label saying where the words came from is prose. */
.f-quotes-label { font-size: 13px; font-weight: 500; color: var(--ink-2); }
/* The fold's own labels, one step under the summary that opens them rather
   than louder than it (the critique of 2026-11-03): the summary's size, the
   form's label weight. */
.how-found .how-label { font-size: 12.5px; font-weight: 500;
                        color: var(--ink-2); }
.f-quote { margin: 0; max-width: 68ch; }
.f-held-where { grid-column: 1 / -1; }
.f-held-row > .quiet { grid-column: 1 / -1; }
.f-held .quiet { font-size: 12.5px; line-height: 1.45; max-width: 68ch; }
/* While the note is composed in English, an Arabic page must not let the
   paragraph direction reorder it. */
html[dir="rtl"] .f-held .quiet { direction: ltr; text-align: end; }
.finding form {
  padding: var(--s4); display: grid; gap: var(--s3);
  border-top: 1px solid var(--rule);
  /* THE SAME GROUND AS EVERY OTHER BLOCK IN THE CARD. This was `--sunk`,
     which INVERTS in dark: a card carrying both a note and a form drew two
     bands in two different shades, and the seam between them read as two
     panels that had been stacked rather than one card. `--recess` is the
     token `.f-hint` already chose for exactly this reason. */
  background: var(--recess);
}

/* One approval per document, under the value-by-value banner. It is a
   secondary control and is set as one: no callout ground, no accent, smaller
   type. The count sits with the document's name because the number is the
   reason to press it, and the reason box is required — an approval with no
   sentence behind it is the click-through this control exists to avoid.
   Logical properties only; the layout mirrors whole in RTL. */
/* Demoted to one line that opens.
   Ali's question was the right one: "why do I need to approve documents, if I
   am already approving the values deduced from them via the form." He does
   not — it is a shortcut, not a second obligation, and a panel three rows tall
   presented it as the main way to work. It earns its place only on the day
   somebody faces forty values and has genuinely read the invoice. So: a line,
   closed, that says what it is. */
.bydoc > summary {
  cursor: pointer; font-size: 13px; color: var(--ink-2); list-style: none;
}
.bydoc > summary::-webkit-details-marker { display: none; }
.bydoc > summary::before {
  content: "\25B8"; display: inline-block; margin-inline-end: var(--s2);
  color: var(--ink-3); transition: transform .12s;
}
.bydoc[open] > summary::before { transform: rotate(90deg); }
html[dir="rtl"] .bydoc > summary::before { transform: rotate(180deg); }
html[dir="rtl"] .bydoc[open] > summary::before { transform: rotate(90deg); }
.bydoc > summary:hover { color: var(--ink); }

.bydoc {
  margin-top: var(--s3); padding: var(--s3) var(--s4);
  border: 1px solid var(--rule); border-radius: var(--r);
  /* NOT `--sunk`: that token inverts in dark, and this panel then became the
     brightest large surface on the screen — the secondary control outshining
     the primary one it is subordinate to, on the one screen where the
     hierarchy is a legal argument rather than a preference. Mixed off `--ink`
     so it recesses in both themes by construction. */
  background: var(--recess);
  /* Rows are grouped apart from the fields inside them. At the same gap
     everywhere, one document's Approve sat as close to the next document's
     name as to its own reason box — and pressing the wrong one approves
     forty-seven values instead of one. */
  display: grid; gap: var(--s4);
}
.bydoc p.quiet { font-size: 12.5px; line-height: 1.5; max-width: 72ch;
                 margin: 0; }
.bydoc-row {
  display: grid; grid-template-columns: minmax(9rem, max-content)
                                        minmax(0, 1fr) max-content;
  gap: var(--s2); align-items: center;
}
.bydoc-name { display: flex; gap: var(--s2); align-items: baseline;
              font-size: 14px; }
/* The number is the consequence of the press, so it is set as a number:
   mono, tabular, its own colour. As body sans directly after a bold document
   name it read as a document NUMBER — "Invoice 47" is a plausible invoice
   number on this screen — and three counts at three x positions cannot be
   compared at a glance. */
.bydoc-name .count {
  font: 500 13px var(--mono); font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}
/* The values no document and no box accounts for. They are the tail of the
   work list and are set as a tail — small, wrapped, one control each — but
   they are PRESENT, because a work list that cannot be finished is the one
   thing this banner must never be. */
.bydoc-loose { display: flex; flex-wrap: wrap; gap: var(--s2); }
/* 12px floor. The path under the label is secondary, not small print — and
   an audit reference nobody can read is not a reference. */
.bydoc-loose .mono { font-size: 12px; color: var(--ink-3); }
/* A resting border, because `.btn.ghost` shows its edge only on hover and a
   touch device never hovers — so the last items on the work list looked like
   captions. They are the only control for those values. */
.bydoc-loose .btn {
  border-color: var(--rule-strong); flex-direction: column;
  align-items: flex-start; gap: 1px; min-height: 44px; padding: 6px 10px;
}
/* A real label, not a placeholder standing in for one. A placeholder
   vanishes the moment anybody types — so the requirement disappeared exactly
   when it was being met, and at phone width it truncated mid-sentence. The
   label states the field; the liability sentence stays as the placeholder. */
.bydoc-req { display: grid; gap: 3px; min-width: 0;
             font-size: 12px; color: var(--ink-2); }
.bydoc-row input { min-width: 0; }
/* Stacked below the point where three columns stop fitting — a reason box
   two words wide is a box nobody writes a reason in. */
@media (max-width: 640px) {
  .bydoc-row { grid-template-columns: minmax(0, 1fr); }
  /* A full-width button is the strongest button shape on a phone, and three
     of them outranked the primary "next value" pill in the banner above.
     Typographic hierarchy was right; the economic one was inverted. */
  .bydoc-row .btn { justify-self: end; width: max-content; }
  .bydoc-row + .bydoc-row { border-top: 1px solid var(--rule);
                            padding-top: var(--s3); }
}

/* Where the answer lives, on a finding that has no answer box of its own.
   It takes the form's footer position and the form's ground, because it is
   doing the form's job: it is the bottom of the card and it is what a person
   acts on next. It is NOT `--stop`-accented — the stop is already stated
   above, and repeating it here would say the destination is another problem
   rather than the way out of this one. */
.f-hint {
  padding: var(--s4); display: grid; gap: var(--s2);
  border-top: 1px solid var(--rule);
  /* NOT `--sunk` — that token inverts in dark and made this subordinate
     panel the brightest surface in the drawer. */
  background: var(--recess);
}
.f-hint p { font-size: 13.5px; color: var(--ink-2); line-height: 1.5;
            max-width: 66ch; margin: 0; }
/* ONE LABEL VOICE INSIDE A CARD. `.eyebrow` is tracked mono capitals and names
   a SECTION of the page; used in here it shouted WAITING ON ROWS ABOVE two
   lines above "Where does this come from?" in sentence case, so one card
   carried two kinds of label for the same kind of thing. */
.f-hint > .eyebrow {
  font-family: inherit; font-size: 13px; font-weight: 500;
  letter-spacing: 0; text-transform: none; color: var(--ink-2);
}
/* The way to the screen the sentence names. `justify-self: start` so it is a
   button and not a bar across the panel. */
.f-hint .btn { justify-self: start; text-decoration: none; }

.options { display: grid; gap: var(--s2); }
.opt {
  display: flex; gap: var(--s3); align-items: center;
  border: 1px solid var(--rule-strong); border-radius: var(--r);
  padding: 11px var(--s3); cursor: pointer; background: var(--surface);
  transition: border-color .12s, background .12s;
}
.opt:hover { border-color: var(--brand); }
/* THE AI'S PROPOSAL WEARS AMBER, because amber on this screen already means
   "nothing settled this yet — a person must", and a model's proposal is
   precisely that. Not red (nothing is stopped by advice) and never a colour
   of its own: a fourth colour would be a fourth vocabulary. */
.ai-proposal {
  background: var(--hold-soft); border: 1px solid var(--hold);
  border-radius: 6px; padding: var(--s3) var(--s4);
  font-size: 14px; color: var(--ink); display: grid; gap: var(--s1);
}
.ai-proposal .mono { color: var(--hold); font-weight: 600; }
.opt.proposed { border-color: var(--hold); background: var(--hold-soft); }
/* A BOX ARRIVING WITH AN ANSWER IN IT, in the same amber as everything else
   on this screen that nothing has settled. Without it a proposal and a value
   a person typed are the same white box, and the difference between them is
   the whole of what the colour vocabulary here says. Cleared on input: once
   somebody has typed, it is theirs and no longer a proposal. */
.field input.proposed {
  border-color: var(--hold); background: var(--hold-soft);
}
/* The proposed code inside a SAD cell wears the CELL'S own state (the
   2026-08-29 unified ruling: a blocking value is red, whatever it displays).
   This chip was amber back when the caption above it carried the red;
   captions stopped being coloured under unified marks and the amber chip
   became the box's only voice — a blocking box 33 that read as attention.
   Found by Ali on 4735-161, the fifth leftover of the retired model. */
.proposed-code { color: inherit; background: none; font-weight: 600; }
.opt.proposed:has(input:checked) {
  border-color: var(--hold); background: var(--hold-soft);
}
/* THE PRE-SELECTED PROPOSAL WEARS THE CELL'S OWN WASH (the modal spec,
   2026-08-29): a proposal arrives in the colour the cell is painted in, so
   the option list and the box it answers read as one state. `.opt.proposed`
   above is that wash for a question that stops nothing; inside a BLOCKING
   card it is red, because the cell that opened the drawer is (2026-09-07).

   It is read off the CARD, not off a class the caller passes: the `.stop`
   spelling that once sat here was emitted by a `washTone` argument, and the
   half of that argument left behind when the classes were collapsed on
   2026-09-06 threw at render and left the drawer unopenable. An ancestor
   cannot go stale.

   Nothing about the RULE changes. A proposal is still pending, still
   pre-selected, still nobody's answer until a person ticks it (contract
   rule 6) — the colour says which question it belongs to, not what it is. */
.finding.block .opt.proposed,
.finding.block .opt.proposed:has(input:checked) {
  border-color: var(--stop); background: var(--stop-soft);
}
.opt:has(input:checked) { border-color: var(--brand); background: var(--brand-soft); }
.opt input { width: auto; min-height: 0; margin: 0; accent-color: var(--brand); }
/* THE CONSIGNEE BLOCK AS THE PAPER PRINTED IT, leading the importer card.
   Line breaks kept — they are often why the readers disagreed — and amber,
   because a block nothing witnessed is a proposal, not a value. */
.consignee-quote {
  margin: 0 0 var(--s2); padding: var(--s2) var(--s3);
  border-inline-start: 3px solid var(--hold); background: var(--hold-soft);
  border-radius: var(--r); white-space: pre-line; font-weight: 600;
}
/* The importer list, sized for an office that files for many: it scrolls
   inside its own frame once a filter box is warranted, so two hundred
   importers cost the card no more height than eight. */
/* `max-content` rows, because a grid whose height is capped by its scroll
   frame sizes auto rows to the items' min-height and a two-name importer
   painted its Arabic name outside its own row (seen live). */
.pick-list { display: grid; gap: var(--s2); grid-auto-rows: max-content; }
.pick-list.tall {
  max-height: 288px; overflow-y: auto; padding-inline-end: 2px;
}
.pick-head { margin: var(--s2) 0 0; }
.pick-none { margin: 0; padding: 4px 2px; }
/* An importer's two recorded names stack: the reader finds their client in
   whichever language the office recorded, not only in Latin. */
.opt.pick .body { display: grid; gap: 2px; min-width: 0; }
/* THE CLASSIFICATION PANEL. One question: the goods, the lines, a reason.
   What was here — a proposal block above the list, a pre-filled field
   holding the row's own name, and a second form underneath with a second
   submit button — is gone. */
/* SCOPED TO THE TARIFF LIST, so the judgement and importer pickers keep the
   single row they were built for. Three columns: the radio, the line, and the
   rate — which decides the question and therefore holds its own column
   instead of trailing the Arabic text off the end of a wrapped row. */
.lines .opt {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--s3); align-items: start;
}
.lines .opt > input { margin-top: 3px; }
.lines .opt .body { display: grid; gap: 3px; min-width: 0; }
.lines .opt .duty { align-self: start; }

/* THE PROPOSED CODES, GROUPED, AT THE HEAD OF THE DECLARATION (Ali,
   2026-09-07, on 4739: 163 items, 149 proposals, ten codes). One line per
   code, and the line IS the decision: what the schedule calls it, what it
   costs, how many rows it answers, one press.

   It borrows the tariff list's grammar — mono code, Arabic text, the rate in
   its own column — so a group reads exactly like the candidate lines a
   broker already knows, and it borrows those two atoms rather than restating
   them. What it does not borrow is the radio: nothing here is selected, the
   press is the whole act.

   NO COLOUR OF ITS OWN. The card around it is red because the file waits on
   these rows (contract rule 6, as amended 2026-09-07); the lines inside it
   are ink on paper, and the one saturated thing is the press. */
.groups { display: grid; gap: var(--s2); padding: 0 var(--s4) var(--s4); }
.groups .group {
  display: grid; gap: var(--s2) var(--s3); align-items: baseline;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  border: 1px solid var(--rule); border-radius: var(--r);
  padding: 10px var(--s3); background: var(--surface);
}
/* THE CODE AND THE WORDS FOR IT ARE ONE OBJECT, on one line. Stacked — the
   drawer's own grammar, where the column is 300px — the Arabic description
   flushed to the far end of a 965px row and sat four hundred pixels from the
   code it names, with nothing between them. `dir="auto"` makes that text RTL
   and RTL text takes the end of its box; the fix is to stop giving it the
   whole box. It wraps under the code on a narrow screen, which is the same
   pair one line lower rather than two objects. */
.groups .group .body {
  display: flex; flex-wrap: wrap; align-items: baseline;
  column-gap: var(--s3); row-gap: 2px; min-width: 0;
}
/* Smaller than the code, because the code is the decision and the legally
   exact string; the description is what it means. Body size made the Arabic
   the largest thing on the line and the code the smallest. Scoped: in the
   candidate list the description IS the differentiator and keeps its size. */
.groups .ar { font-size: 13.5px; }
.groups .group .tally {
  font: 600 12.5px/1.35 var(--sans); color: var(--ink-2); white-space: nowrap;
}
/* Recorded, and the line stays until the rebuild takes its rows away: a line
   that vanished under the press would leave a person with a toast and no
   trace of what they had just done (review finding 6). One signal for it —
   the words, in `--clear` — and not a dashed border as well, which already
   means "type a code of your own" one card over. */
.groups .group.done .tally.said { color: var(--clear); }
/* THE PRESS IS `.scopeq`, IN THE QUESTION'S OWN COLOUR — the pill this
   product already uses for "answer this", and its geometry is shared below
   rather than restated with numbers of its own. What differs is the hue and
   only the hue: `.scopeq` wears `--hold` because the questions it opens stop
   nothing, and this one is the exit from a question the file is waiting on
   (rule 6, 2026-09-07). Soft ground at rest, solid on hover and on keyboard
   focus, which is the atom's own direction. */
.groups .group > button.qa-all {
  border: 1px solid var(--stop); background: var(--stop-soft);
  color: var(--stop); white-space: nowrap;
}
.groups .group > button.qa-all:hover,
.groups .group > button.qa-all:focus-visible {
  background: var(--stop); color: var(--stop-ink); outline: none;
}
/* WHAT IS BEING AGREED TO, before the press and not after it. Closed, under
   the line it belongs to — and it takes `.bydoc`'s marker rather than the
   dotted underline the PROSE folds use, because this opens a list and the
   triangle is what says so before it is pressed. The two RTL rules come with
   it: a marker pointing the wrong way in Arabic is a control lying about
   which way it opens. */
.group-rows { margin: -4px 0 2px; padding-inline-start: var(--s3); }
.group-rows > summary {
  font-size: 12.5px; color: var(--ink-3); cursor: pointer;
  list-style: none; width: fit-content;
}
.group-rows > summary::-webkit-details-marker { display: none; }
.group-rows > summary::before {
  content: "\25B8"; display: inline-block; margin-inline-end: var(--s2);
  color: var(--ink-3); transition: transform .12s;
}
.group-rows[open] > summary::before { transform: rotate(90deg); }
html[dir="rtl"] .group-rows > summary::before { transform: rotate(180deg); }
html[dir="rtl"] .group-rows[open] > summary::before { transform: rotate(90deg); }
.group-rows > summary:hover { color: var(--ink); }
/* A COLUMN, NOT A RAGGED RUN. Part numbers differ in width, so a flex row
   put every description at a different x — a staircase at two rows and a
   wall of them at ninety-five. */
.group-rows ul {
  margin: var(--s2) 0 0; padding-inline-start: var(--s4);
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: 4px var(--s3); font-size: 12.5px; color: var(--ink-2);
  /* Each description hugs the identifier it belongs to. Without it the
     second column is as wide as the card and the goods sit at the far end
     of it — which in Arabic put them at the opposite edge of the row from
     the part number naming them. */
  justify-items: start;
}
.group-rows li { display: contents; }
.groups-foot { margin: var(--s1) 0 0; font-size: 12.5px; }
/* On a phone the identity takes the width and the decision sits under it,
   rather than four columns squeezing a tariff line into two characters. */
@media (max-width: 560px) {
  .groups .group { grid-template-columns: auto minmax(0, 1fr); }
  .groups .group .body, .groups .group > button.qa-all { grid-column: 1 / -1; }
  .groups .group > button.qa-all { justify-self: start; }
  .groups .duty { margin-inline-start: 0; }
}
/* THE PROPOSAL'S TAG IS ONE WORD, "Proposed" (2026-10-03, the review's one
   voice). It was "THE SOFTWARE'S PICK" in tracked mono capitals — a code
   string where a word belongs, and in Arabic a third font. Sentence case,
   the sans, and the colour of the card it sits in: amber in an attention
   card, red in the card the file waits on, as the option around it is. */
.pick-tag {
  font: 600 12px/1.4 var(--sans); color: var(--hold);
  justify-self: start;
}
.finding.block .pick-tag { color: var(--stop); }
.pick-tag.opt-end { margin-inline-start: auto; }
html[dir="rtl"] .pick-tag { font-family: var(--ar); }
/* WHY, AS A CITATION — not as a second decision.
   Every answer carries a reason, because Art. 416 makes good faith no defence
   and the reason is the proof. It used to be a required textarea, and what a
   required textarea produces under time pressure is `test`: 72 of the reasons
   typed by hand on the office's own database said `test`, `t` or `testıng`.
   Filler is worse than an empty column — it looks like evidence.

   So it is a closed list now. But it deliberately does NOT reuse `.opt`: a
   card would then carry two identical stacks of radio boxes, the decision and
   its footnote in the same weight, and a broker reading fast would answer the
   wrong one. A reason names WHICH PAPER, so it reads like a citation — small,
   inline, one line each, sitting under the answer rather than beside it.
   The radio is the control and the chip is the whole of its target, so the
   hit area is the chip and the keyboard focus lands where the eye is. */
.reasons { display: grid; gap: var(--s2); margin-top: var(--s4); }
/* The form's own label voice, not `.eyebrow`. An eyebrow names a SECTION in
   tracked mono capitals, and a question set in it shouts — WHERE DOES THIS
   COME FROM? reads as an accusation rather than a field. */
.reasons > .reason-ask {
  margin: 0; font-size: 13px; font-weight: 500; color: var(--ink-2);
}
.reason-list { display: flex; flex-wrap: wrap; gap: var(--s2); }
.reason-chip {
  display: inline-flex; align-items: center;
  border: 1px solid var(--rule-strong); border-radius: 999px;
  padding: 6px 13px; cursor: pointer; background: var(--surface);
  font-size: 13px; color: var(--ink-2); line-height: 1.35;
  transition: border-color .12s, background .12s, color .12s;
}
.reason-chip:hover { border-color: var(--brand); color: var(--ink); }
/* The input is the control and must stay reachable; it is the CHIP that is
   drawn. Clipped rather than `display:none`, which removes it from the
   accessibility tree and from the tab order with it. */
.reason-chip input {
  position: absolute; width: 1px; height: 1px; min-height: 0;
  margin: 0; padding: 0; overflow: hidden; clip-path: inset(50%);
}
.reason-chip:has(input:checked) {
  border-color: var(--brand); background: var(--brand-soft);
  color: var(--ink); font-weight: 500;
}
.reason-chip:has(input:focus-visible) {
  outline: 2px solid var(--brand); outline-offset: 2px;
}

/* The way out of the list. Dashed, because it is the option that is not a
   line on the schedule yet — and the input only matters once it is chosen. */
.opt.other { border-style: dashed; }
.lines .opt.other { grid-template-columns: auto minmax(0, 1fr) auto; }
.opt.other input[name="typed_code"] {
  width: 9em; min-height: 32px; padding: 5px 9px; font-size: 13px;
}
/* The model's rationale, next to the pick it explains rather than above the
   list it is about. Closed by default: it is the least actionable thing on
   the panel and it was the largest. */
/* Indented to sit under the option it explains — inline-start, not left:
   a physical margin here hangs it off the wrong edge in Arabic. */
/* WHY THIS IS ASKED, folded, UNDER the button (Ali, 2026-08-29). The
   pale-row stack, the where-it-is-answered hint and the raise site's longer
   sentence all used to stand above the controls, so a person met three
   paragraphs of reference before the thing they came to do. None of it is
   wrong and none of it is an answer; it reads like the `why-pick` disclosure
   because it is the same kind of thing. */
.why-asked { margin: var(--s3) 0 0; }
/* THE FOLD SITS INSIDE THE CARD (D2). It is a direct child of the box, after
   the form — and the head and the form carry their own padding while the
   details carries none, so its summary sat ON the bottom border and read as
   a broken element hanging off the card. The box clips, so it was never
   visibly outside: it was flush against the edge, which is worse, because it
   looks deliberate. One rule, every drawer that draws a fold. */
.finding .box > .why-asked { padding: 0 var(--s4) var(--s4); }
/* WHY THE DECLARATION DIFFERS FROM A PAPER, as one line under the acts
   (2026-11-03, the drawer in plain words, PR-B): a card's title and, where
   they are its point, the figures under it — in the card head's own two
   voices (`.f-title`, `.f-msg`) and with none of its chrome. It is not a
   question, so it takes no box, no rail dot and no colour: the value above
   stays the drawer's one loud thing. A bare class, grepped free before it
   was spent (`.reason*` is a person's reason chips). */
.because { display: grid; gap: 4px; margin-block-start: var(--s4); }
/* HOW THE SOFTWARE GOT THIS (2026-11-03, the drawer in plain words). The
   drawer's own fold, at its foot under the acts: the ledger's note, the old
   basis word and the readings, kept word for word and opened on purpose. The
   SAME disclosure as a card's "Why this is asked" — it is the same kind of
   thing, reference — so it takes that summary's voice by sharing its rule,
   and its own class only so the driver can hold it to one per drawer. */
.how-found { margin-block-start: var(--s5); }
.why-asked > summary, .how-found > summary {
  font-size: 12.5px; color: var(--ink-3); cursor: pointer;
  list-style: none; width: fit-content;
  border-bottom: 1px dotted var(--rule-strong);
}
.why-asked > summary::-webkit-details-marker,
.how-found > summary::-webkit-details-marker { display: none; }
.why-asked > p, .why-asked .f-hint p, .how-found .how-block p {
  margin: var(--s2) 0 0; font-size: 12.5px; color: var(--ink-2);
  line-height: 1.5; max-width: 62ch;
}
.why-asked .f-hint { margin-top: var(--s2); }
/* One block per kind of record, each under its own label in the form's
   label voice (`.f-quotes-label`), a step apart; the readings take the held
   rows' own grid, so a reading looks like a reading wherever it is shown. */
.how-found .how-block { margin-block-start: var(--s3); }
.how-found .how-block > .f-held-row { margin-block-start: var(--s2); }
/* THE RECORD AND ITS READINGS KEEP THEIR OWN DIRECTION AND THE DRAWER'S EDGE
   (found by rendering the Arabic fold, 2026-11-03). `dir="auto"` gives an
   English record — or a bare figure, which has no strong character and
   resolves LTR — its own direction, and `match-parent` aligns it to the
   drawer's start rather than the opposite edge, where a "720" read as a
   second column 380px from its label. */
/* `match-parent` would say this logically, and Chrome computes it as plain
   `start` — the element's own (LTR) start, the far edge — so in Arabic the
   drawer's start is named outright. */
html[dir="rtl"] .how-found .how-block p,
html[dir="rtl"] .how-found .f-held-text,
html[dir="rtl"] .prov .proposal .val { text-align: right; }
/* ARABIC IS NOT SET IN MONO. The reading label is mono in the held rows;
   the mono face carries no Arabic, and the fallback drew قراءة with its
   letters apart — the same cure `.f-held-word` and `.eyebrow` already take. */
html[dir="rtl"] .f-held-reader { font-family: var(--ar); letter-spacing: 0; }

/* A form nobody signed in may submit. The options still render — seeing what
   the act WOULD be is the difference between a question and a wall — and one
   line says whose act it is. */
#held-form fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
#held-form fieldset[disabled] { opacity: .72; }
.whose-act { margin: var(--s2) 0 0; font-size: 12.5px; }

.lines .why-pick { margin: -2px 0 2px; margin-inline-start: 30px; }
.why-pick > summary {
  font-size: 12.5px; color: var(--ink-3); cursor: pointer;
  list-style: none; width: fit-content;
  border-bottom: 1px dotted var(--rule-strong);
}
.why-pick > summary::-webkit-details-marker { display: none; }
.why-pick > p {
  margin: var(--s2) 0 0; font-size: 12.5px; color: var(--ink-2);
  line-height: 1.5; max-width: 62ch;
}
/* THE GOODS, as data and not as a field. A pre-filled input holding the row's
   identity invites somebody to edit the one thing the question is not
   about. */
.goods {
  background: var(--sunk); border-radius: var(--r); padding: var(--s4);
  display: grid; gap: var(--s2);
}
.goods .what { font-size: 16px; font-weight: 600; letter-spacing: -.01em; }
.goods .line {
  display: flex; flex-wrap: wrap; gap: var(--s3);
  font-size: 12.5px; color: var(--ink-3);
}
.goods .line b { color: var(--ink-2); font-weight: 500; }
.opt .duty, .groups .duty {
  margin-inline-start: auto; font: 500 13px var(--mono);
  font-variant-numeric: tabular-nums;
}
/* DEARER, NOT WRONG. The figure says this code costs more duty than the one
   above it — a fact about money, and the only red left on a card would read
   as an alarm now that a question is amber. Weight carries it instead. */
.opt .duty.dear { color: var(--ink); font-weight: 600; }
/* DUTY IN WORDS (2026-10-03): "duty 20% ≈ 192.89", and the schedule's VAT
   rate under it as a rate. The words are prose and set as prose; the
   figures stay in their own isolated LTR runs (`bdi`), in the mono. */
.opt .duty { display: grid; justify-items: end; gap: 1px;
             font-family: var(--sans); text-align: end; }
.opt .duty bdi { font-family: var(--mono); }
.opt .duty .duty-line { white-space: nowrap; }
.opt .duty .vat { font-size: 12px; font-weight: 400; color: var(--ink-3); }
html[dir="rtl"] .opt .duty { font-family: var(--ar); }

/* ---------------------------------------------------------------------------
   Callouts
   --------------------------------------------------------------------------- */
.callout {
  display: flex; gap: var(--s3); align-items: flex-start;
  padding: var(--s4); border-radius: var(--r); font-size: 14px;
  border: 1px solid var(--rule); background: var(--sunk); color: var(--ink-2);
  line-height: 1.5;
}
.callout .icon { margin-top: 2px; color: var(--ink-3); }
.callout b { display: block; color: var(--ink); font-weight: 600;
             margin-bottom: 2px; }
/* NEW SHIPMENTS ARE PAUSED — the dashboard's one banner, above the stats,
   because what this office may do next outranks how it has been doing. It
   wears `hold`: a person must record a payment, which is what amber says
   here, and red stays spent on the declaration this software will not
   produce. `p` because the sentence is a paragraph and `.callout b` has
   already claimed the heading line. */
.intake-paused { margin-block-end: var(--s4); }
.intake-paused p { margin: 0; max-width: 78ch; }
.callout.clear { background: var(--clear-soft); border-color: color-mix(in srgb, var(--clear) 28%, var(--rule)); }
.callout.clear .icon, .callout.clear b { color: var(--clear); }
.callout.stop  { background: var(--stop-soft);  border-color: color-mix(in srgb, var(--stop) 28%, var(--rule)); }
.callout.stop .icon, .callout.stop b { color: var(--stop); }
.callout.hold  { background: var(--hold-soft);  border-color: color-mix(in srgb, var(--hold) 28%, var(--rule)); }
.callout.hold .icon, .callout.hold b { color: var(--hold); }
.callout.brand { background: var(--brand-soft); border-color: color-mix(in srgb, var(--brand) 28%, var(--rule)); }
.callout.brand .icon, .callout.brand b { color: var(--brand); }

/* The configuration lint. A diagnostic must not outrank the data it
   annotates: eleven keys of one class printed eleven copies of the same
   154-character sentence and took the screen (design review H5). The class's
   sentence is said once at a readable measure, and a key underneath it is a
   name and a verdict — both short enough to scan down. */
/* The lint sits in a `card flush` body, which carries no padding of its own —
   so the callout's rounded corner landed a pixel off the card's straight
   border while `max-inline-size` stopped its other edge mid-card, and the
   block read as dropped rather than placed. It takes the same --s5 inset the
   card's own head and table cells use, so its start edge lines up with the
   content column and the measure is what ends it. */
.callout.lint {
  margin-block-start: var(--s3); margin-inline: var(--s5);
  max-inline-size: 68ch;
}
.lint-why { font-size: 13px; margin: var(--s2) 0 0; max-inline-size: 62ch; }
.lint-why:first-of-type { margin-top: var(--s1); }
.lint-keys {
  list-style: none; margin: var(--s1) 0 0; padding: 0;
  display: grid; gap: var(--s1); font-size: 13px;
}
.lint-keys li {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2);
}
/* A key name is what the office typed into a file, so it stays in its own
   direction and its own face in both languages. */
.lint-key { font-family: var(--mono); font-size: 12.5px; }

.empty {
  display: grid; gap: var(--s3); justify-items: center; text-align: center;
  padding: var(--s7) var(--s5); color: var(--ink-2);
}
.empty .icon { width: 28px; height: 28px; color: var(--ink-3); }
.empty b { font-size: 15px; color: var(--ink); }

/* ---------------------------------------------------------------------------
   The next-step bar. One primary action per screen, and it says what happens.
   --------------------------------------------------------------------------- */
.nextbar {
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s4) var(--s5); border-top: 1px solid var(--rule);
  background: var(--sunk); flex-wrap: wrap;
}
/* Nothing to say, nothing to draw: the strip's rule and ground would
   otherwise paint an empty band under a form that is finished with it. */
.nextbar:empty { display: none; }
.nextbar .what { display: grid; gap: 2px; flex: 1; min-width: 240px; }
.nextbar .what b { font-size: 14px; font-weight: 600; }
.nextbar .what span { font-size: 13px; color: var(--ink-2); }

/* ---------------------------------------------------------------------------
   Provenance drawer
   --------------------------------------------------------------------------- */
.prov {
  position: fixed; inset-block: 0; inset-inline-end: 0;
  width: min(480px, 100vw); z-index: 50;
  background: var(--surface); border-inline-start: 1px solid var(--rule);
  box-shadow: var(--shadow-3); overflow: auto;
  animation: slidein .22s cubic-bezier(.2, .8, .2, 1);
}
@keyframes slidein { from { transform: translateX(6%); opacity: 0; } }
html[dir="rtl"] .prov { animation-name: slideinrtl; }
@keyframes slideinrtl { from { transform: translateX(-6%); opacity: 0; } }
.prov .head {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--s3); padding: var(--s4) var(--s5); position: sticky; top: 0;
  background: var(--surface); border-bottom: 1px solid var(--rule); z-index: 1;
}
#prov-body { padding: var(--s5); }
/* THE STEPPER IN THE HEAD (2026-10-03, the review's 6.2). One line: where
   this question sits among the counted ones, then the value's own label.
   The count is set as small type before the label, not as a second heading;
   it takes the stop colour only where the question IS counted, because the
   number it states is the bar's number — the red the file waits on. */
.prov-headline { display: flex; flex-wrap: wrap; align-items: baseline;
                 gap: 2px var(--s2); min-width: 0; flex: 1; }
.prov-headline h3 { margin: 0; }
.prov-step { font: 500 12.5px/1.4 var(--sans); color: var(--ink-3);
             white-space: nowrap; }
.prov-step.counted { color: var(--stop); }
.prov-step:not([hidden]) + h3::before {
  content: "\00b7"; margin-inline-end: var(--s2); color: var(--ink-3);
  font-weight: 400;
}
.prov-nav { display: inline-flex; gap: 2px; flex: none; }
.prov-nav .btn { min-width: 32px; font-size: 18px; line-height: 1; }
/* Two cards on one item, one at a time: "1 of 2" and its arrows, quiet,
   above the card they page. */
.card-pager { display: flex; align-items: center; justify-content: flex-end;
              gap: var(--s1); margin-top: var(--s3); }
.card-pager .card-of { font: 500 12.5px/1.4 var(--sans); color: var(--ink-2);
                       font-variant-numeric: tabular-nums; }
.card-pager .btn { min-width: 30px; font-size: 17px; line-height: 1; }
/* THE ONE FOOTNOTE, after the button: what the answer becomes. */
.f-foot { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--ink-3);
          text-align: end; max-width: none; }
/* The line the reading was found in, the reading underlined. */
.f-quote-hit { text-decoration-thickness: 1.5px; text-underline-offset: 3px;
               color: var(--ink); }
.f-quote-none { margin: 0; font-size: 12.5px; }
/* The bar's count is the way to the first question. */
button.pill { border: 0; cursor: pointer; font-family: inherit; }
button.pill:hover, button.pill:focus-visible {
  background: var(--stop); color: var(--stop-ink); outline: none;
}

/* ---------------------------------------------------------------------------
   The papers, at hand
   --------------------------------------------------------------------------- */
/* On the side OPPOSITE the question drawer, on purpose: paper on one side of
   the desk, questions on the other, and both mirror together in RTL. Nothing
   here is a question, so nothing here takes colour — the active tab is ink,
   not brand, because opening a page is not the screen's primary action. */
.paperdock {
  position: fixed; inset-block-end: var(--s5); inset-inline-start: var(--s5);
  z-index: 45; display: inline-flex; align-items: center; gap: var(--s2);
  padding: 10px 16px; border-radius: var(--r-full);
  background: var(--surface); border: 1px solid var(--rule-strong);
  box-shadow: var(--shadow-2); color: var(--ink);
  font: 600 13px var(--sans); cursor: pointer;
}
.paperdock:hover { box-shadow: var(--shadow-3); }
.paperpanel {
  position: fixed; inset-block-end: var(--s5); inset-inline-start: var(--s5);
  z-index: 45; width: min(620px, 94vw); height: min(82vh, 940px);
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface); border: 1px solid var(--rule-strong);
  border-radius: var(--r-lg); box-shadow: var(--shadow-3);
  animation: paperin .16s ease-out;
}
@keyframes paperin { from { transform: translateY(8px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .paperpanel { animation: none; } }
.paperpanel .bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--rule);
}
.paperpanel .tabs { display: flex; gap: var(--s1); overflow-x: auto; }
.papertab {
  border: 0; background: none; cursor: pointer; white-space: nowrap;
  font: 500 12.5px var(--sans); color: var(--ink-3);
  padding: 6px 8px; border-block-end: 2px solid transparent;
}
.papertab.on {
  color: var(--ink); border-block-end-color: var(--ink); font-weight: 600;
}
.paperpanel .pager { display: flex; align-items: center; gap: var(--s1); }
.paperpanel .count {
  font: 500 12px var(--mono); color: var(--ink-2);
  font-variant-numeric: tabular-nums; min-width: 44px; text-align: center;
}
/* The sheet sits on --sunk so the scan's own white edge reads as paper. */
.paperpanel .sheet { overflow: auto; flex: 1; background: var(--sunk); }
.paperpanel .sheet img {
  display: block; width: 100%; cursor: zoom-in; background: #fff;
  transition: opacity .12s ease;
}
.paperpanel .sheet img.loading { opacity: .4; }
.paperpanel .sheet img.zoomed { width: 175%; max-width: none; cursor: zoom-out; }
/* ---------------------------------------------------------------------------
   THE PANEL AS A WINDOW (Ali, 2026-09-06: "I can't move the docs preview
   around"). It keeps every one of its own manners — tabs, pager, zoom, the
   preloaded step — and gains a place of its own.
   --------------------------------------------------------------------------- */
/* The native handle at the corner, which needs no script. `overflow: hidden`
   above is what makes `resize` apply at all. */
.paperpanel {
  resize: both; min-width: 320px; min-height: 240px;
  max-width: 96vw; max-height: 96vh;
}
/* PLACED BY A PERSON: physical left/top take over from the logical insets the
   panel opens with, and they are written by `placePanel` and nothing else. */
.paperpanel.placed { inset: auto; }
.paperpanel .bar { cursor: grab; touch-action: none; }
.paperpanel.dragging { user-select: none; }
.paperpanel.dragging .bar { cursor: grabbing; }
/* WHERE THE HAND GOES. The bar is mostly tabs and a pager — a person aiming
   at "empty bar" is aiming at a tab — so the grip is a real target with a
   real cursor, and the tabs keep their clicks. */
.paperpanel .grip {
  width: 20px; height: 28px; flex: none; opacity: .55; cursor: grab;
  border-radius: 4px; align-self: center;
  background-image: radial-gradient(circle, var(--ink-3) 1.1px, transparent 1.1px);
  background-size: 5px 5px; background-position: center;
}
.paperpanel .grip:hover { opacity: .9; background-color: var(--sunk); }
.paperpanel.dragging .grip { cursor: grabbing; }
/* WHERE IT WOULD LAND. Drawn only while a drag is near an edge. */
/* The edge it would dock to, drawn only while a drag is near one. Physical
   left and right: a person dragging a window to the side of their screen
   means that side, in both reading directions. */
.paper-snap {
  position: fixed; inset-block: 0; z-index: 44; width: 30vw;
  background: var(--brand-soft); opacity: .5; pointer-events: none;
}
.paper-snap.left { left: 0; border-right: 2px solid var(--brand); }
.paper-snap.right { right: 0; border-left: 2px solid var(--brand); }

/* THE SOURCE TAG WHEN IT IS A WAY THERE. It sits where the quiet tag sits
   and reads like it — this is a place to go, not a decision — so it takes the
   underline every other link on this screen has and none of a button's
   weight. Inline, because it lives inside an option's own line. */
.src-jump {
  display: inline; padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; color: var(--ink-3); text-decoration: underline;
  text-decoration-style: dotted; text-underline-offset: 2px;
}
.src-jump:hover, .src-jump:focus-visible {
  color: var(--brand); text-decoration-style: solid;
}

@media (max-width: 720px) {
  /* The bottom sheet stays exactly as it was: no drag, no resize, no snap.
     The jump still opens the document it is about. */
  .paperpanel, .paperpanel.placed {
    inset: auto var(--s3) var(--s5) var(--s3); width: auto; height: 70vh;
    resize: none; max-width: none;
  }
  .paperpanel .bar { cursor: default; }
  .paperpanel .grip, .paperpanel .swap { display: none; }
}

/* ---------------------------------------------------------------------------
   Toasts. Errors say what happened; they do not apologise.
   --------------------------------------------------------------------------- */
/* CENTRED BY LOGICAL PROPERTIES ALONE (2026-10-06, found by rendering a
   refusal in Arabic). This was `inset-inline-start: 50%` — logical, and right
   — paired with `transform: translateX(-50%)`, which is PHYSICAL and therefore
   does not know which way round the page is. In Arabic the inset puts the
   box's RIGHT edge at the centre and the transform then moves it 260px
   further LEFT: measured on the review screen at 1440px, x = -60, so 60px of
   every toast sat off the edge of the screen and the first words of the
   sentence could not be read. It moved every toast the app has ever drawn in
   Arabic, not only the one that found it — the sheet's own header says
   "logical properties throughout, never a mirrored sheet", and half a rule
   had stayed physical.
   `inset-inline: 0` with `margin-inline: auto` over a fixed width centres in
   both directions with no transform at all, so there is nothing left to
   disagree about the direction. */
#toasts {
  position: fixed; inset-block-end: var(--s5);
  inset-inline: 0; margin-inline: auto; z-index: 70;
  display: grid; gap: var(--s2); width: min(520px, calc(100vw - 32px));
}
.toast {
  display: flex; gap: var(--s3); align-items: flex-start;
  padding: var(--s3) var(--s4); border-radius: var(--r);
  background: var(--ink); color: var(--paper); font-size: 13.5px;
  box-shadow: var(--shadow-2); line-height: 1.45;
  animation: rise .2s cubic-bezier(.2, .8, .2, 1);
}
.toast.bad { background: var(--stop); color: var(--stop-ink); }
.toast .icon { margin-top: 1px; flex: none; }
@keyframes rise { from { transform: translateY(8px); opacity: 0; } }

/* ---------------------------------------------------------------------------
   Skeletons
   --------------------------------------------------------------------------- */
.skel {
  border-radius: var(--r-sm); height: 12px;
  background: linear-gradient(90deg, var(--sunk), var(--rule), var(--sunk));
  background-size: 220% 100%; animation: sweep 1.3s ease-in-out infinite;
}
.skel + .skel { margin-top: var(--s2); }
.skel.w60 { width: 60%; } .skel.w40 { width: 40%; } .skel.tall { height: 60px; }
@keyframes sweep { to { background-position: -120% 0; } }

/* ---------------------------------------------------------------------------
   Notices
   --------------------------------------------------------------------------- */
.note {
  border-inline-start: 3px solid var(--hold); background: var(--hold-soft);
  padding: var(--s3) var(--s4); font-size: 13px; border-radius: var(--r-sm);
  margin: var(--s3) 0; color: var(--ink-2);
}
.assumption {
  border-inline-start: 3px solid var(--brand); background: var(--brand-soft);
  padding: var(--s2) var(--s3); font-size: 12.5px; border-radius: var(--r-sm);
  color: var(--ink-2);
}

/* ---------------------------------------------------------------------------
   Responsive and accessibility floors
   --------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .stats { grid-template-columns: repeat(2, 1fr); }
}
/* The dashboard's inline "new shipment" form owns the header's free space,
   so the one-line description gets out of the way before it becomes a
   four-word column. */
@media (max-width: 1000px) { .head .titles > .quiet { display: none; } }

@media (max-width: 620px) {
  main { padding: var(--s5) var(--s4) var(--s7); }
  h1 { font-size: 22px; }
  .card > .head, .card > .body, .card > .foot, .nextbar { padding: var(--s4); }
  th, td { padding: 10px var(--s4); }
  /* The content column narrows here, and the lint's inset is that column. */
  .callout.lint { margin-inline: var(--s4); }
  .stats { grid-template-columns: 1fr; }
  .top { padding: 0 var(--s4); }
  .brand small { display: none; }
}

/* THE PHONE LAYOUT IS A STACK, NOT A SCROLL (design review H2, M1). At
   390px the shipments table cut STATUS mid-chip and put BLOCKING, CREATED
   and Resume off-screen behind a scrollbar that is hidden — a broker on a
   phone could not see which shipment is blocked or act on it. The pattern
   is the one the memory route already ships: each row becomes a small
   card, decision first. The cells carry named classes so this layout
   cannot silently break when a column moves. */
@media (max-width: 640px) {
  .shipments { min-width: 0; }
  .shipments thead { display: none; }
  .shipments tbody, .shipments tr, .shipments td { display: block; }
  .shipments tr {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    /* The cost joins the row of quiet meta figures beside the date, and the
       action moves to a line of its own rather than being crowded off the
       end of it. Named areas, so this cannot silently break when a column
       moves — which is the whole reason the cells carry classes. */
    grid-template-areas: "job status" "client blocks" "created cost"
                         "act act";
    gap: 2px var(--s3); align-items: center;
    padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rule);
  }
  .shipments tr:last-child { border-bottom: 0; }
  .shipments td { padding: 0; border: 0; }
  .shipments td.c-job { grid-area: job; }
  .shipments td.c-client { grid-area: client; white-space: normal; }
  .shipments td.c-status { grid-area: status; justify-self: end; }
  .shipments td.c-blocks { grid-area: blocks; justify-self: end; }
  .shipments td.c-created { grid-area: created; }
  .shipments td.c-cost { grid-area: cost; justify-self: end; }
  .shipments td.c-act { grid-area: act; justify-self: end; }
  .shipments td[colspan] { grid-column: 1 / -1; }

  /* Configuration's precedent, alias and waiver rows: same stack. The
     reason gets the full width back — it was wrapping one word per line
     in a nine-character column (M1). */
  .cfg-rows { min-width: 0; }
  .cfg-rows tbody, .cfg-rows tr, .cfg-rows td { display: block; }
  .cfg-rows tr {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "pair status" "reason reason" "act act";
    gap: 2px var(--s3); align-items: center;
    padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rule);
  }
  .cfg-rows tr:last-child { border-bottom: 0; }
  .cfg-rows td { padding: 0; border: 0; }
  .cfg-rows td.c-pair { grid-area: pair; overflow-wrap: anywhere; }
  .cfg-rows td.c-reason { grid-area: reason; }
  .cfg-rows td.c-status { grid-area: status; justify-self: end; }
  .cfg-rows td.c-act { grid-area: act; justify-self: end; }
  .cfg-rows td[colspan] { grid-column: 1 / -1; }

  /* The severity table keeps its two columns; the rule name learns to
     wrap so the chip stops being crushed to a sliver. */
  .cfg-sev { min-width: 0; }
  .cfg-sev td.c-rule { overflow-wrap: anywhere; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}


/* A scan that is already uploaded should not keep offering itself. The picker
   stays reachable — a wrong scan has to be replaceable — but it stops
   competing with the step's real action for attention. */
/* De-emphasis is a colour demotion, never an opacity: fading the whole form
   dragged every string in it under the 4.5:1 floor (the audit's remaining
   file-control criticals were exactly this). The muted ink step reads as
   secondary and still clears the floor in both themes. */
#upload-form.secondary .filefield {
  border-style: solid; border-color: var(--rule); background: none;
}
#upload-form.secondary .file-btn {
  border-color: var(--rule); color: var(--ink-2); background: none;
}
#upload-form.secondary .file-name { color: var(--ink-2); }
#upload-form.secondary .btn { border-color: var(--rule); color: var(--ink-2); }
#upload-form.secondary:hover .file-btn,
#upload-form.secondary:focus-within .file-btn,
#upload-form.secondary:hover .btn,
#upload-form.secondary:focus-within .btn {
  color: var(--ink); border-color: var(--rule-strong);
}
#upload-form.secondary:hover .filefield,
#upload-form.secondary:focus-within .filefield {
  border-color: var(--rule-strong);
}


/* Directional icons follow the reading direction. An arrow pointing right
   inside a right-to-left page points backwards, which is the opposite of what
   the button does. Only the two that carry direction are mirrored. */
html[dir="rtl"] .icon:has(use[href="#i-next"]),
html[dir="rtl"] .icon:has(use[href="#i-back"]) { transform: scaleX(-1); }


/* "A declaration nobody can look at is a declaration asked to be trusted"
   stood here over a syntax-highlighted pane of the emitted XML. The principle
   holds and the pane was the wrong answer to it: what a broker looks at is the
   FORM, on the review step, where a wrong value can still be corrected. The
   bytes are a download and a link. */
/* ---------------------------------------------------------------------------
   The SAD, drawn as boxes
   ---------------------------------------------------------------------------
   Ruled boxes with the number in the corner, because that is the object the
   broker has keyed a thousand times and recognition is the whole point. The
   evidence is not on it: a click opens the same drawer everything else opens.
   The layout is an assumption and the screen says so above it. */
/* THE COLUMN THE DECLARATION SITS IN, and the one thing it does is hold the
   two boxes apart. The grouped classification card is a `.finding`, which
   takes its rhythm from the exception rail's own `gap` — and this card is
   not in a rail, so it had none: on 4737 its red frame ended flush against
   the declaration's top border and the two overlapped on Ali's viewport
   (2026-09-08). The gap is on the CONTAINER because both children are shared
   classes: `.finding` is every card on the screen and `.sad` is drawn
   wherever the form is. `gap` and not a margin, so a column with no card in
   it opens no space where nothing is. */
.declaration-column { display: grid; gap: var(--s4); }
.sad {
  display: grid; gap: var(--s3);
  border: 1px solid var(--rule-strong); border-radius: var(--r);
  background: var(--surface); padding: var(--s3);
}
/* The hairlines are drawn by each BOX, not by the grid's background showing
   through the gaps. With the old technique any track without a box rendered as
   a solid slab of rule colour — four of them, darker than the table header
   band and in the same neutral family, so they read as a header row that had
   lost its labels rather than as space. Together they took more of the page
   than the entire question system. Nothing is not a thing. */
.sadgrid {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px; background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--r-sm); overflow: hidden;
}
@media (max-width: 720px) {
  .sadgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sadbox { grid-column: span 1 !important; }
}
.sadbox {
  position: relative; background: var(--surface);
  box-shadow: 0 0 0 1px var(--rule);
  padding: 7px var(--s3) 9px;
  display: grid; gap: 3px; align-content: start; min-height: 56px;
}
/* THE CAPTION IS THE CONTROL, and the box number is part of it: "18.
   Transport at arrival", the way a broker says it out loud.

   It used to be the VALUE that carried the state and the click, with the
   number floating in the corner as a separate badge. Both were wrong for the
   same reason: the value is the one thing a box may not have. The currency
   that stops a shipment is stopping it BECAUSE the box is empty, so the mark
   had nothing to paint and the corner reserved a gutter the caption then had
   to break around. A caption exists whatever the box holds, it names the
   field rather than colouring a number that is not the problem, and it is a
   real target for a finger.

   `button` and not a span with a role: it is a button. */
.sadbox .cap {
  font-size: 12px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-3); overflow-wrap: break-word; text-align: start;
}
button.cap {
  appearance: none; background: none; border: 0; padding: 2px 0;
  font-family: inherit; cursor: pointer; min-height: 24px;
  display: inline-flex; align-items: center; gap: 6px;
}
/* TWO COLOURS, ONE PLACE (Ali, 2026-08-29). Red stops the file; amber is
   attention and not a stop. Both sit on the VALUE, in every half of the
   screen — the caption used to carry it here because "a box may have no
   value to paint", and that was never quite true: a box with nothing in it
   renders the words for nothing, and those words are what the wash goes on.
   One placement is what stops the same red meaning two things eight rows
   apart, and what lets a person learn the screen once.

   No dots. A dot beside a value is a mark with no meaning on its face — it
   said "there is a note here" in a vocabulary nobody was taught. */
/* A LABEL NEVER CHANGES COLOUR (Ali, 2026-08-29). The caption used to carry
   the box's state — red or amber, with a `!` or a `?` after it — which put
   the mark on the name of the field rather than on the thing that is wrong
   with it, and gave one screen two places to look for the same fact. State
   lives on the VALUE now, in every surface, and the caption is a caption. It
   stays a control: the click still opens the drawer. */
button.cap.asked, button.cap.held, button.cap.owed, button.cap.trusted,
button.cap.unconfirmed, button.cap.noted { color: var(--ink-2); }
button.cap:hover, button.cap:focus-visible {
  text-decoration: underline; text-underline-offset: 3px; outline: none;
}
button.cap:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* The box's own ring went with the coloured caption: a box is not a state,
   the value inside it is. Its hairline is the grid's, like every other box. */

/* The DECLARATION step keeps the value as the link to its own provenance
   chain; there is nothing to answer there, so nothing is on the caption. */
.sadbox .sv[role="button"] { cursor: pointer; }
/* The provenance link's own hover, and only its own: a marked value has a
   state colour and hovers into it. Left unscoped, this rule outranked both
   state hovers on specificity and washed a red stop in the brand tint. */
.sadbox .sv[role="button"]:not(.asked):not(.held):not(.owed):not(.trusted):hover,
.sadbox .sv[role="button"]:not(.asked):not(.held):not(.owed):not(.trusted):focus-visible {
  background: var(--brand-soft);
}
.sadbox .vals { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); }
/* A box with two elements of different meaning captions each value (box 17:
   destination, first destination). 12px, the floor the eyebrow keeps. */
.sadbox .cellpair { display: inline-flex; flex-direction: column; gap: 2px; }
.sadbox .cellcap { font: 500 12px var(--sans); color: var(--ink-2); }
/* Data sequences read left-to-right in both languages, but on the Arabic
   form they stay anchored to the box's start edge like every other value —
   the ORDER is fixed, the anchoring follows the page. */
html[dir="rtl"] .sadbox .vals.ltr-data { justify-content: flex-end; }
.sadbox .sv {
  font: 500 13.5px var(--mono); font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.sadbox .sv.weighty { font-size: 15px; }
/* A typed value is the one no document carried. On a form, that is the box
   worth a second look. */
.sadbox .sv.typed { color: var(--hold); }
/* A BOX THE DECLARATION LEAVES EMPTY quiets the WORDS it shows in place of a
   value — "left blank", "not on the declaration". It must not reach a marked
   value the box displays: box 20 on 4865-256 blocks with the held reading
   `CFR` shown in it, and this rule is three classes to `.sv.asked`'s two, so
   it won on specificity whatever the order and painted that CFR grey at
   regular weight beside box 36's bold red NP. One state, two looks, decided
   by whether the box around the value happened to be empty. */
.sadbox.blank .sv:not(.asked):not(.held):not(.owed):not(.trusted) {
  color: var(--ink-3); font-weight: 400; font-size: 12px;
}
.sadgrid.muted .sadbox { background: var(--sunk); }
.saditem .sadgrid { border-color: var(--rule-strong); }
.sadbox.no .sv { font-size: 18px; font-weight: 600; }
.sadbox.docs .doclist { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); }
/* One attached document is one LTR sequence — code, reference, date. The
   RTL page was reading the chips backwards (design review H1, box 44). */
.sadbox.docs .doc {
  display: flex; gap: var(--s2); align-items: center;
  direction: ltr; unicode-bidi: isolate;
}
.sadbox.docs .doc b {
  font: 500 12.5px var(--mono); cursor: pointer; border-bottom: 1px dotted var(--rule-strong);
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 24px; min-width: 24px;
}
.sadbox.docs .doc b:hover { color: var(--brand); }

/* ---------------------------------------------------------------------------
   The checkout — step 3
   ---------------------------------------------------------------------------
   The XML viewer that stood here is GONE (2026-08-23), and with it the second
   copy of the SAD. By the time a person is on this step the form has been read
   and consented to; what they need is what they are handing over, the file,
   and somewhere to say what customs did with it.

   One column, generous gaps, and no panel that can be mistaken for work: the
   only two controls are Download and the outcome. */
.checkout { display: grid; gap: var(--s5); }

.handover { display: grid; gap: var(--s3); }
/* auto-fit rather than a fixed count: a fact whose value the declaration does
   not carry is not rendered at all, so the row length varies by shipment. */
.facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden;
}
.fact { background: var(--surface); padding: var(--s4);
        display: grid; gap: 2px; align-content: start; min-width: 0; }
.fact .k { font-size: 11.5px; color: var(--ink-3); }
/* pre-line: the exporter is DERIVED by packing the name and the country into
   one element, and the break between them is how the block is printed. */
.fact .v { font-size: 16px; font-weight: 600; letter-spacing: -.01em;
           white-space: pre-line; overflow-wrap: anywhere; }
.fact .v.mono { font-weight: 500; font-variant-numeric: tabular-nums; }
.fact .sub { font-size: 12px; color: var(--ink-3); }

.filerow {
  display: flex; align-items: center; gap: var(--s5); flex-wrap: wrap;
  padding: var(--s5); background: var(--sunk); border-radius: var(--r);
}
.filerow .what { display: grid; gap: 4px; min-width: 0; flex: 1 1 320px; }
/* The LTR run still sits at the reading edge of an RTL page: isolation
   fixes the ORDER of the characters, not which side the line starts on. */
html[dir="rtl"] .filerow .what { justify-items: end; text-align: end; }
.filerow .name { font-size: 15px; font-weight: 600; }
.filerow .meta { font-size: 12.5px; color: var(--ink-3); }
/* The hash wraps rather than scrolling the page sideways, and it is capped at
   a measure so it does not become the widest thing on the screen. */
.filerow .sha { font-size: 11.5px; color: var(--ink-3);
                overflow-wrap: anywhere; max-width: 58ch; }

/* THE ROWS ONE DECLARATION ITEM IS MADE OF (Ali, 2026-09-23). An item is
   several invoice rows grouped by tariff line and nothing on the form says
   which; clicking the item's name marks them here.

   ON THE `> td`, the way `.rows tr.pale > td` below does, and for that rule's
   own reason: a background on the `tr` is over-painted by `tbody tr:hover`,
   one on the cells is not. The existing strip already solved this.

   `--brand-soft` is the sheet's "this is the one you picked" wash — the same
   token as `.opt:has(input:checked)`, `.doc.working` and `.assumption`. It is
   NEITHER `--stop-soft` nor `--hold-soft`: red is a question the file waits
   on and amber one that stops nothing (contract rule 6), and a highlight in
   either would say these rows have a problem when they do not. It is not
   `--sunk` either, which is spent on `tbody tr:hover` — indistinguishable
   from the pointer — and on `.rows tr.pale.taken`, where it means something
   else about a row.

   A MARKED ROW IS A SCROLL TARGET: the header is 58px and sticky, so the row
   clears it. `.sv.current`'s own 120px is for a cell inside a scroller and is
   not this measure. */
.rows tr.of-item > td { background: var(--brand-soft); }
.rows tr.of-item { scroll-margin-block: 76px; }

/* THE GAPS COLLAPSE AROUND THEM (Ali, 2026-09-23: "same declaration item
   highlights lines that are very far apart and needs scrolling to view
   them"). While an item is traced, a run of rows that are not its own folds
   into one band saying how many. Nothing is removed and nothing is
   reordered — the table's order is the paper's order.

   `--recess` is the one muted ground this table has not spent: `--sunk` is
   `tbody tr:hover` and `.rows tr.pale.taken` below, `--brand-soft` is the
   highlight itself, and `--hold-soft`/`--stop-soft` are the amber and the
   red the screen keeps for a question (contract rule 6) — a band in either
   would say these rows have a problem when they are only not this item's.

   ON THE `> td`, for the reason stated above it: a background on the `tr` is
   over-painted by `tbody tr:hover`, one on the cells is not. Which also
   means the band never takes the pointer wash a data row takes, so its
   button carries the hover and the focus ring instead.

   SHORTER THAN A ROW AND THE ONLY CENTRED THING IN THE TABLE: it is not a
   line of the invoice and must not be read as one.

   ONE OBJECT, ONE GROUND, in both states. Expanded, the band stays — it is
   the run's own boundary and the way back — and what says it is open is the
   WORDS on it, "Hide these rows" over the rows themselves, rather than a
   second wash a person would have to learn. */
.rows tr.gapband > td {
  background: var(--recess); text-align: center; padding: 2px var(--s5);
}
.rows tr.gapband button {
  font: 500 11.5px/1.6 var(--mono); letter-spacing: .08em;
  color: var(--ink-3); background: none; border: 0; cursor: pointer;
  padding: var(--s1) var(--s3); border-radius: var(--r-sm);
}
.rows tr.gapband button:hover { color: var(--ink); }

/* A ROW THE PAPER CARRIES AND THE DECLARATION DOES NOT.
   Amber: one reader saw it and the other did not, nothing has settled where
   its money goes, and it is the only row on the table that is not on the
   file — so it reads as thinner than the rows around it without reading as
   an error.

   THE BANNER ROW UNDER IT IS GONE (2026-08-29). Every pale row emitted a
   second full-width `<tr>` carrying a sentence and a pill, in all three
   states, so two pale rows made six table rows out of four invoice rows and
   the sentence was printed once per row. The sentence is in the table's
   caption now, once, with a count; the act is the row's own ✓ and the click
   that opens its question, which is what every other marked value on this
   screen already does. */
.rows tr.pale > td { background: var(--hold-soft); }
.rows tr.pale .sv.unread { color: var(--hold); font-style: italic; }
.rows tr.pale .sv.proved { color: var(--ink-2); }
/* RED IS THE FILE'S STATE, NOT THE VALUE'S PROVENANCE (2026-08-29; amber
   from 2026-09-06 to 2026-09-07, and red again since). An open BLOCK on the
   ROW — `row_not_declared` — paints the whole row red, because this is a
   question about the row and not about any one figure on it, and because a
   row the file is waiting on is one of the things the bar is counting: on
   4760-161 two of the four are FAN SPARE PARTS and COLOR BOX PRINTING. The
   italic reading and the proved figures keep their own weights; only the
   ground and the unread text change, so the row still reads as thinner than
   a declared one. */
.rows tr.pale.asked > td { background: var(--stop-soft); }
.rows tr.pale.asked .sv.unread { color: var(--stop); }
.rows tr.pale[data-pale] { cursor: pointer; }
/* TAKEN STOPS BEING AMBER. Amber on this product means "nothing settled
   this, a person must" — which is exactly what a taken row is no longer. It
   keeps its place in the table, because it is still not on the declaration,
   and it stops shouting for attention it has already had. A taken row that
   BLOCKS keeps the red: the file's state outranks the reading's. */
.rows tr.pale.taken > td { background: var(--sunk); }
.rows tr.pale.taken.asked > td { background: var(--stop-soft); }
.rows tr.pale.taken .sv.unread { color: var(--ink-3); }
.rows tr.pale .sv.typed { color: var(--ink); font-style: normal; }
/* A ROW SOMEBODY HAS DECLARED UNREAL. It stays on the table — the paper has
   a line the declaration does not, and that is a fact the person reading the
   two side by side needs — but it is the quietest thing there: no wash, no
   tick, struck through, saying what was decided. Its only affordance is the
   click, and its only act is the way back. */
.rows tr.pale.dismissed > td { background: none; color: var(--ink-3); }
.rows tr.pale.dismissed .sv.struck {
  text-decoration: line-through; color: var(--ink-3);
}
.rows tr.pale.dismissed .quiet { margin-inline-start: var(--s3); }
/* ONE ACT AT A TIME on the row's card: the reason chips belong to the act
   above them, and the group that is not picked is disabled — out of the
   form data and out of constraint validation — rather than merely hidden. */
fieldset.bare { border: 0; margin: 0; padding: 0; min-inline-size: 0; }

/* WHAT IS STILL OWED. Amber, because amber on this product means "not
   settled, a person must" — which is what an outstanding permit is. Not red:
   nothing is stopped, the file is built and handed over. */
.owed { display: grid; gap: var(--s3); padding: var(--s4);
        border: 1px solid var(--hold); border-radius: var(--r);
        background: var(--hold-soft); }
.owed .quiet { max-width: 66ch; }
.owe { display: grid; gap: 2px; padding-top: var(--s2);
       border-top: 1px solid color-mix(in srgb, var(--hold) 30%, transparent); }
.owe b { font-size: 14.5px; }
.owe .quiet { font-size: 13px; }

/* DID IT IMPORT. Three choices as real radios in real labels: the whole label
   is the target, which keeps every option over the 24px floor. */
.ask { display: grid; gap: var(--s4); }
.ask .q { font-size: 16px; font-weight: 600; }
.ask .why { margin: 4px 0 0; font-size: 13px; color: var(--ink-3);
            max-width: 66ch; }
.ask form { display: grid; gap: var(--s4); justify-items: start; }
.choices { display: flex; gap: var(--s3); flex-wrap: wrap; width: 100%; }
.choice {
  flex: 1 1 200px; display: flex; gap: var(--s3); align-items: flex-start;
  cursor: pointer; padding: var(--s4); border-radius: var(--r);
  border: 1px solid var(--rule-strong); background: var(--surface);
  transition: border-color .12s, background .12s;
}
/* THE SAME CURE `.opt` CARRIES, and it should have been taken from there in
   the first place: the global `input` rule sets `min-height: 38px` and
   `width: 100%` on every field, which turns a radio into a 38px circle
   floating above its own label. Reset here, beside the pattern it copies. */
.choice input {
  width: auto; min-height: 0; margin: 2px 0 0; flex: none;
  accent-color: var(--brand);
}
.choice .what { display: grid; gap: 2px; min-width: 0; }
.choice b { font-size: 14.5px; font-weight: 600; }
.choice .what > span { font-size: 12.5px; color: var(--ink-3); }
.choice:hover { border-color: var(--brand); }
/* A PROPOSAL, IN THE DRAWER'S OWN AMBER — the same two tokens `.opt.proposed`
   uses one pattern up, because the screen has one vocabulary for "nothing
   settled this yet; a person must" and a second amber would be a second
   meaning. The checked state keeps the amber rather than turning brand blue:
   pressing the row's ✓ agrees with the proposal, and the colour it agreed
   with should still be the colour on the record. */
/* A SUB-CHOICE, NOT A PEER. Inside the row drawer these three sit under an
   option that has already been picked, and at `.choice`'s own weight they
   read as three more answers to the question above — six equal options where
   there are three. Narrower basis so they sit side by side at drawer width,
   and quieter type, because the act is the decision and the box is its
   detail. */
.choices.compact { gap: var(--s2); }
.choices.compact .choice {
  flex: 1 1 0; min-width: 0; padding: var(--s3); gap: var(--s2);
}
.choices.compact .choice b { font-size: 13px; font-weight: 500; }
/* A SUM MUST NOT BREAK (2026-09-20). The row-reading choices are the same
   sub-choice as the charge boxes above and are read a different way: a box
   name is two words and a closure is `25200 × 1.250 = 31500`, which at
   `compact`'s equal narrow columns broke across three lines and became three
   unreadable fragments of arithmetic. So the sums size to their content and
   wrap as whole lines — a person compares them by reading DOWN — and each
   one stays on one line whatever the drawer's width. Everything else
   `compact` says is kept, because they are still details of an act that has
   already been chosen. */
.choices.compact.readings .choice { flex: 0 1 auto; }
.choices.compact.readings .choice b { white-space: nowrap; }
.choice.proposed { border-color: var(--hold); background: var(--hold-soft); }
.choice.proposed:has(input:checked) {
  border-color: var(--hold); background: var(--hold-soft);
}
/* Cheaper is a fact worth seeing at a glance and keeps its green; dearer is
   the same fact the other way and no longer wears the question's old colour
   (2026-09-06). A card carries one alarm colour or none. */
.choice.good b { color: var(--clear); }
.choice.bad  b { color: var(--ink); font-weight: 600; }
.choice:has(input:checked) {
  border-color: var(--brand); background: var(--brand-soft);
}
.choice:has(input:focus-visible) { outline: 2px solid var(--ink);
                                   outline-offset: 2px; }
.ask .field { width: min(100%, 34em); }

.checkout .tail {
  display: flex; gap: var(--s5); flex-wrap: wrap; align-items: baseline;
  padding-top: var(--s4); border-top: 1px solid var(--rule); font-size: 13.5px;
}
.checkout .tail a { color: var(--brand); }

/* WHAT THEY SAID, on its own line. Run in as a span it read as one sentence
   with the confirmation after it — "ASYCUDA refused code 781 Recorded." */
.callout .said { margin: 2px 0 4px; color: var(--ink); }


/* ---------------------------------------------------------------------------
   Values nobody has vouched for yet
   ---------------------------------------------------------------------------
   A DIFFERENT AXIS FROM `.weighty`, and deliberately a different colour.
   `.weighty`
   marks a field whose being wrong costs money — the project's own definition
   of what matters. `.unconfirmed` marks a value no person has put their name
   to. A field can be either, both or neither, and painting them the same
   would tell a broker that an unchecked package count and an unchecked
   container number are the same kind of problem.

   This is the one place the interface shouts. Under Art. 416 liability is
   strict and good faith is no defence, so the record of who looked at each
   machine reading is the product rather than paperwork around it. */
/* QUIET, because there are fifty of them.
   The first version painted every unconfirmed reading in the stop colour and
   the screen came out half red — which breaks the one rule the whole palette
   rests on: colour is scarce, and saturation means a person is needed HERE.
   Fifty values shouting is fifty values whispering, and the two genuine
   questions on the form disappeared among them.
   So a reading awaiting a name is marked, not alarmed: a dotted rule under it,
   ink not red. The eye can still find them, they read as a texture rather than
   an emergency, and the QUESTIONS — few, and actual decisions — keep the
   colour to themselves. */
.sv.unconfirmed {
  cursor: pointer;
  /* Visible as a STATE, not as an alarm. A dotted rule alone vanished at page
     scale and fifty-five values that need a name became fifty-five values
     nobody could find — the opposite failure to painting them all red, and
     just as useless. A faint fill reads as "this field is pending" the way an
     empty form field does, which is what it is. */
  /* INK, not amber. Amber now means the two readers disagreed and red means
     the file is stopped, and a tint that shares either of them would tell a
     broker that a routine reading awaiting a signature is the same kind of
     thing as a block. This is the neutral "pending" of an empty form field,
     which is exactly what it is. */
  background: color-mix(in srgb, var(--sunk) 85%, transparent);
  box-shadow: inset 0 -1px 0 0 color-mix(in srgb, var(--ink-3) 55%, transparent);
  border-radius: 2px; padding: 1px 4px;
}
.sv.unconfirmed:hover, .sv.unconfirmed:focus-visible {
  background: var(--brand-soft); color: var(--brand);
  border-bottom-color: var(--brand); outline: none;
}
/* The one the clerk is being sent to next. Exactly one at a time, so the mark
   still means what it says.

   On ANY cell, not only an unconfirmed one: the walk goes to questions first,
   and a question cell is `.asked`, so the mark it landed on had no rendering
   at all — the page scrolled and nothing said where to. A ring rather than a
   fill, because the cell's own colour is the thing being pointed AT and
   repainting it would replace the message with the pointer. */
.sv.current {
  outline: 2px solid var(--brand); outline-offset: 2px;
  border-radius: var(--r-sm); scroll-margin-block: 120px;
}
.sv.unconfirmed.current {
  background: var(--brand-soft); color: var(--brand);
  border-bottom: 2px solid var(--brand); font-weight: 600;
}
/* Both at once: still the outstanding colour, with the weight `.weighty`
   carries.
   The two never blend into a third colour nobody can name. */
.sv.unconfirmed.weighty { font-weight: 600; }

/* The third marker: something to KNOW about this value.
   Quietest of the four and, like the other three, a COLOUR — not a dot. The
   dot said "there is a note here" in a vocabulary nobody was taught, and it
   sat on the value while every other state sat on the label. One design. */
.sv.noted {
  cursor: pointer; color: var(--ink-2);
  text-decoration: underline dotted; text-underline-offset: 3px;
}
.sv.noted:hover, .sv.noted:focus-visible {
  color: var(--ink); background: var(--paper); outline: none;
}


/* A third state, and deliberately the quietest of the three: the declaration
   carries no value here at all. It is not pending (nobody will confirm it),
   it is not a question (the question, where there is one, sits on the box in
   its own colour). It is a hole in the form, and it has to LOOK like one —
   an em dash sitting in ordinary ink is indistinguishable from a value.
   Colour is not carrying this alone: the dash is the shape, and the title
   attribute says it in words. */
/* Measured, and the first attempt failed both ways: a diagonal hatch at
   1.05:1 against the paper is a smudge rather than a pattern, and the em dash
   left to carry the meaning alone came in at 3.04:1 — under the body floor,
   for a value rather than an ornament. The rest of the meaning was in a
   `title`, which is hover-only and therefore absent for touch and for the
   keyboard.
   So the state is carried in TYPE, the way the direction file already
   specifies for gaps: a named absence in grey italic, never an empty cell and
   never a mark only a mouse can interrogate. `--ink-2` clears 4.5:1 in both
   themes. */
/* PROSE, in the prose face. `.sv` is `--mono` tabular at value size and
   value weight — right for a code, wrong for a sentence, and a sentence set
   that way in a value slot reads as though the declaration literally carries
   the string "not on the declaration". The reference table already renders
   its own named gaps as 12px sans italic; this is the same statement and
   takes the same treatment. */
.sadbox .sv.missing, .sadbox .b31-source, .rows-table .sv.missing {
  font: italic 400 12px/1.4 var(--sans); font-variant-numeric: normal;
  color: var(--ink-2);
}
/* Box 31's stand-in while the item's code is a question: the invoice's own
   words for these goods, and beside them the phrase saying so. ONE voice for
   the pair, deliberately — the whole line is a named absence, and giving the
   note a second treatment would say the two halves are different kinds of
   thing when they are one sentence. `margin-inline-start` and not a space:
   they are separate flex items so that each keeps its own direction. */
/* One shade quieter than the words it names, so the eye can tell where
   the goods end — same face, same italic, no second voice. */
.sadbox .b31-source { margin-inline-start: 6px; color: var(--ink-3); }

.proposal {
  border: 1px solid var(--rule); border-inline-start: 3px solid var(--brand);
  border-radius: var(--r); padding: var(--s3) var(--s4);
  margin-top: var(--s3); display: grid; gap: 4px; background: var(--sunk);
}
.proposal .val { font-size: 17px; font-weight: 600; }
/* THE VALUE IS THE DRAWER'S ONE LOUD THING (2026-11-03, the drawer in plain
   words): with the eyebrow and the note gone from above and beside it, it is
   the first thing read and the thing approved, so it takes the compact
   heading's size, and its figures sit on tabular widths. (Aligned to the
   drawer's start in Arabic by the rule beside the fold's: a bare figure has
   no strong character, so `dir="auto"` resolves it LTR, and it hugged the
   far edge from the sentence under it.) */
.prov .proposal .val { font-size: 22px; font-variant-numeric: tabular-nums; }
.proposal .quiet { font-size: 12.5px; line-height: 1.45; }

/* THE OFFICE'S OWN ACCEPTED FILE, at the foot of the drawer.
   Built out of `.proposal`'s box rather than beside it, because that is what
   it is: something a source stated, shown so a person can weigh it. The edge
   is AMBER and not the brand navy — this is a question that stops nothing,
   and the wash on the cell it came from says the same. It sits LAST because
   the drawer's own question is the one somebody opened it to answer. */
.decided-by {
  border: 1px solid var(--rule); border-inline-start: 3px solid var(--hold);
  border-radius: var(--r); padding: var(--s3) var(--s4);
  margin-block-start: var(--s4); display: grid; gap: var(--s2);
  background: var(--sunk);
}

/* `hidden` is an attribute, not a suggestion. `.field { display: grid }` beats
   the browser's default `[hidden] { display: none }` on specificity, so the
   correction fields were on screen from the moment the panel opened — the
   two-press design silently became one press. Anything explicitly hidden stays
   hidden, whatever else it is. */
[hidden] { display: none !important; }

/* THE THIRD STATE. `red` is a field whose being wrong costs money,
   `unconfirmed` is a machine reading nobody has vouched for, and `asked` is a
   box carrying an open question. Three states, three names, three renderings —
   because approving a reading and answering a question are different acts and
   must not share an affordance. The reading is a tint; the question is a
   marker in the corner and a solid edge, so a box with a question on it reads
   as something to open rather than something to tick. */
/* RED IS "THIS STOPS THE FILE", AND NOTHING ELSE MAY WEAR IT.
   The review screen is the declaration and the invoice now, so the only way a
   blocking finding can be seen at all is on the value it is about. It gets the
   stop colour and an exclamation, because a person scanning two documents for
   what is wrong should find every one of them without reading a word. */
/* ONE TREATMENT PER STATE, AND IT IS A WASH (Ali, 2026-08-29, on a rendered
   mock: "perfect. we are aligned"). The bordered chip carried a `!` or a `?`
   in its corner — a second alphabet on top of a colour that already said the
   same thing, and a third look beside it for the trust tier. It is the soft
   fill and nothing else: the value's own text on its state's ground.

   IT WAS ONE COLOUR FOR A DAY (Ali, 2026-09-06: "do we need amber and red?
   I think not. The goal is a review page with almost zero questions"), and
   the reasoning still holds for most of what it collapsed: a screen where a
   third of the cells are red teaches people to read past red, and the
   difference between a held reading and a trust tier is the software's
   business.

   ONE DIFFERENCE IS THE PERSON'S (Ali, 2026-09-07, on job 4760-161): "There
   are 4 blocking questions I need to clear to proceed to the next step, but
   all questions are highlighted in amber. The blocking ones should be in red
   so I can easily relate which ones I need to answer to proceed. The ones
   that are not blocking stay amber." The bar names a NUMBER and the page did
   not name the CELLS — four counted, five amber washes, and no way to tell
   which four. So red is the question the file waits on, and it is exactly
   the set the bar counts: `state.blocks` minus `unconfirmed_reads`, the one
   list both halves read.

   The classes are unchanged and so are the severities: `asked` is still a
   blocking rule's cell, `held` is still two readings nobody settled, and
   `rulebook.py` still decides which is which. This is the paint.

   ORDER MATTERS. A cell can be both — blocked, and read two ways nobody
   settled — and the two rules carry the same specificity, so the cascade
   decides. The blocking one comes last: red wins, because the file waiting
   on a cell outranks a reading waiting on one.

   AND THE ONLY DIFFERENCE IS THE HUE. Red carried `font-weight: 600` before
   2026-09-06, on the reasoning that a stop should be the heaviest thing on
   the page; it does not carry it back. One treatment per state (Ali,
   2026-08-29) — the colour already says the whole of it, and a second axis
   beside it is the bordered chip's `!` in another spelling. Two states, two
   hues, one treatment.

   ONE EXCEPTION, AND IT IS ALI'S OWN (2026-09-20, after 4808). For nine
   seconds a held amber cell was one the software was at that moment asking
   the page about, and the cell said nothing: he read it as "this one is
   mine, and it is stuck", waited a minute, and went round by the dashboard.
   So a cell a focused re-read is working on wears the sheet's `.dots` while
   the pass runs, and stops the moment it returns.

   IT IS A SECOND AXIS ON A CELL, which is the thing the rule above removed,
   and he made the exception knowing that. What makes it affordable is that
   it spends NO COLOUR: `.dots i` is `background: currentColor`, so the mark
   takes the cell's own ink — amber in an amber cell, red in a red one — and
   the palette gains nothing. It is also the only state on this screen that
   ENDS BY ITSELF; every other one waits for a person, and a treatment that
   removes itself cannot accumulate.

   What was proposed and refused: `.pill.reading`'s amber, which resolves to
   the same two tokens as `.sv.held.unconfirmed` and would have painted
   nothing at all; and brand, which on an `.sv` is already spent on
   `.sv.current`. */
/* A FOURTH MEMBER, AND NO FOURTH COLOUR (2026-09-26). `.sv.contested` is a
   figure two PAPERS state differently — the packing list's count against the
   invoice's — where `.sv.held` is one two READERS state differently. Both are
   the same sentence to the person reading the screen: nothing the software did
   settled this, and it is yours to look at. So it joins this rule rather than
   spending a token, which is the 2026-09-16 "reuse, never a new colour" rule.

   `contested` and not `disagreed`, which is spent fourteen times across this
   sheet and app.js for the two-readings drawer; grepped before it was taken,
   because `.pair` cost a whole screen once (2026-09-07).

   WHAT SAYS WHICH is the caption beside it and the drawer behind it, never a
   second tint: the cell's own words name the two papers. */
/* `.sv.owed` (2026-10-03, the question-drawers review, 6.7): a permit or a
   supplementary unit waits on this item. Neither is counted on the bar, so
   neither may go red (contract rule 6); it is the attention tier's own amber,
   and it joins this rule rather than spending a token. */
/* `.sv.waiting` (2026-10-04, F7, 4789 = file 158): the papers claim a
   preference and this item's tariff line places it. Counted nowhere and
   stopping nothing — `hs_unresolved` already holds the file — so it is the
   attention tier's own amber and joins this rule rather than spending a
   token. The sixth class on one declaration, which is the point: rule 6
   leaves exactly one colour for a thing that stops nothing, and a cell that
   wears its own would be a legend to learn. */
.sv.held, .sv.trusted, .sv.wording, .sv.contested, .sv.owed, .sv.waiting {
  background: var(--hold-soft); color: var(--hold); cursor: pointer;
  border-radius: var(--r-sm); padding: 2px 7px;
}
.sv.asked {
  background: var(--stop-soft); color: var(--stop); cursor: pointer;
  border-radius: var(--r-sm); padding: 2px 7px;
}
.sv.held:hover, .sv.held:focus-visible,
.sv.owed:hover, .sv.owed:focus-visible,
.sv.trusted:hover, .sv.trusted:focus-visible,
.sv.contested:hover, .sv.contested:focus-visible,
.sv.wording:hover, .sv.wording:focus-visible,
.sv.waiting:hover, .sv.waiting:focus-visible {
  background: var(--hold); color: var(--brand-ink); outline: none;
}
/* `--stop-ink`, never a raw white and never `--brand-ink`: dark mode's
   `--stop` is a light salmon, and the ink written for that surface is the
   one that reads on it in both themes. */
.sv.asked:hover, .sv.asked:focus-visible {
  background: var(--stop); color: var(--stop-ink); outline: none;
}
/* The pending underline belongs to a value with no mark. Where one of the
   states applies, the wash IS the mark and the underline under it is the
   second look that change existed to remove. A box that is both unanswered
   and unvouched-for is a QUESTION first: approving the reading would not
   answer it. */
.sv.held.unconfirmed, .sv.trusted.unconfirmed, .sv.contested.unconfirmed,
.sv.owed.unconfirmed {
  background: var(--hold-soft); color: var(--hold); box-shadow: none;
}
.sv.asked.unconfirmed {
  background: var(--stop-soft); color: var(--stop); box-shadow: none;
}

/* THE ONE EXCEPTION, in the rules. See the paragraph above `.sv.held`: the
   mark is the sheet's own `.dots`, and `.dots i` is
   `background: currentColor`, so it takes whichever of the two hues the cell
   is already wearing and spends none of its own. */
.sv .dots { margin-inline-start: 5px; vertical-align: middle; }
.sv .dots i { width: 3px; height: 3px; }
/* The global reduced-motion rule stops the pulse at its last keyframe, which
   is `opacity: .25` — right for the pass lines, where the words beside them
   say the same thing, and wrong here, where the dots ARE the statement. */
@media (prefers-reduced-motion: reduce) { .sv .dots i { opacity: 1; } }

/* THE TRUST TINT IS GONE AS A LOOK OF ITS OWN (Ali, 2026-08-29: "the user
   does not need to know about our different categories"). It was a third
   treatment for a third internal category, next to red and amber, and a
   person reading the screen has no use for the distinction — a value
   standing on the vision reader's word alone is attention, not a stop,
   which is what amber already says. `.sv.trusted` is the ordinary amber
   chip above, and the ✓ beside it approves the reading. `.sv.unwitnessed`
   is deliberately left with no rule: the class may still ride the payload,
   and nothing paints it. */

/* A displayed PROPOSAL — the value a red or amber cell shows so the person
   sees the answer they are being offered, not a hole. NO DASH (Ali,
   2026-08-28): a proposal should look like the value it is about to become,
   and the cell's own red or amber is what already says nobody has settled it.
   The dash said the same thing a second time, in the one vocabulary this
   screen does not otherwise use. The class stays — `.sv.asked.proposed`
   below is what turns a red hole into an amber figure to confirm. */

/* THE ONE-PRESS SAVE. A small tick beside a red or amber value that records
   the shown proposal as this person's answer, with the reason prepared from
   the document it was read off. Its own control, outside the value span, so
   a mis-aim opens the question instead of answering it.

   Inline at the value's INLINE-END wherever the value is read down a column
   — the invoice table and the stated band — so it follows the figure into
   Arabic instead of sitting on a Latin value's left edge. */
button.qa {
  border: 0; background: none; padding: 0 2px; margin-inline-start: 6px;
  font: 800 16px/1 var(--sans); cursor: pointer; flex: none;
  vertical-align: -1px; color: var(--ink-3);
  transition: transform .1s;
}
/* THE TICK WEARS ITS VALUE'S COLOUR, so the pair reads as one thing rather
   than as a control that happened to land beside a figure. Two colours again
   since 2026-09-07: the ✓ beside a cell the file is waiting on is red, like
   the cell.

   AND IT IS READ OFF THE DOM, NOT PASSED IN. Until 2026-09-06 every call
   site named the value's state — `qaButton(path, asked ? "stop" : "hold")` —
   which is one place per site to get wrong, and the day those arguments were
   removed one `washTone` was left referenced: a ReferenceError at render,
   the question drawer unopenable, and 891 tests green. So the markup emits
   one class and the cascade decides, from the cell the tick sits beside
   (`.sv.asked + button.qa`), from the line it closes (`.vals`), from the box
   it corners (`.sadbox`) and from the row it belongs to. An ancestor cannot
   go stale. */
button.qa.hold { color: var(--hold); }
.sv.asked + button.qa,
.vals:has(.sv.asked) > button.qa,
.sadbox:has(.sv.asked) > button.qa,
.rows tr.pale.asked button.qa { color: var(--stop); }
button.qa:hover, button.qa:focus-visible {
  transform: scale(1.15); outline: none;
}
button.qa:focus-visible { text-decoration: underline; }

/* IN A SAD BOX IT SITS IN THE CORNER. The box is a framed field with a
   caption above a value, and a control trailing the value pushed the figure
   out of line with the boxes either side of it. Bottom, inline-end — so it
   is the same corner in both directions — clear of the caption and of a
   value that wraps, and one per box whatever the box holds. */
.sadbox > button.qa {
  position: absolute; margin: 0;
  inset-block-end: 4px; inset-inline-end: 4px;
}
.sadbox:has(> button.qa) .vals { padding-inline-end: 26px; }

/* THE SAME PRESS, OVER EVERY ROW THE PROPOSAL ANSWERS (spec L8). The corner
   tick's sibling: it wears the box's own state colour, and it carries words
   because a bulk act has to be readable before it is pressed — the count and
   the code, not a second glyph that means something else.

   Its own line at the foot of the box, inline-start, so the tick keeps its
   corner in both directions; the inline-end padding is that corner's, kept
   clear whatever the label's length. Quieter than the value it sits under:
   this is the exit, not the question. */
.sadbox > button.qa-all {
  justify-self: start; border: 0; background: none; cursor: pointer;
  padding: 2px 22px 0 0; padding-inline: 0 22px; margin: 0;
  font: 600 11.5px/1.35 var(--sans); text-align: start; color: var(--ink-3);
}
.sadbox > button.qa-all.hold { color: var(--hold); }
.sadbox:has(.sv.asked) > button.qa-all { color: var(--stop); }
.sadbox > button.qa-all:hover { text-decoration: underline; }

/* BOX 31 IS TWO LINES, EACH OWNING ITS TICK (ruled 2026-08-29). The corner
   position was for a box with one value; on two symmetrical lines a corner
   tick reads as belonging to the box — to neither line — so each line's
   tick sits INLINE at the end of its own line, in that line's own state
   colour. The lines stack; the wording line keeps its own direction (the
   bidi jumble this split removes), and its tick still follows the text at
   the line's inline end. */
.sadbox .b31-line { display: flex; align-items: baseline; flex-wrap: wrap; }
.sadbox .b31-line button.qa { position: static; }
.sadbox .b31-wording { margin-top: 2px; }

/* A BLOCKED CELL IS RED, PROPOSAL OR NOT (ruled 2026-08-29, on Ali's own
   sentence: "a blocking value in red, a non blocking value in amber", and
   the confirmed mock shows the blocked 209 in the red wash with a red tick).
   The 2026-08-28 downgrade — `.sv.asked.proposed` repainting a blocked
   proposal amber — is superseded: amber on a blocking cell tells a person
   nothing stops the file while the strip counts one they then cannot find.
   The proposal is the VALUE shown and the tick beside it; the colour stays
   the file's own state. */

/* A blank the office DECIDED to leave. An evidence pack where somebody forgot
   looks exactly like one where the office chose, so the cell says which — in
   the quiet treatment, because a standing decision is not work. */
.sv.by-decision { font-style: italic; color: var(--ink-3); }


/* ---------------------------------------------------------------------------
   Corrections from the design review
   --------------------------------------------------------------------------- */

/* ARABIC IS NOT A FALLBACK. The SAD value style is monospace, which is right
   for a tariff code and wrong for `محركات لا تزيد قدرتها عن 37.5 W` — Arabic
   fell into the mono stack, stretched its joins and read like terminal output
   on a Lebanese customs form. Half the paperwork in this country is Arabic.
   `dir: auto` lets each value govern its own direction, which is what stopped
   the wattage in box 31 being thrown to the far left, away from the phrase it
   qualifies. */
.sadbox .sv:lang(ar), .sadbox .sv[dir="auto"]:not(:lang(en)) {
  font-family: var(--ar);
}
.sadbox .vals { unicode-bidi: isolate; }
.sadbox .sv { unicode-bidi: isolate; }

/* FOUR STATES ON THREE COLOURS. `typed` shared `--hold` with `asked`, so a
   value somebody keyed looked like a question nobody had answered — and being
   colour-only it vanished entirely for a red-green reader. It is now a dotted
   underline, which is a mark rather than a hue, and `--hold` belongs to
   `asked` alone. */
.sadbox .sv.typed {
  color: var(--ink); background: none;
  border-bottom: 1px dotted var(--rule-strong);
}

/* FOCUS. Both confirm controls set `outline: none` and swapped to the hover
   fill, so a keyboard user working fifty confirmations could not tell where
   they were, nor focus from a passing mouse. The global focus ring applies. */
.sv.unconfirmed:focus-visible, .sv.asked:focus-visible,
.sadbox .sv[role="button"]:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}

/* THE TAP FLOOR. Every one of these spans is a control — it opens the
   confirm panel, a question, or the provenance chain — and at text height
   they measured 15-19px: the audit's 1,946 tap-target findings were almost
   entirely this one class of element. The hit area grows to 24px in both
   axes; the negative block margin hands the extra height back to the line
   so the form's rhythm does not change. */
.sv.unconfirmed, .sv.asked, .sadbox .sv[role="button"],
.rows-table .sv.unconfirmed, .rows-table .sv.asked {
  display: inline-flex; align-items: center;
  min-height: 24px; min-width: 24px;
  margin-block: -3px;
}

/* A box number a broker cannot read teaches them to look in the wrong place,
   which is the one thing §23 says this layout must not do. */
.sadbox .cap { color: var(--ink-2); }

/* The invoice rows, in the same three states as the form. */
table.rows { min-width: 720px; font-size: 13px; }
table.rows th { padding: 8px var(--s3); }
/* `.num` scales itself down for use inside 15px prose; on a header that is
   already at the 12px floor it landed at 11.375px (audit: tiny-text). */
th.num { font-size: 12px; }
table.rows td { padding: 7px var(--s3); vertical-align: top; }
table.rows td .sv { font: 500 12.5px var(--mono); }

/* EVERY COLUMN HEAD SORTS THE ROWS (2026-11-02, S4). The label becomes a
   real button — the `button.cap` and `.itemtrace` idiom: chrome stripped,
   everything typographic inherited from the head it sits in, so the heads
   read exactly as they did and are still the heads. There is no global
   button reset in this sheet (only `.btn` and `button.linkish`), so this one
   carries its own. */
table.rows th > button {
  appearance: none; background: none; border: 0; margin: 0; padding: 0;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  color: inherit; text-align: inherit;
  display: inline-flex; align-items: center; gap: var(--s1);
  min-height: 24px; cursor: pointer;
}
table.rows th > button:hover {
  color: var(--ink);
  text-decoration: underline; text-underline-offset: 3px;
}
/* THE COLUMN THAT ORDERS THE TABLE says so twice: in ink rather than the
   muted head colour, and with a small triangle at its inline end — up for
   ascending, down for descending. Ink, never a hue: the four hues each have
   one job on this screen and none of them is "sorted". Off `aria-sort`
   alone, so the mark and what a screen reader announces are one fact. */
table.rows th[aria-sort] > button { color: var(--ink); }
table.rows th[aria-sort] > button::after {
  content: "\25B4"; font-size: 1.1em; line-height: 1;
}
table.rows th[aria-sort="descending"] > button::after { content: "\25BE"; }

/* THE DECLARED CODE'S DOOR (2026-11-02, S4) — a settled value with no mark,
   the same quiet treatment the declaration's openable-but-unmarked value
   has (`.sadbox .sv[role="button"]`): the pointer and the brand wash on
   hover, no colour at rest. Scoped to `[data-settled]`, which a cell gets
   only where it wears no state, so it cannot outrank a state's own hover.
   The negative block margin is the tap floor's own (`.sv.unconfirmed` and
   its siblings above): the 24px target hands its extra height back to the
   line, or a row with a door stands taller than the rows around it. */
.rows-table .sv[data-settled]:not(.trusted) {
  cursor: pointer; margin-block: -3px;
}
.rows-table .sv[data-settled]:not(.trusted):hover,
.rows-table .sv[data-settled]:not(.trusted):focus-visible {
  background: var(--brand-soft);
}


/* The tariff's descriptions are Arabic in both language modes — all 6,132
   loaded lines carry Arabic and none carries English. A candidate list is the
   one place a broker reads them side by side, so they are set as Arabic and
   isolated, not left to inherit a Latin measure. */
.opt .ar, .groups .ar {
  font-family: var(--ar); unicode-bidi: isolate; line-height: 1.6;
}
.opt .duty.dear { color: var(--ink); font-weight: 600; }


/* A question about a value the declaration does not carry has no box to sit
   on — `supp_unit_unknown` is exactly a question about an empty one. It rides
   on the item instead of falling to the bottom of the page: "item 5" still
   answers "where does this belong", and a detached list does not. */
.saditem .itemhead {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); padding: 0 0 var(--s2);
}
/* THE ITEM'S NAME IS THE CONTROL that shows which invoice rows it is made of
   (Ali, 2026-09-23). Same idiom as `button.cap` — a label turned into a real
   button, chrome stripped, the type inherited from the class beside it — so
   there is one thing to learn about a clickable label on this screen and not
   two. Its `.eyebrow` sibling supplies the face, size and tracking in both
   directions; nothing here touches them. */
/* CHROME ONLY. The face, the size, the tracking, the uppercase and the ink
   all come from `.eyebrow` on the same element, and a `font: inherit` here
   would quietly cancel them — "ITEM 3" became "Item 3" and the item stopped
   looking like the labels around it, which is the sort of thing only a
   render shows. */
.saditem .itemhead > button.itemtrace {
  appearance: none; background: none; border: 0; padding: 2px 6px;
  margin-inline-start: -6px; border-radius: var(--r-sm);
  cursor: pointer; min-height: 24px;
}
/* The screen's existing "this label is clickable" hover, taken verbatim off
   `.sadbox .sv[role="button"]` two hundred lines up. Neutral: the state
   colours are red and amber and both are spoken for. */
.saditem .itemhead > button.itemtrace:hover,
.saditem .itemhead > button.itemtrace:focus-visible {
  background: var(--brand-soft); color: var(--brand);
}
/* AND THE ITEM SHOWING ITS ROWS STAYS MARKED. The invoice sits ABOVE the
   declaration on this screen, so the rows and the item they belong to are
   rarely both in view: without a mark down here a person who scrolls up to
   read the rows has nothing to scroll back to. Persistent, because nothing
   clears on a timer. */
.saditem.tracing > .itemhead > button.itemtrace {
  background: var(--brand-soft); color: var(--brand);
}
/* The box itself, one step quieter than the name: the existing hairline
   moves from the grid's rule to brand. No fill — the fill is upstairs, on
   the rows, and two washes for one selection would make the screen argue
   about which half of it the person asked for. */
.saditem.tracing .sadgrid { border-color: var(--brand); }
/* ONE ANSWER-PILL, TWO COLOURS. The grouped card's press is this shape (see
   `.groups .group > button.qa-all`), so the geometry lives here once: a
   second set of values for one control is how a screen ends up with four
   spellings of "answer this". */
.scopeq, .groups .group > button.qa-all {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  border-radius: var(--r-full);
  padding: 3px 12px; font: 600 12px/1.5 var(--sans);
}
.scopeq {
  border: 1px solid var(--hold); background: var(--hold-soft);
  color: var(--hold);
}
.scopeq:hover, .scopeq:focus-visible { background: var(--hold); color: var(--hold-ink);
                                       outline: none; }


/* ---------------------------------------------------------------------------
   Corrections from the design review, 2026-08-15
   ---------------------------------------------------------------------------
   The scarcity rule was being obeyed in the token file and broken on the
   rendered page. Counting saturated pixels: every red pixel on the screen was
   banner chrome and not one was a value, and a 42-cell table with no questions
   in it carried more amber than both questions combined. The discipline has to
   hold at page scale or it is decoration. */

/* The progress banner states a fact; it is not an alarm. Red belongs to the
   one value a person is being sent to. */
.callout.progress {
  background: var(--surface); border-color: var(--rule-strong);
  color: var(--ink-2);
}
.callout.progress .icon { color: var(--ink-3); }
.callout.progress b { color: var(--ink); }
.callout.progress .count { font-family: var(--mono); font-weight: 600;
                           color: var(--ink); }

/* The one forward action on the screen was its weakest control — a hairline
   button on a tinted panel, quieter than the sentence beside it. */
.callout.progress .btn { background: var(--ink); border-color: var(--ink);
                         color: var(--paper); min-height: 36px;
                         font-weight: 600; }
.callout.progress .btn:hover { background: var(--ink-2); border-color: var(--ink-2); }

/* A pill is the only way into a question that has no box, so it has to look
   like a way in. It was an outline with a label crammed at one end. */
.scopeq, .groups .group > button.qa-all { min-height: 26px; }
.scopeq::after { content: "›"; font-weight: 700; opacity: .8; }
.sad > .scopeq { align-self: start; }

/* Every cell in the transcription table is unconfirmed, so a per-cell mark
   discriminates nothing and merely makes the table the loudest region on the
   page. Say it once, above the table. */
.rows-table .sv.unconfirmed {
  background: none; box-shadow: none; padding: 0;
  border-bottom: 1px dotted var(--rule-strong);
}

/* Numbers in a column are read by their decimal point. These three are the
   literal task the caption sets — "check them against the invoice in front of
   you" — and they were ragged-right. */
.rows-table td.num, .rows-table th.num {
  text-align: end; font-variant-numeric: tabular-nums;
}

/* Empty grid cells were a darker grey than the table header band and read as a
   header row that had lost its labels. Nothing is not a thing. */
.sadbox.empty, .sadgrid > .filler {
  background: transparent; border-color: var(--rule);
}

/* Named for a screen reader, absent for everyone else.
   Deliberately NOT position:absolute. An absolutely-positioned child escapes
   its scroll container's clipping, so this label — inside a table that scrolls
   sideways inside a card — sat at x=690 and made the whole document scroll,
   which is the defect it was added to help fix. In flow at 1px it cannot. */
.sr-only {
  display: inline-block; width: 1px; height: 1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* A table cell link is a tap target like any other, in BOTH dimensions. The
   height was already raised; a short job reference is still only ~22px wide,
   and a 22px target is a target a thumb misses. The padding is pulled back out
   with a negative margin so the column keeps its alignment — the hit area
   grows, the text does not move. */
td a {
  display: inline-flex; align-items: center;
  min-height: 24px; min-width: 24px;
  padding-inline: 6px; margin-inline: -6px;
}


/* ---------------------------------------------------------------------------
   What the documents state
   ---------------------------------------------------------------------------
   The band under the invoice rows. These are the figures the paperwork itself
   carries — the cost line, the discount, the packing list's totals, the bill
   of lading's number — and the declaration draws a box for almost none of
   them, which is why every cross-document check used to end up in a list at
   the foot of the page instead of on the value it is about.

   A definition list in three columns, not a table: there is one value per row
   and a table header over a single column is furniture. */
.stated {
  display: grid; gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--r-sm);
  overflow: hidden;
}
.statedrow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: baseline; gap: var(--s2) var(--s3);
  background: var(--paper); padding: var(--s2) var(--s3);
}
.statedrow .lb { color: var(--ink-2); font-size: 13px; min-width: 0; }
/* The figure and its tick are ONE cell of the three. As two grid children the
   "from" column wrapped to a second row the moment a proposal appeared. */
.statedrow .val {
  display: inline-flex; align-items: center; justify-self: end; min-width: 0;
}
/* Read down the column by the decimal point, which is the whole task. */
.statedrow .sv {
  font: 500 13px/1.4 var(--mono); font-variant-numeric: tabular-nums;
  justify-self: end;
}
/* 12px is the floor the audit holds every surface to, and this line
   names the document a figure came off — which is the half of the row a
   person is checking when the totals do not close. */
.statedrow .from { font-size: 12.5px; white-space: nowrap; }
/* The figure a document that is not in the set would have supplied. The row
   is drawn anyway: an absent document that says nothing looks exactly like
   one nobody assigned, and the totals it owes are the reason the checks
   cannot run. */
.statedrow.absent { background: var(--sunk); }
.statedrow.absent .sv { font-style: italic; color: var(--ink-3); }
@media (max-width: 540px) {
  .statedrow { grid-template-columns: minmax(0, 1fr) auto; }
  .statedrow .from { grid-column: 1 / -1; }
}

/* The two readings, to choose between. Each is a real option with the reader
   that printed it, and the printed text keeps its own line breaks — they are
   often WHY the two readers disagreed, so flattening them hides the evidence
   the person is being asked to weigh. */
.held-picks { display: grid; gap: var(--s2); }
.held-picks .opt {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: center; gap: var(--s3);
}
.held-picks .f-held-text {
  font: 500 13px/1.4 var(--mono); white-space: pre-wrap; min-width: 0;
  overflow-wrap: anywhere;
}
/* One option, one line: the value and its source read together. The .opt
   grid is radio | content, so an option's parts live inside one flex span —
   siblings would each take a grid row and the source ends up under the
   radio (seen on the first cut of the packaging options). */
.held-picks .opt-line {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 4px 8px; min-width: 0;
}
.held-picks input[name="value"] { min-width: 0; }
/* AN OPTION'S OWN SECOND LINE: the deduction's rationale (C5), and what
   taking a row does and does not do. It used to be a callout ABOVE the
   list, repeating the figure that was already the pre-selected option;
   here it is the second line of that option and nothing else — inside the
   option's own border, which is what says it belongs to the figure.

   `grid-column: 1 / -1`, and the cure was already written down twenty
   lines above: an option is a two-column GRID of radio | content in both
   containers that draw one, and "siblings would each take a grid row and
   the source ends up under the radio". A third child without this lands in
   column ONE of row two, so the column widens to fit a sentence and the
   label beside the radio is squeezed to a single character. That is what
   it did, in both languages, and no test could see it — only the rendered
   page could. The flex pair is the fallback for any option that is still
   a flex row.

   Full width rather than an indent to the content column: a computed
   indent is a guess about the radio's width, and it mirrors wrong. */
.opt-why {
  grid-column: 1 / -1; flex: 0 0 100%;
  margin: 4px 0 0; font-size: 12.5px; line-height: 1.45;
}

/* Every marked value on the review screen is a CONTROL, and a control a
   person taps has to be big enough to hit. A table cell's text is 14px tall,
   so the span inside it was a 55x14 target — under the 24x24 floor, on a
   screen whose whole purpose is being worked through by hand.

   Padding rather than height, so the row rhythm is unchanged and the number
   still sits on its own baseline for reading down the column. */
.rows-table .sv[role="button"],
.statedrow .sv[role="button"],
.sadbox .sv[role="button"] {
  display: inline-flex; align-items: center;
  min-height: 24px; padding-block: 4px;
}

/* A BAND VALUE A PERSON RECORDED, with no mark of its own (2026-09-23).
   Every band cell that was clickable before this carried a state class that
   supplied the pointer and the hover — `.sv.noted`, `.sv.asked`,
   `.sv.unconfirmed`. This one carries none of them: it is a figure somebody
   typed, nothing is waiting on it, and it wears no colour by design. Left as
   it was it would be a control that does not say it is one, in both themes.

   The same quiet treatment the declaration's openable-but-unmarked value has
   had since 2026-09-08 (`.sadbox .sv[role="button"]`), and written with its
   own `:not()` chain rather than by joining that selector: `.statedrow` cells
   DO wear state classes, and a rule of this specificity added to the
   `.sadbox` list would outrank `.sv.noted:hover` and `.sv.unconfirmed:hover`
   and wash both in the brand tint — which is the collision the paragraph
   above `.sadbox .sv[role="button"]:hover` was written for.

   NO FOCUS RULE HERE, and the omission is the point. The review asked for
   one beside the `.sadbox` ring at the top of this file; executed, the
   GLOBAL `:focus-visible` at the head of this stylesheet already paints this
   cell — the `.sadbox` and `.sv.asked` rules exist only because those states
   set `outline: none` of their own, and this state sets nothing. A rule
   added here would be a guard in the diff refusing nothing. The browser pin
   asserts the ring is THERE rather than asserting which rule drew it, so an
   `outline: none` added to this state later still goes red. */
.statedrow .sv[role="button"]:not(.asked):not(.held):not(.trusted):not(.noted):not(.unconfirmed) {
  cursor: pointer;
}
.statedrow .sv[role="button"]:not(.asked):not(.held):not(.trusted):not(.noted):not(.unconfirmed):hover,
.statedrow .sv[role="button"]:not(.asked):not(.held):not(.trusted):not(.noted):not(.unconfirmed):focus-visible {
  background: var(--brand-soft);
}

/* A judgement's answers, as answers. Each one is a sentence a person can
   agree with, on its own line, big enough to hit — not a code they have to
   have been told. */
.options .opt.pick {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: start; gap: var(--s3); padding: var(--s3);
  min-height: 44px; cursor: pointer;
}
.options .opt.pick span { line-height: 1.45; }
.options .opt.pick:hover { background: var(--sunk); }
.options .opt.pick:has(input:checked) {
  background: var(--brand-soft); box-shadow: inset 0 0 0 1px var(--brand);
}

/* ---------------------------------------------------------------------------
   Sign in — the one screen reachable without a session, and the first thing
   the office sees every morning. It is a card in the middle of the page and
   not a form bolted to the top-left corner: an input stretched across 1400px
   is unreadable at any width, and the eye has nowhere to start.
   --------------------------------------------------------------------------- */
body.signin-page {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: var(--s5);
  background: var(--paper);
}
.signin {
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  padding: var(--s6) var(--s5);
}
.signin h1 { margin: 0 0 var(--s1); font-size: 22px; letter-spacing: -0.01em; }
.signin .lede { margin: 0 0 var(--s5); color: var(--ink-soft); font-size: 14px; }
.signin .field { margin-bottom: var(--s4); }
.signin .field label {
  display: block; margin-bottom: var(--s2);
  font-size: 13px; font-weight: 600;
}
.signin input {
  width: 100%; box-sizing: border-box;
  padding: 10px var(--s3);
  border: 1px solid var(--rule-strong); border-radius: var(--r);
  background: var(--paper); color: var(--ink);
  font: inherit; font-size: 15px;
}
.signin input:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 1px;
  border-color: var(--brand);
}
.signin button[type="submit"] { width: 100%; justify-content: center; }
/* The failure sits ABOVE the button, where the eye already is after typing —
   not below it, where a person who has just pressed Enter never looks. */
.signin .callout.stop { margin: 0 0 var(--s4); font-size: 13.5px; }
.signin .lang {
  display: inline-block; margin-top: var(--s5);
  font-size: 13px; color: var(--ink-soft);
}

/* The testing-mode strip. Deliberately loud and at the very top of every
   screen: a quiet developer switch is one somebody does three hours of real
   work under. */
.devmode-strip {
  background: var(--hold); color: var(--hold-ink);
  padding: var(--s2) var(--s4);
  font-size: 13px; font-weight: 600; text-align: center;
  letter-spacing: 0.01em;
}

/* The testing-mode panel while it is on. Same amber as the strip at the top
   of the page, so the two read as one state rather than two notices. */
.card.devmode-on { border-color: var(--hold); }
.card.devmode-on .head { background: var(--hold-soft); }

/* THE CONSENT, under the two documents it is about. IN FLOW, deliberately:
   the 2026-08-22 capture found the revoke panel absolutely positioned inside
   a `.card`, which is `overflow: clip`, so it was cut off at the card's edge
   and unreachable in every state and both languages while twelve screenshots
   showed the section as correct. Nothing here is positioned and nothing here
   is behind a disclosure.

   STACKED, not a row. The old panel was one field and one button per
   document, which a flex row suited; this is a sentence a person reads before
   they act, and a sentence beside a button reads as a caption for it. */
.approve { display: grid; gap: var(--s3); margin: var(--s5) 0 0; }
.approve form { display: grid; gap: var(--s3); justify-items: start; }
/* The tick and its sentence are ONE control: the label wraps both, so the
   words are the hit area as well. A 16px box alone is the smallest target on
   the screen carrying the largest claim. `align-items: start` keeps the box
   on the sentence's first line when it wraps, which it does in both scripts;
   the measure is capped because this is the one paragraph here that must be
   read rather than scanned. */
.approve .tick {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s3); align-items: start; cursor: pointer; max-width: 62ch;
}
.approve .tick input {
  width: 18px; height: 18px; margin: 1px 0 0; accent-color: var(--ink);
}
.approve .tick span { font-size: 14.5px; line-height: 1.5; color: var(--ink); }
/* `.approve .field` and `.approve .amberline` went with the note box and the
   amber tally (2026-08-28): the panel is one sentence and one button now. */

/* DELETING A SHIPMENT, from its own row on the main page. Owner only, and the
   server decides whether the control renders at all.

   SAME FOOTPRINT IN EVERY STATE, which is the whole of the layout brief. The
   first version was a `<details>` holding a paragraph, a required reason box
   and a red button: opened, it quadrupled the row's height and shoved every
   row below it down the page, and its "Not deletable" label ran under the
   Resume button on the row above. A destructive control must not move the
   page while somebody is aiming at it.

   Spend the boldness in ONE place. The bin is ghost and quiet until it is
   used; the confirm is the only red on the row, and it names the job number
   so a mis-aim is visible before the click rather than after it. */
/* WHAT THIS SHIPMENT COST. Narrow on purpose: the ordinary cell is one mono
   figure, and the rare "at least $0.061" — a model this office has no price
   for — wraps to two lines rather than widening the table past its card. Its
   `.pair` spans are what stop that wrap landing between "at" and "least". */
/* THE DASHBOARD'S TWO LISTS. Real hrefs, one per list, and `aria-current`
   carries the state a screen reader cannot see: the same choice the Settings
   rail made, and for the same reasons — linkable, capturable, and correct
   with no JavaScript at all. Deliberately smaller and quieter than that rail:
   this is a filter over one table, not the navigation of a whole surface. */
.listfilter {
  display: flex; gap: var(--s1); flex-wrap: wrap;
  margin-block: 0 var(--s4);
}
.listtab {
  display: inline-flex; align-items: center; min-height: 30px;
  padding: var(--s1) var(--s3); border-radius: var(--r);
  color: var(--ink-2); text-decoration: none; font-size: 13.5px;
  font-weight: 500; border: 1px solid transparent;
}
.listtab:hover { background: var(--sunk); color: var(--ink); }
.listtab:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.listtab.on {
  background: var(--surface); color: var(--ink);
  border-color: var(--rule-strong); font-weight: 600;
}
/* A ROW THAT IS NOT A SHIPMENT. One of the office's own accepted files, read
   back for comparison — never released, never filed, never counted. Quiet
   ink, no pill: the words in the status column say what it is, and a second
   mark would be the vocabulary this screen spends on shipments applied to
   something that is not one. */
.shipments tr.record td { color: var(--ink-2); }
.shipments tr.record td.c-job a { color: var(--ink-2); }

.shipments .c-cost { width: 76px; }

.c-act { display: flex; gap: var(--s2); align-items: center;
         justify-content: flex-end; white-space: nowrap; }
.del { display: inline-flex; gap: var(--s2); align-items: center; }
.del-ask { display: inline-flex; gap: var(--s2); align-items: center; }
.del-ask[hidden], .del [data-del-start][hidden] { display: none; }
/* Square, so the bin reads as an icon rather than a button that lost its
   label. `.btn` sets its own horizontal padding for text. */
.btn.ico { padding-inline: 7px; }
.btn.ico:disabled { opacity: .35; cursor: not-allowed; }
/* Red on the confirm and nowhere else. THE PALETTE IS ALREADY DECIDED: red
   means the file is stopped, and this is the one control in the software that
   destroys a record — the nearest thing to a stop the dashboard has. */
.btn.stop { background: var(--stop); border-color: var(--stop); color: #fff; }
.btn.stop:hover { filter: brightness(1.06); }
/* The quality floor, not announced: the focus ring is visible on the red
   button too, where the default border-coloured ring would vanish into it. */
.btn.stop:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* On a phone the table stacks into cards, where the grid puts the date and the
   actions on ONE row (`"created act"`). Armed, the confirm pair squeezed the
   date and wrapped the importer's name to three lines to make room for a
   button that was still cramped — so an armed row takes the full card width
   and drops to its own line instead.
   640 deliberately: the same breakpoint the card layout itself uses. A second
   breakpoint eight pixels away is two layouts nobody can hold in their head. */
@media (max-width: 640px) {
  .c-act { flex-wrap: wrap; }
  .shipments tr:has(.del-ask:not([hidden])) td.c-act {
    /* `grid-area: auto` FIRST, and it is the whole fix: `grid-column: 1/-1`
       alone left the row line at the named `act` area, so the cell widened
       across the card and sat ON TOP of the date rather than under it. The
       named area has to be released before the column span means anything. */
    grid-area: auto; grid-column: 1 / -1;
    justify-self: stretch; justify-content: flex-start;
    margin-top: var(--s2);
  }
}

/* A ROLE-LOCKED OPTION stays visible and says who can — never absent, never
   live. Found on the 4844 walk: the clerk got a live valuation form and the
   press came back as a raw role refusal. */
.opt.pick.locked { opacity: .55; cursor: not-allowed; }
.opt.pick.locked input { cursor: not-allowed; }
