/* 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; }

/* ---------------------------------------------------------------------------
   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;
}

/* 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. */
.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);
}
/* 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 a marker on the label, which
   is what the declaration's own boxes use. 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. */
.newclient .req {
  color: var(--stop); font-weight: 700; margin-inline-start: 3px;
}
/* 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; }
.pill.stop, .pill.held, .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.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);
}
.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;
}
.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 li.done { color: var(--ink-2); }
.steps li.done .n {
  background: var(--clear-soft); border-color: var(--clear); color: var(--clear);
}
.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); }
.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 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);
}
.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. */
.f-held-text {
  font-size: 14px; color: var(--ink-2); white-space: pre-line;
  overflow-wrap: anywhere; min-width: 0;
}
.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); background: var(--sunk);
}

/* 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; }

.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); }
.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); }
.opt .duty {
  margin-inline-start: auto; font: 500 13px var(--mono);
  font-variant-numeric: tabular-nums;
}
.opt .duty.dear { color: var(--stop); }

/* ---------------------------------------------------------------------------
   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; }
.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;
}
.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); }

.chain {
  border-inline-start: 2px solid var(--rule); padding-inline-start: var(--s4);
  padding-block: 2px; margin-bottom: var(--s3);
}
.chain .src {
  font: 500 12px/1.6 var(--mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3);
}
.chain .val { font: 600 15px var(--mono); font-variant-numeric: tabular-nums; }
.chain img {
  max-width: 100%; border: 1px solid var(--rule); border-radius: var(--r-sm);
  margin-top: var(--s2); display: block; background: #fff;
}
.cand {
  display: flex; gap: var(--s2); align-items: baseline; font-size: 13px;
  padding: 5px 0; border-bottom: 1px dashed var(--rule);
}
.cand .won { color: var(--clear); font-weight: 600; }
.cand .nobox { color: var(--ink-3); font-size: 12px; margin-inline-start: auto; }

/* ---------------------------------------------------------------------------
   Evidence
   --------------------------------------------------------------------------- */
.evidence-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s4); align-items: start;
}
#element-list { max-height: 74vh; overflow: auto; }

/* The declaration in reading order. A value is a button, because every one of
   them opens the chain that explains it. */
.declaration { display: grid; gap: var(--s5); padding: var(--s4) var(--s5); }
.dgroup { display: grid; gap: var(--s2); }
.dcells {
  display: grid; gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--r-sm); overflow: hidden;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}
.el {
  display: grid; gap: 2px; width: 100%; padding: 8px var(--s3);
  border: 0; background: var(--surface); color: inherit; font: inherit;
  cursor: pointer; text-align: start; align-content: start;
}
.el:hover, .el:focus-visible { background: var(--brand-soft); }
.el.on { background: var(--brand-soft); box-shadow: inset 2px 0 0 var(--brand); }
html[dir="rtl"] .el.on { box-shadow: inset -2px 0 0 var(--brand); }
.el .lb { font-size: 12px; color: var(--ink-3); }
.el .v {
  font: 500 13px var(--mono); font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
/* The fields compare.py grades RED: a difference here is a rejected
   declaration, so they carry a mark the eye finds without reading. */
.el.weighty .lb { color: var(--ink-2); font-weight: 600; }
.el.weighty .v { font-size: 14px; }
.el .src {
  font-size: 12px; letter-spacing: .04em; color: var(--ink-2);
  text-transform: uppercase;
}
/* Read, computed and standing are the expected cases and stay quiet — most of
   a declaration is one of the three, and colouring them all colours nothing.
   A TYPED value is the one no document carried and no rule produced, and an
   unexplained one should not exist at all. */
.el .src.input { color: var(--hold); font-weight: 600; }
.el .src.missing { color: var(--stop); font-weight: 600; }

.ditem {
  border: 1px solid var(--rule); border-radius: var(--r);
  background: var(--surface); overflow: hidden;
}
.dhead {
  display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap;
  padding: var(--s3) var(--s4); background: var(--sunk);
  border-bottom: 1px solid var(--rule);
}
.dhead .n {
  font: 500 12px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3);
}
.dhead b { font-size: 16px; }
.dhead .goods {
  font-size: 12.5px; color: var(--ink-2); flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* The description decides its own direction, so the RTL page's ellipsis
     stops landing mid-string on an English run (design review M4). */
  unicode-bidi: plaintext;
}
.ditem .dcells { border: 0; border-radius: 0; }
.ditem details { border-top: 1px solid var(--rule); }
.ditem summary {
  padding: 8px var(--s4); font-size: 12.5px; color: var(--ink-2);
  cursor: pointer; user-select: none;
}
.ditem summary:hover { color: var(--brand); }

/* ---------------------------------------------------------------------------
   Toasts. Errors say what happened; they do not apologise.
   --------------------------------------------------------------------------- */
#toasts {
  position: fixed; inset-block-end: var(--s5); inset-inline-start: 50%;
  transform: translateX(-50%); 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); }
  .evidence-grid { grid-template-columns: 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;
    grid-template-areas: "job status" "client blocks" "created 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-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); }


/* ---------------------------------------------------------------------------
   The declaration viewer
   ---------------------------------------------------------------------------
   A declaration nobody can look at is a declaration asked to be trusted, which
   is the opposite of what this product is for. The bytes that will be imported
   are on screen, and a line carrying a value is a link back to the page crop it
   was read from — the same drawer the review step opens.
   Highlighting is three colours and no more: tag, attribute, value. A rainbow
   here would spend the palette's scarcity on decoration. */
/* ---------------------------------------------------------------------------
   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. */
.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;
}
/* One colour per state, and the same four everywhere on this screen: red
   stops the file, amber means the document was read more than one way, the
   neutral tint means nobody has put their name to it yet, and the quietest
   means there is something to know and nothing to do.

   WHERE the colour sits differs by what the half of the screen IS. The
   declaration is a form of boxes, so the box's caption carries it — a box may
   have no value to paint, and the currency that stops a shipment stops it
   BECAUSE the box is empty. The invoice is a list of figures with no label
   per cell, so there the figure carries it, in the boxed treatment below.
   Two placements, one vocabulary; the alternative is a screen where the same
   red means different things eight rows apart.

   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, and it sat
   on the value rather than on the label like every other state. */
button.cap.asked { color: var(--stop); font-weight: 600; }
button.cap.held { color: var(--hold); font-weight: 600; }
button.cap.unconfirmed { color: var(--ink-2); }
button.cap.noted { color: var(--ink-2); }
button.cap.asked::after { content: "!"; font-weight: 700; }
button.cap.held::after { content: "?"; font-weight: 700; }
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 itself takes the edge, so a marked box is findable at page scale
   without every value inside it shouting. */
.sadbox.marked:has(.cap.asked) { box-shadow: 0 0 0 1px var(--stop); }
.sadbox.marked:has(.cap.held) { box-shadow: 0 0 0 1px var(--hold); }

/* 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; }
.sadbox .sv[role="button"]:hover, .sadbox .sv[role="button"]:focus-visible {
  background: var(--brand-soft);
}
.sadbox .vals { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); }
/* 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); }
.sadbox.blank .sv { 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); }

.viewer-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); margin: var(--s5) 0 var(--s2); flex-wrap: wrap;
}
.viewer-head input { width: 200px; min-height: 32px; padding: 5px 10px;
                     font-size: 13px; }

.viewer {
  border: 1px solid var(--rule); border-radius: var(--r);
  background: var(--sunk); max-height: 60vh; overflow: auto;
  padding: var(--s2) 0; font-variant-numeric: tabular-nums;
  /* THE FILE IS LTR, IN BOTH LANGUAGES (design review C1). Inheriting the
     Arabic page's direction let the bidi algorithm rewrite lines: a
     self-closing tag rendered as a closing one, the prolog scrambled, and
     the pane showed syntax that is not in the file — under a caption that
     says these are the bytes that will be imported. The chrome above stays
     RTL; the bytes do not bend. */
  direction: ltr;
}
/* 24px line boxes: every provenance-carrying line is a control, and a 20px
   row is a target a thumb misses (the tap-target floor is 24px). Uniform on
   all lines so the pane keeps one rhythm. */
.viewer .ln { display: flex; gap: var(--s3); align-items: baseline; }
.viewer .ln > code {
  font: 400 12.5px/24px var(--mono); white-space: pre; color: var(--ink-2);
  padding-inline-end: var(--s4);
}
.viewer .no {
  flex: none; width: 44px; text-align: end; user-select: none;
  /* 12px, and no opacity: 11px is under the readable floor, and the .7
     alpha blended the ink-3 token below 4.5:1 against the sunk band — the
     single largest contrast failure in the audit (719 nodes per shot). */
  font: 400 12px/24px var(--mono); color: var(--ink-3);
}
.viewer .t { color: var(--ink-3); }
.viewer .a { color: var(--hold); }
.viewer .v { color: var(--ink); font-weight: 600; }

/* Only the lines that can explain themselves invite a click. */
.viewer .ln.has-prov { cursor: pointer; }
.viewer .ln.has-prov:hover, .viewer .ln.has-prov:focus-visible {
  background: var(--brand-soft); outline: none;
}
.viewer .ln.has-prov:hover .v, .viewer .ln.has-prov:focus-visible .v {
  color: var(--brand); text-decoration: underline; text-underline-offset: 2px;
}


/* ---------------------------------------------------------------------------
   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, .rows-table .sv.missing {
  font: italic 400 12px/1.4 var(--sans); font-variant-numeric: normal;
  color: var(--ink-2);
}

.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; }
.proposal .quiet { font-size: 12.5px; line-height: 1.45; }


/* `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. */
.sv.asked {
  background: var(--stop-soft); color: var(--stop); cursor: pointer;
  border: 1px solid var(--stop); border-radius: var(--r-sm);
  /* Logical, not physical: the marker sits at inline-end, so the wide
     padding must follow it into RTL rather than staying on the right. */
  padding-block: 1px; padding-inline: 5px 18px;
  font-weight: 600; position: relative;
}
.sv.asked::after {
  content: "!"; position: absolute; inset-inline-end: 6px; top: 50%;
  transform: translateY(-50%); font: 700 11px/1 var(--mono); opacity: .9;
}
.sv.asked:hover, .sv.asked:focus-visible {
  background: var(--stop); color: var(--brand-ink); outline: none;
}
/* A box that is both unanswered and unvouched-for is a QUESTION first:
   approving the reading would not answer it. */
.sv.asked.unconfirmed { background: var(--stop-soft); color: var(--stop);
                        border-color: var(--stop); }
.sadbox:has(.sv.asked) { border-color: var(--stop); }

/* AMBER IS "THE TWO READERS DISAGREED".
   A different sentence from red and therefore a different colour. Red says the
   file cannot be built; amber says nothing decided this value and a person has
   to choose between two readings — which is work, not a fault. The question
   mark is literal: the panel behind it shows what each reader printed, and
   taking one is recorded as that PERSON'S decision, never as a reading. */
.sv.held {
  background: var(--hold-soft); color: var(--hold); cursor: pointer;
  border: 1px solid var(--hold); border-radius: var(--r-sm);
  padding-block: 1px; padding-inline: 5px 18px;
  font-weight: 600; position: relative;
}
.sv.held::after {
  content: "?"; position: absolute; inset-inline-end: 5px; top: 50%;
  transform: translateY(-50%); font: 700 11px/1 var(--mono); opacity: .9;
}
.sv.held:hover, .sv.held:focus-visible {
  background: var(--hold); color: var(--brand-ink); outline: none;
}
.sadbox:has(.sv.held) { border-color: var(--hold); }

/* 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); }


/* 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 { font-family: var(--ar); unicode-bidi: isolate; line-height: 1.6; }
.opt .duty.dear { color: var(--stop); 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);
}
.scopeq {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  border: 1px solid var(--hold); background: var(--hold-soft);
  color: var(--hold); border-radius: var(--r-full);
  padding: 3px 12px; font: 600 12px/1.5 var(--sans);
}
.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 { 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; }
/* 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;
}
.held-picks input[name="value"] { min-width: 0; }

/* 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;
}

/* The statute, behind one line. A finding's `detail` is where the legal
   reasoning lives — Arts. 421/425, Art. 98(1) — and it is what a broker needs
   when they go looking for it, which is not the same as always. A clerk
   answering the question needs the sentence above it and the box below. */
.f-why > summary {
  cursor: pointer; list-style: none; font-size: 13px; color: var(--ink-2);
  padding: var(--s2) 0; min-height: 24px; display: flex; align-items: center;
  gap: 6px;
}
.f-why > summary::-webkit-details-marker { display: none; }
.f-why > summary::before {
  content: "\25B8"; color: var(--ink-3); transition: transform .12s;
}
.f-why[open] > summary::before { transform: rotate(90deg); }
html[dir="rtl"] .f-why > summary::before { transform: rotate(180deg); }
html[dir="rtl"] .f-why[open] > summary::before { transform: rotate(90deg); }
.f-why > summary:hover { color: var(--ink); }

/* 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 { width: min(100%, 34em); }
/* What is still unanswered, in the palette that already means it: amber is
   "the readers disagreed" everywhere else on this screen, and the rule sits
   on the reading side of the text in either direction. */
.approve .amberline {
  border-inline-start: 3px solid var(--hold);
  padding-inline-start: var(--s3); max-width: 62ch;
}

/* 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. */
.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);
  }
}
