/*
 * hidden must actually hide.
 *
 * The [hidden] attribute is a UA rule of the lowest possible specificity, so any
 * class that sets display beats it. `.btn{display:inline-flex}` on the batch
 * grid's "Hold back the rows that are not ready" button meant `hold.hidden =
 * true` set the property, the DOM agreed the element was hidden, and the button
 * sat on screen offering to hold back nought rows. Twenty-three places in the
 * two client modules set .hidden; every one of them was a coin toss.
 */
[hidden] { display: none !important; }

/*
 * A label on a filled swatch is var(--surface), never #fff.
 *
 * In the dark theme --brand, --ok, --bad and --stamp are all LIGHT colours, so
 * white on them measures between 2.73:1 and 3.09:1 — the L1 pin on the route,
 * the unread count in the nav, the timeline dots, the notification dot and the
 * skip link were all failing and none of it was visible in a light-theme axe
 * run. --surface inverts with the theme, which is exactly what a label sitting
 * on a themed fill needs: 5.5:1 to 6.3:1 in both.
 *
 * The one legitimate #fff left is .avatar, whose background is an hsl() at 40%
 * lightness computed in JS and dark in both themes.
 */
/* ============================== TOKENS ============================== */
:root{
  /* The one line that makes every NATIVE control follow the theme: the popup
     list a <select> opens, the scrollbars, the date picker, the spin buttons.
     It was set only on .inp[type=date], so a dropdown opened in the dark theme
     dropped a white list over a dark page. A theme that stops at the edge of
     the browser's own widgets is not a theme. */
  color-scheme:light;
  /*
   * Deepened, because "the colours are too washed out" was measurably true.
   *
   * Measured before: every tint was 1.14–1.20:1 against the surface it sat on —
   * a status pill's background was all but white — and a table rule was 1.30:1,
   * so a table had no structure and a card did not read as a card. The accents
   * were never the problem: brand was already 80% saturated and warn 91%. The
   * colour was there, it was just whispered.
   *
   * Every tint is now 1.28–1.37:1 and every rule 1.49:1 or better, and the inks
   * on them were darkened to match so nothing lost contrast on the way. Held by
   * tests/palette.test.js, which checks every pair on this page against its own
   * floor in both themes — and fails on the palette that was here before.
   */
  --ground:#DFE5EB; --surface:#FFFFFF; --surface-2:#F2F6FA; --surface-3:#E6ECF2;
  --ink:#0C1722; --ink-2:#3C4C59; --ink-3:#566674;
  --rule:#CBD5DF; --rule-2:#A8B7C5;
  /* 3:1 against the field it outlines. --rule is 1.30 and stays decorative:
     a table divider identifies nothing, an input border identifies a control. */
  --field-border:#6E8AA3;
  --brand:#0B5F70; --brand-soft:#C8E3EA; --brand-ink:#07404B;
  --stamp:#992914;
  --ok:#0B6340; --ok-soft:#C9E7D8;
  --warn:#7C4B05; --warn-soft:#F6E1BB;
  --bad:#992914; --bad-soft:#F6D5CE;
  --idle:#4B5862; --idle-soft:#DBE2E9;
  --chart:#0B5F70; --chart-soft:#A9D6E0;
  --shadow:none;
  --shadow-lg:0 20px 48px -18px rgba(17,28,37,.28);
  --r:5px; --r-sm:5px;
  --t:150ms cubic-bezier(.2,0,.2,1);
  --sans:"IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono:"IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --serif:"Instrument Serif", Georgia, "Times New Roman", serif;
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    color-scheme:dark;
    --ground:#04080C; --surface:#18232F; --surface-2:#212E3B; --surface-3:#2B3A49;
    --ink:#EAF1F7; --ink-2:#AFBECB; --ink-3:#95A5B3;
    --rule:#32424F; --rule-2:#4A5C6C; --field-border:#62809A;
    --brand:#5BC0D4; --brand-soft:#17434F; --brand-ink:#8FD8E7;
    --stamp:#F2836E;
    --ok:#45C28C; --ok-soft:#14412D;
    --warn:#E8AC3C; --warn-soft:#443212;
    --bad:#F2836E; --bad-soft:#5A2C23;
    --idle:#94A3B0; --idle-soft:#27353F;
    --chart:#5BC0D4; --chart-soft:#1F4E5C;
    --shadow:none;
    --shadow-lg:0 20px 48px -18px rgba(0,0,0,.7);
  }
}
:root[data-theme="dark"]{
  --ground:#0C1218; --surface:#141C24; --surface-2:#1A242D; --surface-3:#212D37;
  --ink:#E3EAF0; --ink-2:#A2B0BC; --ink-3:#86949F;
  --rule:#26323C; --rule-2:#384652; --field-border:#516A7F;
  --brand:#48A9BD; --brand-soft:#123039; --brand-ink:#7FCBDB;
  --stamp:#E5705C;
  --ok:#35A877; --ok-soft:#12301F;
  --warn:#D99A2B; --warn-soft:#31250D;
  --bad:#E5705C; --bad-soft:#331812;
  --idle:#8A98A4; --idle-soft:#1E2831;
  --chart:#48A9BD; --chart-soft:#1B3C46;
  --shadow:none;
  --shadow-lg:0 20px 48px -18px rgba(0,0,0,.7);
}

/* ============================== BASE ============================== */
*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  background:var(--ground); color:var(--ink);
  font-family:var(--sans); font-size:14px; line-height:1.5;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,h4,p,figure,ul,ol{margin:0}
ul,ol{padding:0;list-style:none}
button,input,select,textarea{font:inherit;color:inherit}
button{cursor:pointer;background:none;border:none;padding:0}
a{color:var(--brand-ink)}
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:3px}
@media (prefers-reduced-motion:reduce){*{animation-duration:.01ms !important;transition-duration:.01ms !important}}
.mono{font-family:var(--mono);font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;letter-spacing:.005em}
.num{font-family:var(--mono);font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.eyebrow{font-family:var(--mono);font-size:10px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3)}
.muted{color:var(--ink-3)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ============================== SHELL ============================== */
.app{display:none;min-height:100vh;grid-template-columns:246px 1fr;grid-template-rows:auto 1fr}
.app.on{display:grid}
@media (max-width:900px){ .app.on{grid-template-columns:1fr} }

.topbar{
  grid-column:1/-1; display:flex; align-items:center; gap:16px;
  padding:0 20px; height:58px; background:var(--surface);
  border-bottom:1px solid var(--rule); position:sticky; top:0; z-index:40;
}
.brand{display:flex;align-items:baseline;gap:8px;text-decoration:none;color:inherit}
.brand .mark{font-family:var(--serif);font-size:23px;line-height:1;letter-spacing:-.01em}
.brand .dot{width:6px;height:6px;border-radius:50%;background:var(--stamp);display:inline-block;transform:translateY(-3px)}
.brand .sub{font-family:var(--mono);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3)}
.topbar .spacer{flex:1}

.userchip{display:flex;align-items:center;gap:9px;padding:5px 9px 5px 5px;border:1px solid var(--rule);border-radius:999px;background:var(--surface-2)}
.userchip:hover{border-color:var(--rule-2)}
.avatar{width:26px;height:26px;border-radius:50%;display:grid;place-items:center;font-family:var(--mono);font-size:10.5px;font-weight:600;color:#fff;flex:none}
.userchip .who{text-align:left;line-height:1.15}
.userchip .who b{font-weight:600;font-size:12.5px;display:block}
.userchip .who span{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3)}
.iconbtn{width:32px;height:32px;border-radius:5px;display:grid;place-items:center;color:var(--ink-2);border:1px solid transparent}
.iconbtn:hover{background:var(--surface-3);color:var(--ink)}

/* ============================== RAIL ============================== */
.rail{border-right:1px solid var(--rule);background:var(--surface);padding:16px 12px 28px;display:flex;flex-direction:column;gap:22px;overflow-y:auto}
.nav{display:flex;flex-direction:column;gap:1px}
.nav button{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--r-sm);color:var(--ink-2);font-size:13.5px;text-align:left;width:100%}
.nav button:hover{background:var(--surface-3);color:var(--ink)}
.nav button.sel{background:var(--brand-soft);color:var(--brand-ink);font-weight:600}
.nav button .cnt{margin-left:auto;font-family:var(--mono);font-size:10.5px;font-weight:600;background:var(--surface-3);color:var(--ink-2);border-radius:999px;padding:1px 7px}
.nav button.sel .cnt{background:var(--brand);color:var(--surface)}
.nav button .cnt.hot{background:var(--stamp);color:var(--surface)}
.railhead{padding:0 10px 6px}

/* The menu button only exists on narrow screens. */
.menubtn{display:none}

/* ------------------------------ RAIL AS A DRAWER ------------------------------
   These rules live after the base .rail block on purpose. The stylesheet used to
   carry `.rail{display:none}` inside a media query near the top, which a later
   `.rail{...display:flex...}` of equal specificity silently overrode — so the
   full navigation column rendered above the content on a phone, pushing the page
   itself a screen and a half down. Hiding it outright is not the fix either:
   that leaves a phone with no navigation at all. It becomes a drawer.          */
@media (max-width:900px){
  .menubtn{display:inline-flex}
  /* A transform moves a box. It does not take it out of the tab order — so the
     drawer, parked at x=-294, was still twenty-one Tab stops: Dashboard, My
     queue, All invoices and every route step, each focused, invisible, and one
     Enter away from navigating somewhere nobody asked to go. axe could not see
     it: by the DOM and the accessibility tree a slid-away drawer is a perfectly
     good list of buttons. What is wrong is where the box IS.

     visibility is the one property that both removes an element from the tab
     order and from the accessibility tree while leaving it laid out — and,
     unlike display, it is transitionable. `0s linear .22s` holds it visible for
     the length of the slide and hides it the instant the slide ends, so the
     drawer still animates out. browser/phone.mjs is what holds this. */
  .rail{
    position:fixed; inset:58px auto 0 0; width:min(82vw,300px); z-index:60;
    transform:translateX(-102%); visibility:hidden;
    transition:transform .22s ease, visibility 0s linear .22s;
    box-shadow:0 0 0 1px var(--rule), 12px 0 32px rgba(0,0,0,.14);
  }
  .rail.open{transform:none; visibility:visible; transition:transform .22s ease, visibility 0s linear 0s}
  .railscrim{position:fixed; inset:58px 0 0 0; background:rgba(8,16,20,.44); z-index:55}
  .railscrim[hidden]{display:none}
}
@media (max-width:900px) and (prefers-reduced-motion:reduce){
  .rail{transition:none}
}

/* route diagram */
.route{padding:0 4px;display:flex;flex-direction:column;gap:0}
.rstep{display:grid;grid-template-columns:22px 1fr;gap:10px;position:relative;padding-bottom:2px}
.rstep .col{display:flex;flex-direction:column;align-items:center}
.rstep .pin{width:22px;height:22px;border-radius:50%;border:1.5px solid var(--rule-2);display:grid;place-items:center;font-family:var(--mono);font-size:10px;font-weight:600;color:var(--ink-3);background:var(--surface);flex:none}
.rstep.mine .pin{border-color:var(--brand);background:var(--brand);color:var(--surface)}
/* L1/L2/L3 rather than a step number: two characters, so a rounded chip
   rather than a circle, and a touch smaller so it never clips. */
.rstep .pin.lvl{border-radius:6px;font-size:9px;letter-spacing:-.02em}
.rstep .line{width:1.5px;flex:1;background:var(--rule);min-height:16px}
.rstep:last-child .line{display:none}
.rstep .body{padding-bottom:14px}
.rstep .nm{font-size:12.5px;font-weight:500;line-height:1.3}
.rstep.mine .nm{color:var(--brand-ink);font-weight:600}
.rstep .meta{font-family:var(--mono);font-size:10px;color:var(--ink-3);margin-top:1px}
/* A stage with a backlog and nobody standing at it. The pin goes amber and the
   step says so in words — a tint alone tells a colour-blind reader nothing, and
   this is the most consequential fact the rail can carry. */
.rstep.nobody .pin{border-color:var(--warn);color:var(--warn)}

/* THE END OF THE ROUTE. Not a desk — nobody waits at it — so it gets a tick
   rather than a level, no staffing banner, and no connecting line onwards.
   Without it the rail showed four desks and no destination, and a ledger of
   163 settled invoices was represented nowhere at all. */
.rstep.settled .pin{border-color:var(--ok);color:var(--ok);font-size:11px}
.rstep.settled .nm{color:var(--ink-2)}
.rstep.settled .meta{color:var(--ok)}

/* The countersignature gate: an exception on the way through, not a desk of its
   own, so it is marked rather than numbered. */
.rstep.gate .pin{border-color:var(--warn);color:var(--warn)}
.rstep.gate .nm{color:var(--warn)}

/* Accounts that have been stood down. A different ground, not a lower opacity:
   fading a table fades the text in it, and this stylesheet says twice over what
   that costs. The rows still say "Deactivated" in words. */
table.tbl.stooddown tbody tr{background:var(--surface-2)}
table.tbl.stooddown tbody tr:hover{background:var(--surface-3)}

/* A voided payment: the row stays, because removing it would take the question
   "wasn't this paid?" away with it and leave nothing that answers it.

   Struck through rather than faded. A fade is a blend, and this stylesheet
   already says twice why that turns a passing colour into a failing one — at
   the stage stamp and at the route step. The line through the figure carries
   the meaning and costs the text nothing, and the row underneath says in words
   who voided it and why. */
tr.voided td{text-decoration:line-through;text-decoration-thickness:1.5px}
tr.voided td:last-child{text-decoration:none}
tr.voidnote td{padding-top:0;border-top:0;font-size:12px}

/* The second line, when a desk's count is made of more than one thing — three
   drafts and two sent back for fixes are not the same job. */
.alsohere{font-family:var(--mono);font-size:9.5px;color:var(--warn);margin-top:2px}

/* What belongs to nobody: on hold, rejected. Off to the side because they are
   not steps, and only rendered at all when they hold something. */
.routeaside{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 0 32px}
.routeaside button{font-family:var(--mono);font-size:9.5px;color:var(--ink-3);
  background:var(--surface-2);border:1px solid var(--rule);border-radius:999px;
  padding:2px 8px;cursor:pointer}
.routeaside button:hover{color:var(--ink);border-color:var(--rule-2)}
.routeaside button:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
.rstep.nobody .nm{color:var(--warn)}
/* A desk nobody holds, that an administrator is covering. Informational, not a
   fault: the route is slow rather than stopped, so it is stated without the
   amber pin. */
.rstep.covered .nm{color:var(--ink)}
.nobodyhere.covering{color:var(--ink-2);background:transparent;border-left-color:var(--rule-2)}
.nobodyhere{font-size:11px;line-height:1.35;color:var(--warn);background:var(--warn-soft);
  border-left:2px solid var(--warn);padding:4px 7px;margin-top:5px;border-radius:0 4px 4px 0}

/* ============================== MAIN ============================== */
.main{padding:24px 28px 64px;overflow-x:hidden;max-width:1280px;width:100%}
@media (max-width:640px){ .main{padding:18px 14px 56px} }
.pagehead{display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap;margin-bottom:20px}
.pagehead h1{font-family:var(--serif);font-size:30px;font-weight:400;line-height:1.05;letter-spacing:-.01em;text-wrap:balance}
.pagehead p{color:var(--ink-3);font-size:13px;margin-top:3px;max-width:60ch}
.pagehead .spacer{flex:1}

/* ============================== CARDS ============================== */
.card{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r)}
.card-h{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--rule)}
.card-h h2{font-size:13.5px;font-weight:600}
.card-h .spacer{flex:1}
.card-b{padding:16px}
.grid{display:grid;gap:14px}

/* stat tiles */
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(178px,1fr));gap:12px;margin-bottom:16px}
.kpi{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r);padding:13px 15px 12px}
.kpi .v{font-family:var(--mono);font-size:26px;font-weight:600;letter-spacing:-.03em;line-height:1.1;margin-top:7px;font-variant-numeric:tabular-nums}
.kpi .d{font-size:11.5px;color:var(--ink-3);margin-top:4px}
.kpi.hi .v{color:var(--stamp)}

/* ============================== PILLS / BADGES ============================== */
.pill{display:inline-flex;align-items:center;gap:5px;padding:2.5px 9px 2.5px 7px;border-radius:999px;font-size:11.5px;font-weight:500;white-space:nowrap;line-height:1.45}
.pill .g{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.pill.s-draft{background:var(--idle-soft);color:var(--idle)}
.pill.s-pending_review,.pill.s-pending_finance,.pill.s-pending_billing{background:var(--warn-soft);color:var(--warn)}
.pill.s-paid{background:var(--ok-soft);color:var(--ok)}
.pill.s-part_paid{background:var(--brand-soft);color:var(--brand-ink)}
.pill.s-rejected{background:var(--bad-soft);color:var(--bad)}
.pill.s-returned{background:var(--bad-soft);color:var(--bad)}
.pill.s-on_hold{background:var(--idle-soft);color:var(--ink-2)}
.rolebadge{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;padding:2px 6px;border-radius:3px;background:var(--surface-3);color:var(--ink-2)}

/* ============================== BUTTONS ============================== */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:8px 14px;border-radius:5px;font-size:13px;font-weight:500;border:1px solid var(--rule-2);background:var(--surface);color:var(--ink);transition:background var(--t),border-color var(--t)}
.btn:hover{background:var(--surface-3)}
/*
 * The label colour is a token, not #fff, and that is not tidiness.
 *
 * In dark, --brand is #48A9BD and --brand-ink is #7FCBDB — both light. White
 * on them measured 2.73:1 at rest and 1.83:1 on hover, so the one button on
 * the sign-in page had lettering that faded as the pointer arrived. --surface
 * is white in light and near-black in dark, which is exactly the inversion
 * needed: 5.97:1 / 6.29:1 at rest, 8.55:1 / 9.39:1 on hover.
 */
.btn.pri{background:var(--brand);border-color:var(--brand);color:var(--surface)}
.btn.pri:hover{background:var(--brand-ink);border-color:var(--brand-ink)}
.btn.ok{background:var(--ok);border-color:var(--ok);color:var(--surface)}
.btn.ok:hover{filter:brightness(1.08)}
.btn.danger{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 40%,transparent)}
.btn.danger:hover{background:var(--bad-soft)}
.btn.sm{padding:5px 10px;font-size:12px;border-radius:5px}
.btn.ghost{border-color:transparent;background:transparent;color:var(--ink-2)}
.btn.ghost:hover{background:var(--surface-3);color:var(--ink)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btnrow{display:flex;gap:8px;flex-wrap:wrap}

/* ============================== FORM ============================== */
.field{display:flex;flex-direction:column;gap:5px}
.field > label{font-family:var(--mono);font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3)}
.inp{width:100%;padding:8px 10px;border:1px solid var(--field-border);border-radius:5px;background:var(--surface);color:var(--ink)}
/* A focus ring you can actually see. The old rule set outline:none and leaned on
   a 1px border recolour plus an 18%-opacity glow, which is roughly 1.2:1 against
   white — an indicator in name only, and WCAG 2.2's 2.4.11 asks for better. */
.inp:focus-visible{border-color:var(--brand);outline:2px solid var(--brand);outline-offset:1px;
  box-shadow:0 0 0 3px color-mix(in srgb,var(--brand) 28%,transparent)}
.inp:focus:not(:focus-visible){border-color:var(--brand);outline:none;
  box-shadow:0 0 0 3px color-mix(in srgb,var(--brand) 28%,transparent)}
.inp::placeholder{color:var(--ink-3)}
textarea.inp{resize:vertical;min-height:70px;line-height:1.5}
/*
 * The arrow on a dropdown, drawn once and centred at every size.
 *
 * It used to be two linear-gradients pinned to an absolute offset —
 * `background-position: calc(100% - 15px) 16px` — so it was only ever centred
 * on a control of exactly that height. Every other dropdown on the site needed
 * its own override to nudge it back, the filter bar had one, and the ones
 * nobody had noticed (the batch grid's carry-down row, the month picker) simply
 * sat wrong. `right 11px center` needs no arithmetic and no overrides: it is
 * correct on a 26px control and on a 44px one.
 */
select.inp{
  appearance:none; padding-right:30px;
  background-repeat:no-repeat; background-position:right 11px center; background-size:9px 6px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'%3E%3Cpath d='M1 1l3.5 3.5L8 1' fill='none' stroke='%23616F7B' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
select.inp:hover{border-color:var(--rule-2)}
select.inp:disabled,.inp:disabled{background-color:var(--surface-2);color:var(--ink-3);cursor:not-allowed}
:root[data-theme="dark"] select.inp{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'%3E%3Cpath d='M1 1l3.5 3.5L8 1' fill='none' stroke='%2386949F' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) select.inp{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'%3E%3Cpath d='M1 1l3.5 3.5L8 1' fill='none' stroke='%2386949F' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
}
.formgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px}
.err{color:var(--bad);font-size:11.5px}

/* ============================== TABLE ============================== */
.tablewrap{overflow-x:auto}
table.tbl{width:100%;border-collapse:collapse;font-size:13px;min-width:760px}
/* --ink-2, not --ink-3. At 9.5px, uppercase, with a tenth of an em of letter
   spacing, --ink-3 measured 4.89:1 — over the line, but only just, and axe
   read it below on one pass. A column heading is the label a person looks for
   when they are scanning rather than reading; it is the wrong place to be one
   rounding error from unreadable. --ink-2 is 7.18:1 light and 7.10:1 dark. */
table.tbl th{text-align:left;font-size:9.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2);padding:8px 12px;border-bottom:1px solid var(--rule);background:var(--surface-2);position:sticky;top:0;white-space:nowrap}
table.tbl td{padding:8px 12px;border-bottom:1px solid var(--rule);vertical-align:middle;white-space:nowrap;
  max-width:280px;overflow:hidden;text-overflow:ellipsis}
table.tbl tbody tr{height:37px}
table.tbl tbody tr{cursor:pointer}
table.tbl tbody tr:hover{background:var(--surface-2)}
table.tbl tbody tr:last-child td{border-bottom:none}
/*
 * A number's heading sits over the number.
 *
 * `td.r,th.r{text-align:right}` reads as though it does this, and for the cells
 * it did. For the headings it never took: `table.tbl th` further up is one
 * element more specific than a bare `th.r`, so every heading over every
 * right-aligned column — Amount, Balance, Due, Invoices, Outstanding, Share —
 * sat at the LEFT edge of a column whose values were at the right edge. On a
 * wide column that is half a table's width of empty space between a word and
 * the figures it names.
 *
 * Specificity, not order: moving this line below the other one would not have
 * helped, and that is exactly the kind of fix that gets tried first.
 */
td.r,th.r{text-align:right}
table.tbl th.r,table.tbl td.r{text-align:right}
.vend{font-weight:500}
.subtle{color:var(--ink-3);font-size:11.5px}
.empty{padding:44px 20px;text-align:center;color:var(--ink-3)}
.empty .big{font-family:var(--serif);font-size:20px;color:var(--ink-2);margin-bottom:4px}

.filters{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:12px}
.filters .inp{width:auto;padding:6px 10px;font-size:12.5px}
/* No background-position here any more: the arrow centres itself. */
.filters select.inp{padding-right:30px;max-width:200px}
.filters .search .inp{min-width:200px}
.search{position:relative}
.search .inp{padding-left:29px;min-width:210px}
.search svg{position:absolute;left:9px;top:50%;transform:translateY(-50%);color:var(--ink-3)}

/* ============================== DETAIL ============================== */
.detail{display:grid;grid-template-columns:1fr 316px;gap:16px;align-items:start}
@media (max-width:960px){ .detail{grid-template-columns:1fr} }
.kv{display:grid;grid-template-columns:auto 1fr;gap:7px 18px;font-size:13px}
.kv dt{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);padding-top:2px}
.kv dd{margin:0}
.totalrow{display:flex;justify-content:space-between;padding:6px 0;font-size:13px}
.totalrow.grand{border-top:1px solid var(--rule-2);margin-top:6px;padding-top:10px;font-weight:600;font-size:16px}
.totalrow.grand .num{font-size:19px}

/* timeline */
.tl{display:flex;flex-direction:column}
.tlitem{display:grid;grid-template-columns:20px 1fr;gap:11px}
.tlitem .col{display:flex;flex-direction:column;align-items:center}
.tlitem .dot{width:20px;height:20px;border-radius:50%;display:grid;place-items:center;flex:none;border:1.5px solid var(--rule-2);background:var(--surface);color:var(--ink-3)}
.tlitem .dot svg{width:11px;height:11px}
.tlitem.a-ok .dot{background:var(--ok);border-color:var(--ok);color:var(--surface)}
.tlitem.a-bad .dot{background:var(--bad);border-color:var(--bad);color:var(--surface)}
.tlitem.a-info .dot{background:var(--brand);border-color:var(--brand);color:var(--surface)}
.tlitem .line{width:1.5px;flex:1;background:var(--rule);min-height:12px}
.tlitem:last-child .line{display:none}
.tlitem .body{padding-bottom:16px;min-width:0}
.tlitem .t{font-size:12.5px;line-height:1.4}
.tlitem .t b{font-weight:600}
.tlitem .when{font-family:var(--mono);font-size:10px;color:var(--ink-3);margin-top:2px}
.tlitem .note{margin-top:6px;padding:8px 10px;background:var(--surface-2);border:1px solid var(--rule);border-left:2px solid var(--rule-2);border-radius:0 5px 5px 0;font-size:12.5px;color:var(--ink-2)}

/* stage stamp */
/* No opacity. .9 blended --ok (#12784F, 4.84:1) into #288460, which is 4.06:1
   on the page ground — the same way .78 on --ink-3 failed in an earlier pass.
   The rotation is what makes this read as a rubber stamp; the fade added
   nothing and cost the contrast. It went unseen because axe never had a paid
   invoice to look at until one was driven all the way down the route. */
.stamp{border:2px solid currentColor;border-radius:5px;padding:5px 11px;font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;transform:rotate(-2.5deg);display:inline-block}
.stamp.paid{color:var(--ok)} .stamp.rejected{color:var(--bad)}

/* ============================== CHARTS ============================== */
.chartrow{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px;align-items:start}
.hbar{display:grid;grid-template-columns:minmax(96px,152px) 1fr auto;gap:12px;align-items:center;padding:5px 0;font-size:12.5px}
.hbar .lab{color:var(--ink-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hbar .track{display:block;height:12px;background:var(--surface-3);border-radius:3px;overflow:hidden;position:relative}
.hbar .fill{display:block;height:12px;background:var(--chart);border-radius:0 3px 3px 0;min-width:2px;transition:width var(--t)}
.hbar .val{font-family:var(--mono);font-size:12px;font-variant-numeric:tabular-nums;color:var(--ink);min-width:62px;text-align:right}
.hbar:hover .fill{filter:brightness(1.12)}

.vbars{display:flex;align-items:flex-end;gap:8px;height:150px;padding-top:18px}
.vb{flex:1;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;gap:6px;height:100%;position:relative}
.vb .bar{width:100%;max-width:52px;background:var(--chart);border-radius:3px 4px 0 0;min-height:2px;transition:height var(--t)}
.vb:hover .bar{filter:brightness(1.12)}
.vb .cap{font-family:var(--mono);font-size:10px;color:var(--ink-3);white-space:nowrap}
.vb .top{position:absolute;top:-16px;font-family:var(--mono);font-size:10.5px;font-weight:500;color:var(--ink-2);white-space:nowrap}
.axisnote{font-family:var(--mono);font-size:10px;color:var(--ink-3);margin-top:10px;padding-top:8px;border-top:1px solid var(--rule)}

.tip{position:fixed;z-index:200;pointer-events:none;background:var(--ink);color:var(--surface);padding:6px 9px;border-radius:5px;font-size:11.5px;box-shadow:var(--shadow-lg);opacity:0;transition:opacity var(--t);max-width:230px}
.tip.on{opacity:1}
.tip .tv{font-family:var(--mono);font-weight:600}

/* ============================== MODAL / SHEET ============================== */
.scrim{position:fixed;inset:0;background:rgba(8,14,20,.5);backdrop-filter:blur(2px);z-index:100;display:grid;place-items:center;padding:20px}
.modal{background:var(--surface);border:1px solid var(--rule);border-radius:5px;box-shadow:var(--shadow-lg);width:min(560px,100%);max-height:86vh;overflow:auto}
.modal-h{display:flex;align-items:center;gap:10px;padding:16px 18px 12px}
.modal-h h2{font-family:var(--serif);font-size:21px;font-weight:400}
.modal-h .spacer{flex:1}
.modal-b{padding:0 18px 18px;display:flex;flex-direction:column;gap:14px}
.modal-f{padding:14px 18px;border-top:1px solid var(--rule);display:flex;gap:8px;justify-content:flex-end;background:var(--surface-2);border-radius:0 0 6px 6px}

.menu{position:absolute;right:0;top:calc(100% + 6px);background:var(--surface);border:1px solid var(--rule);border-radius:5px;box-shadow:var(--shadow-lg);min-width:250px;padding:6px;z-index:60}
.menu .mh{padding:8px 10px 6px}
.menu button{display:flex;align-items:center;gap:9px;width:100%;padding:7px 9px;border-radius:5px;text-align:left;font-size:13px}
.menu button:hover{background:var(--surface-3)}
.menu button.sel{background:var(--brand-soft);color:var(--brand-ink)}
.menu hr{border:none;border-top:1px solid var(--rule);margin:6px 4px}

.toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%) translateY(14px);background:var(--ink);color:var(--surface);padding:10px 16px;border-radius:5px;font-size:13px;box-shadow:var(--shadow-lg);opacity:0;transition:.2s;z-index:300;pointer-events:none;display:flex;gap:8px;align-items:center}
.toast.on{opacity:1;transform:translateX(-50%) translateY(0)}

.legend{display:flex;gap:14px;flex-wrap:wrap;font-size:11.5px;color:var(--ink-2)}
.legend i{width:9px;height:9px;border-radius:2px;display:inline-block;margin-right:5px}
/* ---- added for the tracker build ---- */
.chip{display:inline-flex;align-items:center;padding:1.5px 7px;border-radius:5px;font-family:var(--mono);font-size:10px;font-weight:500;letter-spacing:.06em;background:var(--surface-3);color:var(--ink-2);white-space:nowrap}
.chip.site{background:var(--brand-soft);color:var(--brand-ink)}
.due-ok{color:var(--ink-2)}
.due-soon{color:var(--warn);font-weight:500}
.due-late{color:var(--bad);font-weight:600}
.flagnote{display:flex;gap:9px;align-items:flex-start;padding:10px 12px;border:1px solid var(--rule);border-left:2px solid var(--warn);border-radius:0 5px 5px 0;background:var(--warn-soft);color:var(--ink-2);font-size:12.5px}
.flagnote svg{flex:none;margin-top:1px;color:var(--warn)}
.miniwrap{display:flex;flex-direction:column;gap:2px}
.split{display:grid;grid-template-columns:1fr 1fr;gap:14px}
@media (max-width:760px){ .split{grid-template-columns:1fr} }
.imported{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);border:1px dashed var(--rule-2);border-radius:3px;padding:1.5px 6px}
.tblnum{font-family:var(--mono);font-variant-numeric:tabular-nums}
.bigamt{font-family:var(--mono);font-size:30px;font-weight:600;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.bulkbar{position:fixed;left:50%;transform:translateX(-50%);bottom:18px;z-index:80;
  display:flex;align-items:center;gap:9px;flex-wrap:wrap;justify-content:center;
  max-width:min(1040px,calc(100vw - 32px));padding:10px 14px;border-radius:5px;
  background:var(--ink);color:var(--surface);box-shadow:var(--shadow-lg)}
.bulkbar .n{font-family:var(--mono);font-size:12.5px;font-weight:600}
.bulkbar .sp{flex:1}
.bulkbar .btn{background:transparent;border-color:rgba(255,255,255,.28);color:var(--surface);padding:5px 11px;font-size:12.5px}
.bulkbar .btn:hover{background:rgba(255,255,255,.14)}
.bulkbar .btn.danger{color:#FFB4A4;border-color:rgba(255,180,164,.4)}
.bulkbar .btn.danger:hover{background:rgba(255,180,164,.16)}
table.tbl td.pick,table.tbl th.pick{width:34px;padding-right:0}
table.tbl input[type=checkbox]{width:15px;height:15px;accent-color:var(--brand);cursor:pointer;vertical-align:middle}
.lockrow{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.cardlist{display:none;flex-direction:column}
.irow{display:grid;grid-template-columns:1fr auto auto;gap:2px 12px;padding:11px 14px;border-bottom:1px solid var(--rule);text-align:left;width:100%;background:none}
.irow:last-child{border-bottom:none}
.irow:active{background:var(--surface-2)}
/*
 * The supplier name, clipped to two lines.
 *
 * This was `overflow:hidden;text-overflow:ellipsis;white-space:nowrap` on a
 * plain <span>, and none of those three properties apply to a non-replaced
 * INLINE box. So they did nothing: on a 390px screen "Falcon Enterprises" was
 * laid out at its full width and painted straight over the amount beside it.
 * Eight of the first twenty rows of the demo ledger were unreadable that way,
 * with up to 47px of one string on top of another. That is the register a
 * person opens on a phone, and it is what "alignment is fucked" meant.
 *
 * Two lines rather than one, because on a phone the supplier is the thing you
 * are scanning for and a name cut to eight characters is not a name. The button
 * around it already carries the whole thing in its aria-label, so nothing is
 * lost to a screen reader. browser/phone.mjs measures the painted boxes and
 * holds them apart.
 */
.irow .v{font-weight:600;font-size:13.5px;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;
  overflow:hidden;overflow-wrap:break-word;white-space:normal}
.irow .a{font-family:var(--mono);font-variant-numeric:tabular-nums;font-weight:600;font-size:13.5px;text-align:right}
.irow .m{font-size:11.5px;color:var(--ink-3);display:flex;gap:7px;flex-wrap:wrap;align-items:center}
.irow .r{text-align:right}
@media (max-width:760px){
  /* :not(.gridpane) — the batch grid swaps in its own card renderer inside
     the same pane, so hiding the pane would leave a phone with nothing. */
  .tablewrap.rows:not(.gridpane){display:none}
  .cardlist{display:flex}
}
.pager{display:flex;align-items:center;gap:10px;padding:10px 14px;border-top:1px solid var(--rule);font-size:12.5px;color:var(--ink-3)}
.pager .sp{flex:1}
.dropzone{border:1px dashed var(--rule-2);border-radius:5px;padding:16px;text-align:center;color:var(--ink-3);font-size:12.5px;background:var(--surface-2)}

/* ============================== SERVER-APP ADDITIONS ============================== */
.authwrap{min-height:100vh;display:grid;grid-template-columns:1.05fr 1fr}
@media (max-width:880px){ .authwrap{grid-template-columns:1fr} .authwrap .aside{display:none} }
/*
 * The inline padding grows with the column. At 1.05fr of a 1600px window the
 * aside is 820px wide holding 420px of content, and a fixed 46px gutter left
 * all of that slack on one side — the panel read as unfinished rather than as
 * composed. Growing the gutter keeps the text block near the middle of its own
 * column without moving anything on a laptop.
 */
.authwrap .aside{background:var(--surface);border-right:1px solid var(--rule);
  padding:44px clamp(46px,7vw,150px);
  display:flex;flex-direction:column;justify-content:center;gap:26px}
/*
 * The display line is a <p>, not the page <h1>.
 *
 * It sits inside <aside>, which is a complementary landmark, so an <h1> there
 * put the page's only top-level heading inside the decoration — and below
 * 880px, where the aside is hidden, the document had no <h1> at all. The
 * heading now lives on the form, where the page's actual purpose is.
 *
 * clamp() rather than a fixed 42px: the column is fluid and the font is not,
 * so at 881px — one pixel above the breakpoint — a 42px serif was being set in
 * a 359px measure. 16ch and text-wrap:balance even out a rag that was leaving
 * "record." alone on a line of its own.
 */
.authwrap .aside .lede{font-family:var(--serif);font-size:clamp(30px,3.4vw,42px);font-weight:400;
  line-height:1.05;letter-spacing:-.015em;max-width:16ch;text-wrap:balance;color:var(--ink)}
.authwrap .aside .lede em{font-style:italic;color:var(--brand-ink)}
.authwrap .aside p:not(.lede){color:var(--ink-2);max-width:46ch;font-size:14.5px}
/* The form is wrapped in a <main> so the landmark sits on an element allowed to
   carry it — role="main" on a <form> is not valid ARIA. The wrapper takes over
   as the grid item, so the column still centres the way it did. */
.authmain{display:flex;align-items:center;justify-content:center;min-width:0}
.authform{padding:40px 34px;display:flex;flex-direction:column;gap:14px;max-width:440px;width:100%}
.authform h1{font-family:var(--serif);font-size:26px;font-weight:400}
/* The eyebrow belongs to the heading under it, not to the 14px rhythm between
   unrelated blocks — at the shared gap "SIGN IN" floated away from "Welcome
   back" and read as a separate thing. */
.authform .eyebrow{margin-bottom:-8px}
/*
 * Centring only makes sense when the viewport is taller than the content. The
 * grid row is sized by the aside, and centring the form inside that row put
 * the submit button below the fold on a 700px-tall laptop — on the page whose
 * entire job is that button.
 */
@media (max-height:820px){
  .authwrap .aside{justify-content:flex-start}
  .authmain{align-items:flex-start}
}
.banner{padding:10px 13px;border-radius:var(--r);font-size:13px;border:1px solid var(--rule)}
.banner.bad{background:var(--bad-soft);color:var(--bad);border-color:color-mix(in srgb,var(--bad) 30%,transparent)}
.banner.ok{background:var(--ok-soft);color:var(--ok);border-color:color-mix(in srgb,var(--ok) 30%,transparent)}
.banner.warn{background:var(--warn-soft);color:var(--warn);border-color:color-mix(in srgb,var(--warn) 30%,transparent)}
.spin{display:inline-block;width:13px;height:13px;border:2px solid var(--rule-2);border-top-color:var(--brand);
  border-radius:50%;animation:sp .7s linear infinite;vertical-align:-2px}
/*
 * A spinner inside a primary button was drawn with --brand as its moving arc,
 * on a --brand background: the one part that shows rotation was painted in the
 * button's own fill, so nothing appeared to turn. Inside a filled button the
 * ring is drawn from the label colour instead.
 */
.btn.pri .spin,.btn.ok .spin{border-color:color-mix(in srgb,var(--surface) 40%,transparent);
  border-top-color:var(--surface)}
/*
 * Busy is not the same as unavailable. .btn:disabled fades to 45%, which on a
 * submit button waiting for the server means the words "Signing in" are barely
 * legible at the exact moment somebody is looking for reassurance that their
 * click registered — so they click again.
 */
.btn.busy:disabled{opacity:1;cursor:progress}
@keyframes sp{to{transform:rotate(360deg)}}
.skel{background:linear-gradient(90deg,var(--surface-3) 25%,var(--surface-2) 50%,var(--surface-3) 75%);
  background-size:200% 100%;animation:sh 1.2s ease-in-out infinite;border-radius:3px;height:12px}
@keyframes sh{to{background-position:-200% 0}}
.copyfield{display:flex;gap:8px;align-items:center}
.copyfield input{flex:1;font-size:12px}

/* the top bar must not set the page's minimum width on a phone */
.app{min-width:0}
.app > *{min-width:0}
.topbar{min-width:0}
.topbar .brand{min-width:0;overflow:hidden}
@media (max-width:640px){
  .topbar{padding:0 12px;gap:8px}
  .brand .mark{font-size:19px}
  .userchip{padding:4px}
  .userchip .who{display:none}
  .userchip > svg{display:none}
  .pagehead h1{font-size:25px}
}

/* ============ the batch entry grid ============
   Replaces the correction grid that only existed after a paste. Two of that
   block's declarations were hardcoded rgba() literals, which is why a red
   tint on a dark surface read as brown mud; everything below is built from
   the theme's own tokens. The screen itself is public/bulkgrid.js. */
/* The action bar is sticky at the bottom of the card, so the page needs room
   under it or the bar sits on top of the last row of the paste panel. */
.bulkroot{padding-bottom:96px}

/* THE STEPS STRIP. Always visible, never a disclosure: a person frightened of
   getting it wrong will never open one. */
.steps{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 14px}
.steps li{display:flex;align-items:flex-start;gap:8px;flex:1 1 240px;
  padding:10px 12px;border:1px solid var(--rule);border-radius:var(--r);
  background:var(--surface);color:var(--ink-2);font-size:12.5px;line-height:1.45}
.steps li .n{flex:none;width:18px;height:18px;border-radius:50%;
  background:var(--brand-soft);color:var(--brand-ink);font-family:var(--mono);
  font-size:11px;font-weight:600;line-height:18px;text-align:center}

/* THE CARRY STRIP. */
.carrycard{margin-bottom:14px}
.carryrow{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}
/*
 * A block of selected cells.
 *
 * A tint plus a real border, not a tint alone: at the contrast a tint can
 * safely carry over a white field it is nearly invisible in bright light, and
 * "nearly invisible" is not a selection anybody trusts enough to press Delete
 * on. The border is --brand, which is the same colour focus uses, because this
 * IS an extension of where you are.
 */
.selcell{
  background:var(--brand-soft) !important;
  box-shadow:inset 0 0 0 1px var(--brand);
  border-color:var(--brand) !important;
}
/* The focused cell inside a block keeps its own ring on top of the tint. */
.selcell:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
.selnote{
  margin:8px 0 0;padding:6px 10px;border-radius:var(--r-sm);
  background:var(--brand-soft);color:var(--brand-ink);
  font-size:12px;font-weight:500;
}
.selnote[hidden]{display:none}

.carryrow .inp{font-size:13px;padding:6px 8px}
/* ...but never at the cost of the arrow's room. A shorthand `padding` on a
   container's inputs silently takes back the 30px a dropdown needs on the
   right, and the selected text then runs underneath the chevron. Measured:
   bk-carry-site had padding-right 8px against a 9px arrow inset 11px.
   browser/controls.mjs walks every select on every page and holds this. */
.carryrow select.inp{padding-right:28px}
.carrycap{margin-top:10px}
.carryhint{margin-top:4px}

/* THE TOOLBAR. */
.gridtools{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px}
.toolgroup{display:flex;flex-wrap:wrap;gap:8px}
.filepick{display:inline-flex;position:relative}
/* The tab stop is the visually hidden <input>; the ring has to appear on the
   label, which is the only visible part of the control. */
.filepick input:focus-visible + .btn{outline:2px solid var(--brand);outline-offset:2px}
.gridhelp{flex-basis:100%;margin:2px 0 0}
.keyshelp{font-size:12.5px}
.keyshelp summary{cursor:pointer;color:var(--ink-2);padding:4px 0}
.keyshelp summary::-webkit-details-marker{color:var(--ink-3)}
.keylist{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;margin:8px 0 0;
  padding:10px 12px;border:1px solid var(--rule);border-radius:var(--r);background:var(--surface-2)}
.keylist dt{font-family:var(--mono);font-size:11.5px;color:var(--ink);white-space:nowrap}
.keylist dd{margin:0;color:var(--ink-2);font-size:12.5px}

/* THE PANE. This is what makes sticky work at all. `.tbl th{position:sticky;
   top:0}` already exists and today sticks to nothing, because `.tablewrap` is
   `overflow-x:auto` — which computes overflow-y to auto — with no bounded
   height, so the scrollport is as tall as the table. `top:0` is correct here
   and `top:58px` would be wrong: sticky resolves against the scrollport, not
   the viewport, and the topbar is not in play inside a bounded pane. */
.gridpane{overflow:auto;max-height:calc(100vh - 320px);min-height:240px;
  border:1px solid var(--rule);border-radius:var(--r);background:var(--surface);
  overscroll-behavior:contain}
.gridpane.dropping{outline:2px dashed var(--brand);outline-offset:-3px}
/* Below a short viewport the cap is dropped and the page scrolls instead. A
   200px scroll box is worse than a header that scrolls away. */
@media (max-height:560px){ .gridpane{max-height:none} }

/* THE TABLE. `table-layout:fixed` with an explicit colgroup: with auto layout
   a twelve-column table re-measures on every keystroke and the Amount column
   slides under the cursor mid-figure. That is a data-entry hazard, not a
   performance complaint. Fixed also makes the sticky offsets constants.
   `border-collapse:separate` because collapsed borders are dropped or
   mis-painted on sticky cells in every engine; the dividers are drawn as
   inset box-shadows instead. */
table.tbl.entrygrid{table-layout:fixed;border-collapse:separate;border-spacing:0;
  width:1534px;min-width:1534px;font-size:13px}
.entrygrid th,.entrygrid td{padding:0;border:0;white-space:normal;
  box-shadow:inset 0 -1px 0 var(--rule);background:var(--surface);vertical-align:middle}
.entrygrid thead th{position:sticky;top:0;z-index:3;background:var(--surface-2);
  padding:6px 8px;text-align:left;text-transform:none;letter-spacing:0;
  font-size:11px;font-weight:600;color:var(--ink);line-height:1.25;white-space:normal}
/* The hint is the second line of its own header cell. Reference material folded
   behind a disclosure, on the screen whose entire purpose is getting eleven
   columns right, is reference material nobody opens. The word "required"
   replaces the old red dot: a dot is colour-only meaning, the word survives a
   projector and a monochrome print, and it teaches. */
.entrygrid thead th .h{display:block}
.entrygrid .colhint{display:block;font-family:var(--sans);font-size:10.5px;
  font-weight:400;text-transform:none;letter-spacing:0;color:var(--ink-3);margin-top:1px}
/* A row-wide hover highlight is noise in an entry grid, and the pointer is a lie. */
.entrygrid tbody tr{height:auto;cursor:auto}
.entrygrid tbody tr:hover{background:transparent}
.entrygrid tbody tr:hover > *{background:var(--surface)}
.entrygrid tbody tr.datarow > td{padding:3px 4px}

/* STICKY LEFT: the row number and the invoice number, 178px in total. Vendor
   at 208px would freeze 386px of a ~1100px viewport — less room than the
   columns you scrolled right to read. Out at Notes, the one thing you need is
   which invoice this is, and that is the number. */
.entrygrid .gut{position:sticky;left:0;z-index:2;width:46px;padding:0 2px;
  text-align:right;font-family:var(--mono);font-size:11px;color:var(--ink-3);
  background:var(--surface)}
.entrygrid thead th.gut{z-index:4;background:var(--surface-2)}
.entrygrid td.c-number{position:sticky;left:46px;z-index:2;background:var(--surface)}
.entrygrid thead th:nth-child(2){position:sticky;left:46px;z-index:4}
/* The seam between the frozen block and the scrolling columns is drawn ONCE, on
   the sticky side. Drawn on both it doubles at rest; drawn on the scrolling
   side it vanishes under the sticky cell. */
.entrygrid td.c-number,.entrygrid thead th:nth-child(2){
  box-shadow:inset 0 -1px 0 var(--rule),1px 0 0 var(--rule)}
.entrygrid .gut .gutn{display:inline-block;min-width:16px}
/* 24×24 exactly, with the gutter's 46px keeping any other target well clear. */
.entrygrid .rowmenu,.rowcard .rowmenu{display:inline-flex;align-items:center;
  justify-content:center;width:24px;height:24px;border-radius:4px;color:var(--ink-3);
  font-size:14px;line-height:1}
.entrygrid .rowmenu:hover,.rowcard .rowmenu:hover{background:var(--surface-3);color:var(--ink)}

/* THE CELL. One control at rest, filling its cell. */
.cellinp{width:100%;min-height:30px;font:inherit;font-size:12.5px;padding:5px 20px 5px 7px;
  border:1px solid var(--field-border);border-radius:var(--r-sm);
  background:var(--surface);color:var(--ink)}
.cellinp.right{text-align:right;font-variant-numeric:tabular-nums}
.cellsel{padding-right:6px}
.cellinp[aria-readonly="true"],.cellinp[readonly]{background:var(--surface-3);color:var(--ink-2)}
/* 2.4.11: nothing programmatically focused may end up under the sticky header,
   the sticky action bar or the 178px frozen block. */
.cellinp{scroll-margin-block:46px 84px;scroll-margin-inline-start:190px}
/* Inside the cell, so a sticky neighbour cannot clip the ring. */
.cellinp:focus-visible{outline:2px solid var(--brand);outline-offset:-1px;border-color:var(--brand)}
.cellinp:focus:not(:focus-visible){border-color:var(--brand);outline:none;
  box-shadow:inset 0 0 0 1px var(--brand)}
.underecho{display:block;font-size:11px;line-height:1.25;padding:0 7px 2px;
  color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.entrygrid td{position:relative}
.cellglyph{position:absolute;top:9px;right:9px;width:14px;height:14px;
  font-size:11px;line-height:14px;text-align:center;pointer-events:none}
.cellinp.bad ~ .cellglyph{color:var(--bad)}
.cellinp.warn ~ .cellglyph{color:var(--warn)}

/* MARKS. The distinction between a problem and a warning carries in BORDER
   STYLE and a GLYPH, never in hue alone: colour on its own fails on a
   projector and for roughly one man in twelve. */
.cellinp.bad{border:2px solid var(--bad);background:var(--bad-soft);padding:4px 19px 4px 6px}
.cellinp.warn{border:1px dashed var(--warn);border-bottom-width:2px;
  border-bottom-style:dashed;background:var(--warn-soft)}
/* A --brand focus ring on a --bad-soft field is two saturated colours competing
   for the same 2px and neither reads, so the fill drops back while the coloured
   border stays. */
.cellinp.bad:focus,.cellinp.warn:focus{background:var(--surface)}
.entrygrid tr.rowbad > .gut{box-shadow:inset 3px 0 0 var(--bad),inset 0 -1px 0 var(--rule)}
.entrygrid tr.rowwarn > .gut{box-shadow:inset 3px 0 0 var(--warn),inset 0 -1px 0 var(--rule)}
/* A held row is frozen, and says so in a word as well as in the dimming. */
.entrygrid tr.held > *,.rowcard.held{background:var(--surface-2)}
.entrygrid tr.held .cellinp{border-style:dotted}

/* THE FOCUSED ROW AND COLUMN. In a 1,534px grid, "which cell am I in" has to be
   answerable without counting headers. The column is edged from a data attribute
   against twelve static rules rather than by touching 200 nodes on every move. */
.entrygrid[data-focuscol="1"] tr.datarow > *:nth-child(2),
.entrygrid[data-focuscol="2"] tr.datarow > *:nth-child(3),
.entrygrid[data-focuscol="3"] tr.datarow > *:nth-child(4),
.entrygrid[data-focuscol="4"] tr.datarow > *:nth-child(5),
.entrygrid[data-focuscol="5"] tr.datarow > *:nth-child(6),
.entrygrid[data-focuscol="6"] tr.datarow > *:nth-child(7),
.entrygrid[data-focuscol="7"] tr.datarow > *:nth-child(8),
.entrygrid[data-focuscol="8"] tr.datarow > *:nth-child(9),
.entrygrid[data-focuscol="9"] tr.datarow > *:nth-child(10),
.entrygrid[data-focuscol="10"] tr.datarow > *:nth-child(11),
.entrygrid[data-focuscol="11"] tr.datarow > *:nth-child(12){background:var(--surface-2)}
.entrygrid[data-focuscol="1"] thead th:nth-child(2),
.entrygrid[data-focuscol="2"] thead th:nth-child(3),
.entrygrid[data-focuscol="3"] thead th:nth-child(4),
.entrygrid[data-focuscol="4"] thead th:nth-child(5),
.entrygrid[data-focuscol="5"] thead th:nth-child(6),
.entrygrid[data-focuscol="6"] thead th:nth-child(7),
.entrygrid[data-focuscol="7"] thead th:nth-child(8),
.entrygrid[data-focuscol="8"] thead th:nth-child(9),
.entrygrid[data-focuscol="9"] thead th:nth-child(10),
.entrygrid[data-focuscol="10"] thead th:nth-child(11),
.entrygrid[data-focuscol="11"] thead th:nth-child(12){color:var(--brand-ink)}
.entrygrid tr.datarow:focus-within > *{background:var(--surface-2)}

/* THE NOTE ROW. Its content is pinned to the left edge as the table scrolls
   right, so the one thing a person needs to read does not slide off the screen
   in a table 1,534px wide. */
.entrygrid tr.noterow > td{padding:0 8px 7px 50px;box-shadow:none;background:var(--surface)}
.entrygrid tr.rowbad + tr.noterow > td{box-shadow:inset 3px 0 0 var(--bad)}
.entrygrid tr.rowwarn + tr.noterow > td{box-shadow:inset 3px 0 0 var(--warn)}
.notebody{position:sticky;left:8px;width:max-content;max-width:min(880px,90vw);
  display:flex;flex-direction:column;gap:5px}
/* At least 24px between adjacent targets (2.5.8). */
.noteline{display:flex;align-items:center;flex-wrap:wrap;gap:10px;font-size:12.5px;line-height:1.45}
.noteline .g{flex:none;font-size:11px}
.noteline.problem .g,.jump.problem{color:var(--bad)}
.noteline.warning .g,.jump.warning{color:var(--warn)}
.noteline.checked .g,.noteline.checked .ck,.noteline.heldline .g,.noteline.heldline .ck{color:var(--ink-3)}
.jump{display:inline-flex;align-items:center;gap:6px;min-height:24px;text-align:left;
  border-radius:3px;padding:1px 3px}
.jump b{font-weight:600}
.jump:hover{text-decoration:underline}
.jump:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
.fixbtn,.okbtn{min-height:24px;padding:3px 9px}
.noterow.stale{opacity:.7}

/* THE BANNER STACK AND THE ACTION BAR. */
.bkbanner{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.bkbanner:empty{display:none}
.bkbanner .banner{display:flex;align-items:center;flex-wrap:wrap;gap:10px}
.twowave,.wheretogo{margin:0}
.checkfacts{display:flex;align-items:center;flex-wrap:wrap;gap:10px}
.checkfacts p{margin:0;flex-basis:100%}
.undertable{display:flex;gap:8px;margin-top:8px}
/* Sticky at the bottom of the card body, below the pane. `.bulkroot`'s bottom
   padding above is what stops it covering anything. */
.actionbar{position:sticky;bottom:0;z-index:5;display:flex;align-items:center;
  flex-wrap:wrap;gap:10px;margin-top:12px;padding:10px 0 2px;
  border-top:1px solid var(--rule);background:var(--surface)}
.actionbar .spacer{flex:1}
.barcounts{display:flex;align-items:baseline;flex-wrap:wrap;gap:10px}
.barcounts .n{font-family:var(--mono);font-variant-numeric:tabular-nums;
  font-size:14px;font-weight:600;color:var(--ink)}
.barcounts .n.dim{color:var(--ink-3)}
.checkstate.failed{color:var(--bad)}
.barhint{flex-basis:100%;margin:0}

/* THE VENDOR LISTBOX. In <body> and position:fixed, because inside the table it
   would be clipped by the pane's overflow and the list for a rightward column
   simply would not appear. */
.vendorpop{position:fixed;z-index:60;max-height:288px;overflow:auto;padding:4px;
  margin:0;list-style:none;background:var(--surface);border:1px solid var(--rule-2);
  border-radius:var(--r);box-shadow:var(--shadow-lg)}
.vopt{display:flex;align-items:center;justify-content:space-between;gap:12px;
  min-height:32px;padding:6px 9px;border-radius:4px;font-size:13px;cursor:pointer}
.vopt.on{background:var(--brand-soft);color:var(--brand-ink)}
/* The count is how a person tells the real spelling from a stray one. */
.vopt .ct{flex:none;font-size:11.5px;color:var(--ink-3)}
.vopt.on .ct{color:var(--brand-ink)}
.vopt.create{color:var(--brand-ink)}
.vmore{padding:6px 9px;font-size:11.5px;color:var(--ink-3)}

/* THE ROW MENU. `.menu` already exists and is absolutely positioned inside its
   button; this one is fixed, in <body>, for the same clipping reason. */
.menu.rowmenupop{position:fixed;z-index:70;min-width:270px}
.menu.rowmenupop .menuitem{display:flex;align-items:center;justify-content:space-between;
  gap:16px;width:100%;padding:7px 9px;border-radius:4px;text-align:left;font-size:13px}
.menu.rowmenupop .menuitem:hover{background:var(--surface-3)}
.menu.rowmenupop .menuitem .k{font-family:var(--mono);font-size:11px;white-space:nowrap}

/* THE PHONE RENDERER. Not a nicety: without it the correction grid is invisible
   below 760px, and the person in the brief is standing at a site holding a phone. */
.cardgrid{display:flex;flex-direction:column}
.rowcard{border-bottom:1px solid var(--rule);padding:12px 12px 14px;background:var(--surface)}
.rowcard:last-child{border-bottom:none}
.rowcard-h{display:flex;align-items:center;gap:8px;margin-bottom:10px}
.rowcard-h .spacer{flex:1}
.rowcard-h .gutn{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--ink-3)}
.rowcard.rowbad{box-shadow:inset 3px 0 0 var(--bad)}
.rowcard.rowwarn{box-shadow:inset 3px 0 0 var(--warn)}
.rowcard.rowbad .rowglyph::before{content:"▲";color:var(--bad)}
.rowcard.rowwarn .rowglyph::before{content:"○";color:var(--warn)}
.cardfields{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}
.cardfield{position:relative;min-width:0}
.cardfield .cellinp{min-height:38px;font-size:14px}
.cardfield .colhint{display:block;font-size:10.5px;color:var(--ink-3);margin-top:2px}
.cardmore{margin-top:10px}
.cardmore summary{cursor:pointer;font-size:12.5px;color:var(--brand-ink);padding:6px 0;min-height:24px}
.cardmore .cardfields{margin-top:8px}
.cardnotes:empty{display:none}
.cardnotes{margin-top:10px}
.cardnotes .notebody{position:static;width:auto;max-width:none}

/* THE PASTE PANEL, folded away, because it is no longer the first move. */
.pastepanel{margin-top:14px;border:1px solid var(--rule);border-radius:var(--r);
  background:var(--surface);padding:0 14px}
.pastepanel > summary{cursor:pointer;padding:12px 0;font-size:13px;color:var(--ink-2)}
.pastepanel > summary::-webkit-details-marker{color:var(--ink-3)}
.pastebody{padding:0 0 16px;display:flex;flex-direction:column;gap:12px}
.pastebox{font-family:var(--mono);font-size:12px;line-height:1.55;white-space:pre;overflow-x:auto}
/*
 * The ageing table. Wide enough to breathe on a laptop, and on a phone it gives
 * up its padding rather than a column: at 8px either side, Band / Invoices /
 * Outstanding / Share all fit inside 360px. browser/phone.mjs holds it to that.
 */
table.tbl.agetbl{min-width:560px}
@media (max-width:640px){
  table.tbl.agetbl{min-width:0;font-size:12.5px}
  table.tbl.agetbl th,table.tbl.agetbl td{padding:8px 6px}
  table.tbl.agetbl th{font-size:9px;letter-spacing:.04em}
  table.tbl.agetbl th:first-child,table.tbl.agetbl td:first-child{padding-left:0}
  table.tbl.agetbl th:last-child,table.tbl.agetbl td:last-child{padding-right:0}
}

table.tbl.guidetbl{min-width:0;font-size:12.5px}
table.tbl.guidetbl tbody tr{cursor:auto}
table.tbl.guidetbl tbody tr:hover{background:var(--surface)}

/* THE DRAFT BANNER, THE REVIEW SHEET, THE RESULT PANEL. */
.draftslot:not(:empty){margin:0 0 14px}
.draftbanner{display:flex;align-items:center;flex-wrap:wrap;gap:10px;
  background:var(--brand-soft);border-color:var(--brand);color:var(--brand-ink)}
.reviewsheet .bigamt{font-family:var(--mono);font-size:30px;font-weight:600;
  letter-spacing:-.03em;font-variant-numeric:tabular-nums;color:var(--ink)}
.reviewsheet .revline{color:var(--ink-2);font-size:13px;margin:2px 0 14px}
.reviewsheet table.tbl.sitetbl{min-width:0;font-size:12.5px}
.reviewsheet table.tbl.sitetbl tbody tr{cursor:auto}
.reviewsheet table.tbl.sitetbl tbody tr:hover{background:var(--surface)}
.revwarn{display:flex;flex-direction:column;gap:8px;margin-top:14px}
.revwarnline{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin:0;
  font-size:12.5px;color:var(--ink-2)}
.revfoot{margin-top:14px;font-size:12.5px;color:var(--ink-2)}
.resultpanel{margin-top:14px}
.resulth{font-family:var(--serif);font-size:22px;font-weight:400;margin-bottom:6px}
.resulth:focus-visible{outline:2px solid var(--brand);outline-offset:3px}
.resultpanel p{color:var(--ink-2);margin-bottom:4px}
.resultpanel .btnrow{margin-top:14px}

/* ---- notifications ---- */
.belldot {
  position: absolute; top: 3px; right: 2px; min-width: 15px; height: 15px; padding: 0 4px;
  border-radius: 8px; background: var(--bad); color: var(--surface); font-size: 10px; font-weight: 700;
  line-height: 15px; text-align: center; box-shadow: 0 0 0 2px var(--surface);
}
#bellBtn { position: relative; }
.menu.notes { width: min(380px, calc(100vw - 24px)); padding: 0; }
.menu.notes .mh { padding: 12px 14px 10px; }
.waitbar {
  margin: 0 14px 12px; padding: 9px 11px; border: 1px solid var(--brand-soft);
  background: var(--brand-soft); border-radius: 8px; font-size: 13px; color: var(--brand-ink);
}
.noteslist { max-height: min(60vh, 460px); overflow-y: auto; }
/* `.menu button` above lays its children out in a row, which is right for a
   two-line menu entry and wrong here: it put the vendor, the invoice number, the
   action and the note side by side in narrow columns instead of stacking them.
   Same specificity, so this has to be scoped to win. */
.menu.notes .noteitem {
  display: block; width: 100%; text-align: left; padding: 10px 14px; border: 0;
  border-radius: 0; gap: 0; font-size: inherit;
  border-bottom: 1px solid var(--rule); background: transparent; cursor: pointer;
}
.menu.notes .noteitem:hover { background: var(--surface-2); }
/* Unread is marked by a bar as well as a tint: a tint alone is invisible to
   plenty of people and disappears entirely on a projector. */
.menu.notes .noteitem.unread { box-shadow: inset 3px 0 0 var(--brand); background: var(--surface-2); }
.menu.notes .noteitem .nt { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
.menu.notes .noteitem .nv { font-weight: 600; font-size: 13.5px; }
.menu.notes .noteitem .nw { font-size: 12.5px; color: var(--ink-2); }
.menu.notes .noteitem .nn {
  font-size: 12px; color: var(--ink-2); margin-top: 4px; padding-left: 8px;
  border-left: 2px solid var(--rule); overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.menu.notes .notemark {
  display: block; width: 100%; padding: 10px; border: 0; border-radius: 0;
  border-top: 1px solid var(--rule); background: transparent; color: var(--brand);
  font-weight: 500; font-size: 12.5px; text-align: center; cursor: pointer;
}
.menu.notes .notemark:hover { background: var(--surface-2); }

/* ---- things that lead somewhere ----
   A chart that answers "which site owes the most" is followed immediately by
   "show me those" — so a bar that leads to the invoices behind it says so, with
   a pointer and a hover state rather than looking identical to one that does
   not. */
.hbar.go { width: 100%; border: 0; background: transparent; text-align: left; font: inherit; cursor: pointer; border-radius: 5px; }
.hbar.go:hover { background: var(--surface-2); }
.hbar.go:hover .lab { color: var(--ink); }
.hbar.go:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* The route down the side. Each stage opens the register filtered to it. */
button.rstep {
  width: 100%; border: 0; background: transparent; font: inherit; text-align: left;
  cursor: pointer; border-radius: 6px; padding: 2px 6px 2px 0;
}
button.rstep:hover { background: var(--surface-2); }
button.rstep:hover .nm { color: var(--brand-ink); }
button.rstep:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/*
 * The step the register is actually showing.
 *
 * It cannot reuse .mine, which means "your desk": a reviewer looking at the
 * Billing queue would see two tinted steps standing for two different things.
 * The tint is the weakest of three signals — the word "showing" and
 * aria-current carry it for anyone who cannot see a tint.
 */
button.rstep.on { background: var(--brand-soft); box-shadow: inset 2px 0 0 var(--brand); }
/* The tint is the resting state here, so hover thickens the bar rather than
   swapping the background and leaving the step with no hover feedback. */
button.rstep.on:hover { background: var(--brand-soft); box-shadow: inset 3px 0 0 var(--brand); }
button.rstep.on .nm { color: var(--brand-ink); font-weight: 600; }
/* --ink-3 on --brand-soft is 4.41:1 light and 4.48:1 in dark. Both are a hair
   under 4.5, and at 10px this is not large text, so the meta line moves up a
   step wherever it sits on the tint. */
button.rstep.on .meta { color: var(--ink-2); }
.rstep .here { display: none; }
button.rstep.on .here { display: inline; color: var(--brand-ink); font-weight: 600; }
button.rstep.on .here::before { content: " · "; color: var(--ink-2); font-weight: 400; }
/* No opacity here: dimming text is how a passing colour quietly becomes a
   failing one. 0.78 turned #616F7B into #848F98, which is 3.30 on white. The
   words "0 waiting" already say the step is empty. */

/* What the register is currently showing, and a way out of each filter. */
.shownow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: -4px 0 12px; font-size: 12.5px; }
.fchip {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px;
  border: 1px solid var(--brand-soft); background: var(--brand-soft); color: var(--brand-ink);
  border-radius: 999px; font-size: 12px; font-weight: 500; cursor: pointer;
}
.fchip:hover { border-color: var(--brand); }
.fchip span { opacity: .7; font-size: 13px; line-height: 1; }

/* A data check that leads to the rows it flagged. */
.flagnote.go { width: 100%; text-align: left; cursor: pointer; border: 1px solid transparent; }
.flagnote.go:hover { border-color: var(--warn); }
.flagnote.go .goarrow { margin-left: auto; opacity: .55; font-size: 14px; }
.flagnote.go:hover .goarrow { opacity: 1; }

/* A figure that opens the invoices behind it. */
.kpi.go { text-align: left; width: 100%; cursor: pointer; font: inherit; }
.kpi.go:hover { border-color: var(--rule-2); background: var(--surface-2); }
.kpi.go:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* Skip to content.
   Every page puts ten navigation items before the content. Without this a
   keyboard user tabs past all of them on every single page. Off-screen until
   focused, which is the first thing Tab reaches. */
.skiplink {
  position: fixed; top: 8px; left: 8px; z-index: 200;
  transform: translateY(-200%); transition: transform var(--t);
  background: var(--brand); color: var(--surface); padding: 10px 16px;
  border-radius: var(--r); font-weight: 600; text-decoration: none;
}
.skiplink:focus { transform: none; outline: 2px solid var(--ink); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .skiplink { transition: none; } }

/* A countersignature is a detour off the route, not a fifth desk — marked so it
   reads as "stopped, waiting on somebody" rather than as another queue. */
.pill.s-pending_countersign { background: var(--warn-soft); color: var(--warn); }
.pill.s-pending_countersign .g { background: var(--warn); }

/* ============================== PRINT ==============================
   An approved invoice gets printed and put in a folder at a site, and an
   ageing report gets printed for a meeting. Until now that produced the
   navigation column, the filter bar, the buttons and a dark background,
   across three pages, with the amounts in a colour nobody's printer has.

   Two principles. Print what the paper is for and nothing else. And put
   back what a screen can imply but paper cannot: where a link went, what
   the filters were, when it was printed and by whom. */
/* ------------------------- health ------------------------- */
/*
 * A checkbox with a label that is actually clickable and actually focusable.
 * The one on the register was inline-styled; this is the shared version and the
 * register now uses it too.
 */
.chk{display:inline-flex;align-items:center;gap:7px;font-size:13px;color:var(--ink-2);cursor:pointer}
.chk input{width:14px;height:14px;accent-color:var(--brand);cursor:pointer;flex:none}
.chk:focus-within{outline:2px solid var(--brand);outline-offset:3px;border-radius:var(--r-sm)}

/*
 * The machine, one fact per row. A grid rather than a table: these are labelled
 * values, not rows of the same kind of thing, and a screen reader should not
 * have to announce "column 1 of 2" over and over to read them out.
 */
.statusgrid{display:grid;gap:1px;background:var(--rule);border:1px solid var(--rule);border-radius:var(--r)}
.kvrow{display:grid;grid-template-columns:minmax(120px,150px) 1fr;gap:14px;
  padding:10px 12px;background:var(--surface);font-size:13px;align-items:baseline}
.kvrow .k{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3)}
.kvrow .v{color:var(--ink);min-width:0;overflow-wrap:anywhere}
/*
 * Not colour alone. A row that is wrong says so in its value text as well —
 * "NOT WRITABLE", "None found" — because roughly one man in twelve cannot tell
 * these two backgrounds apart, and because this page is printed and faxed about.
 */
.kvrow.warn{background:var(--warn-soft)}
.kvrow.warn .v{color:var(--warn)}
.kvrow.bad{background:var(--bad-soft)}
.kvrow.bad .v{color:var(--bad);font-weight:600}
/*
 * The label and the explanation are --ink-3 everywhere else, which is exactly
 * 4.5:1 on the ordinary surface and 4.29:1 on these tinted ones — so the two
 * rows that most need reading were the two that failed. They step up a shade.
 */
.kvrow.warn .k, .kvrow.warn .subtle,
.kvrow.bad .k,  .kvrow.bad .subtle{color:var(--ink-2)}
@media (max-width:560px){ .kvrow{grid-template-columns:1fr;gap:3px} }

/* ---------------------- actions on the row ---------------------- */
/*
 * The desk's own action, pinned to the right edge.
 *
 * Last in the row so it reads as a sentence — this invoice, from this vendor,
 * for this much, due then, so do this — which also keeps Amount and Balance
 * adjacent for comparison. Being last means it is the column that scrolls off
 * first on a laptop, so it is sticky. The explicit row background is what makes
 * that possible: a sticky cell with a transparent background has the rest of
 * the row sliding visibly underneath it.
 */
table.tbl tbody tr { background: var(--surface); }
table.tbl tbody tr:hover { background: var(--surface-2); }
/*
 * One number, used twice: the Action column's width, and the distance Due is
 * pinned from the right edge. Written down once because they must agree
 * exactly — when they did not, Due sat 2px left of where the column actually
 * was and covered the last sliver of Balance. `box-sizing` is what makes the
 * declared width the measured width under collapsed borders.
 */
table.tbl { --actw: 132px; }
table.tbl th.act, table.tbl td.act { position: sticky; right: 0; white-space: nowrap;
  width: var(--actw); min-width: var(--actw); max-width: var(--actw); box-sizing: border-box; }
table.tbl td.act { background: inherit; padding: 3px 10px; z-index: 2; }
table.tbl th.act { background: var(--surface-2); z-index: 3; text-align: left; }

/*
 * DUE IS PINNED BESIDE ACTION, and this is not decoration.
 *
 * Nine columns of nowrap text are wider than a 1280px laptop with the sidebar
 * open — measured: 1090px of table in a 976px pane. Something has to scroll off,
 * and because Action is sticky at `right: 0`, the column that goes under it is
 * whichever is last. That was Due.
 *
 * Which made the register show "74" where it had written "74d overdue", with
 * the rest of the phrase hidden behind an Approve button — on the one screen
 * whose whole purpose is deciding what to pay next. The overlap is exactly the
 * overflow: 114px at 1280, and more than that with real supplier names, which
 * are longer than the demo's.
 *
 * So Due sticks too, immediately left of Action: how late the bill is, and the
 * button that deals with it, are the two halves of one decision and neither is
 * any use alone. What scrolls instead is the middle — service month, stage —
 * which a person is reading, not acting on.
 *
 * `background: inherit` on both, so the row's own background follows them,
 * including on hover. A sticky cell with a transparent background has the rest
 * of the row sliding visibly underneath it.
 */
table.tbl th.duecol, table.tbl td.duecol { position: sticky; right: var(--actw); }
table.tbl td.duecol { background: inherit; z-index: 2; }
table.tbl th.duecol { background: var(--surface-2); z-index: 3; }

/* And the column that gives way first, so it scrolls less often to begin with.
   The full name is in the cell's title, and on the invoice itself. */
table.tbl td.c-vendor { max-width: 200px; overflow: hidden; text-overflow: ellipsis; }

/*
 * THE LAPTOP. Nine columns, 976px of pane, and no honest way to have both.
 *
 * Pinning Due beside Action stopped Due being the column that hid — and made
 * Amount the one that hid instead, which is not an improvement on a page about
 * money. Measured at 1280 with the sidebar open: 1,089px of table in 976px of
 * pane, so 113px has to come from somewhere or something is always covered.
 *
 * It comes from the padding and from the two columns that can afford it. Cell
 * padding drops from 12px to 9px a side, which is 54px across nine columns and
 * is not visible unless you are looking for it; the supplier name and the site
 * take a ceiling and an ellipsis, with the full text in the cell's title and on
 * the invoice itself. That is 113px, and the whole row fits with nothing hidden
 * behind anything.
 *
 * Above this width there is room, and nothing is squeezed.
 */
/*
 * The register's own floor, which is what was actually doing the covering.
 *
 * It was an inline `min-width: 1090px`, set so the columns never became
 * unreadably cramped. But 1,090px of table in the 976px a 1,280px laptop leaves
 * is an overflow of exactly 114px — the same 114px every time, whatever the
 * content, which is what gave the measurement away: the columns were not too
 * wide, the floor was too high. Trimming padding moved it by a single pixel.
 *
 * So above 1400px the floor stands and the table is roomy. Below it, the floor
 * comes off, the padding tightens, and the two columns that can afford a
 * ceiling take one. The row then fits, and nothing is behind anything.
 */
table.tbl.reg { min-width: 1090px; }

@media (max-width: 1400px) {
  table.tbl.reg { min-width: 0; }
  table.tbl th, table.tbl td { padding-left: 8px; padding-right: 8px; }
  table.tbl td.c-vendor { max-width: 132px; }
  table.tbl td:nth-child(3) { max-width: 108px; overflow: hidden; text-overflow: ellipsis; }
  /* SERVICE MONTH is a wide heading over a narrow value — "May'2026" is half
     the width of the word above it, and the heading was setting the column. */
  table.tbl.reg th:nth-child(4), table.tbl.reg td:nth-child(4) { max-width: 92px;
    overflow: hidden; text-overflow: ellipsis; }
}

.rowact {
  display: inline-flex; align-items: center; gap: 5px; justify-content: center;
  /* 28 x 88 clears the 24 x 24 of WCAG 2.5.8 outright, with no need for the
     spacing exception, inside a 37px row. */
  min-height: 28px; min-width: 108px; padding: 3px 9px;
  border: 1px solid var(--rule-2); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
  font-size: 12px; font-weight: 500; line-height: 1.2; cursor: pointer;
  transition: background var(--t), border-color var(--t);
}
.rowact .g { font-size: 12.5px; line-height: 1; }
.rowact:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* Always an icon AND a word. A green tick alone is unreadable to somebody who
   has never been told what it does, and the word is what stops the fill colour
   being the only carrier of meaning. */
.rowact-ok { background: var(--ok); border-color: var(--ok); color: var(--surface); }
.rowact-ok:hover { filter: brightness(1.08); }
/* Over the desk's ceiling: a different word, a different glyph, a different
   colour. Three channels, because one of them is colour. */
.rowact-cs { background: var(--warn-soft); border-color: var(--warn); color: var(--warn); }
.rowact-cs:hover { background: var(--warn); color: var(--surface); }
/* Payment is not a tick and must not look like one — a click here opens a
   dialog and settles nothing on its own. */
.rowact-pay { border-color: var(--rule-2); color: var(--ink); }
.rowact-pay:hover { background: var(--surface-3); }
.rowact-sending { background: var(--surface-2); border-color: var(--rule-2); cursor: progress; opacity: 1; }
.rowact-done { background: transparent; border-color: transparent; color: var(--ok);
  justify-content: flex-start; padding-left: 0; text-decoration: underline; text-underline-offset: 2px; }
.rowact-done:hover { color: var(--ink); }
.rowact-retry { border-color: var(--bad); color: var(--bad); background: var(--bad-soft); }
.rowerr { font-size: 11px; color: var(--bad); white-space: normal; max-width: 132px; margin-top: 2px; }

/* The undo window. The bar is the clock; the word is the instruction. */
.rowact-undo { position: relative; overflow: hidden; background: var(--surface-3);
  border-color: var(--rule-2); color: var(--ink); }
.rowact-undo .bar { position: absolute; inset: 0 auto 0 0; width: 100%;
  background: var(--brand-soft); animation: undobar 5s linear forwards; }
.rowact-undo { z-index: 0; }
.rowact-undo .bar { z-index: -1; }
@keyframes undobar { to { width: 0; } }
@media (prefers-reduced-motion: reduce) { .rowact-undo .bar { animation: none; width: 100%; } }

/* A row on its way out, and one that would not go. Never colour alone: the
   settled row also carries its new status pill and the button says the word. */
tr.arming td:not(.act) { color: var(--ink-3); }
tr.settled { opacity: .72; }
tr.settled td:not(.act) { color: var(--ink-3); }
tr.failedrow td:first-child { box-shadow: inset 3px 0 0 var(--bad); }
li.irow.settled { opacity: .72; }
li.irow.failedrow { box-shadow: inset 3px 0 0 var(--bad); }

/* Needs a countersignature — said in the stage cell, before the click. */
.chip.cs { background: var(--warn-soft); color: var(--warn); border-color: var(--warn); }

/* How far down the list you have got. */
.rowprogress { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 9px 14px; border-bottom: 1px solid var(--rule); background: var(--surface-2);
  font-size: 12.5px; color: var(--ink-2); }
.rowprogress:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.rowprogress .txt { font-weight: 500; color: var(--ink); }
.rowprogress .btn { margin-left: auto; }

/* The invoice number, as the row's keyboard route to its own detail page. */
.linkish { background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; text-align: left; }
.linkish:hover { color: var(--brand-ink); text-decoration: underline; }
.linkish:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 2px; }

/* The card list on a phone: one stretched target for the card, one real button
   for the action, and never a button inside a button. */
.irow { position: relative; }
.irow-open { background: none; border: 0; padding: 0; font: inherit; color: inherit;
  text-align: left; cursor: pointer; min-width: 0; }
.irow-open::after { content: ""; position: absolute; inset: 0; z-index: 0; }
.irow-open:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.irow .cardact { position: relative; z-index: 1; grid-column: 3; grid-row: 1 / span 3;
  align-self: center; display: flex; align-items: center; }
.irow .cardact:empty { display: none; }
/* A fingertip wants 44, not the 24 the standard requires. */
.irow .cardact .rowact { min-height: 44px; min-width: 92px; }

@media print {
  /* Paper is white. Force the light palette regardless of the viewer's
     theme, or a dark-mode user prints white text onto white paper. */
  :root, :root[data-theme="dark"], :root:not([data-theme="light"]) {
    --ground:#FFFFFF; --surface:#FFFFFF; --surface-2:#FFFFFF; --surface-3:#F4F6F8;
    --ink:#000000; --ink-2:#26313A; --ink-3:#4A555E;
    --rule:#B9C3CB; --rule-2:#8E9AA4; --field-border:#8E9AA4;
    --brand:#0B5666; --brand-soft:#EEF5F7; --brand-ink:#0B5666;
    --ok:#0B5C3C; --warn:#7A4C05; --bad:#8C2716; --idle:#4A555E; --stamp:#8C2716;
    --shadow:none; --shadow-lg:none;
  }
  @page { margin: 14mm 12mm; }

  html, body { background:#fff !important; }
  body { font-size: 10.5pt; line-height: 1.4; }

  /* The furniture. */
  .topbar, .rail, .railscrim, .skiplink, .filters, .pager, .btnrow,
  .btn, .iconbtn, .menubtn, .notemark, #overlay, .toast, .tip,
  .shownow .fchip, .card-h .eyebrow, .noteslist, .belldot { display: none !important; }

  /* The page becomes one column with no sticky anything. */
  .app.on { display: block !important; }
  .main { padding: 0 !important; overflow: visible !important; }
  .card { border:1px solid var(--rule); box-shadow:none !important; break-inside: avoid; margin-bottom: 8mm; }
  .card-h { border-bottom:1px solid var(--rule); }
  .tablewrap { overflow: visible !important; }
  table { width:100% !important; min-width:0 !important; border-collapse: collapse; }
  thead { display: table-header-group; }   /* repeat headings across pages */
  tr { break-inside: avoid; }
  .tbl td, .tbl th { padding: 4px 6px; }

  /* A pill is a coloured background on screen and a smudge on paper. */
  .pill, .chip, .rolebadge {
    background: transparent !important; border:1px solid var(--rule-2) !important;
    color: var(--ink) !important; padding: 0 4px;
  }
  .pill .g { display: none; }

  /* Bars carry meaning, so they have to survive the printer's default of
     dropping backgrounds. */
  .hbar .track { border:1px solid var(--rule-2); }
  .hbar .fill { background: var(--ink-2) !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  /* Screens imply; paper has to say. */
  .pagehead::after {
    content: "Invoice Tracker — printed " attr(data-printed);
    display:block; margin-top:6px; font-size:8.5pt; color:var(--ink-3);
  }
  /* :not(.all) — the summary line is now always rendered, so an unfiltered
     register would otherwise print "Filtered view" underneath itself, which is
     a lie on paper and exactly the failure this block was written against. */
  .shownow:not(.all)::after {
    content: "Filtered view — the register holds more than is shown here.";
    font-size:8.5pt; color:var(--ink-3);
  }
  /* Timeline dots are decoration; the words carry the audit trail.
     Hiding the dot column is not enough: .tlitem is a 20px + 1fr grid, so the
     body slid into the 20px track and the note printed one word per line. The
     row has to stop being a grid. */
  .tlitem .col { display:none; }
  .tlitem { display:block !important; padding-left:0; break-inside: avoid; }
  .tlitem .t { display:block; }
  .tlitem .note { margin-top:2px; padding-left:8px; border-left:2px solid var(--rule); }

  /* On-screen guidance about what you may click has no meaning on paper. */
  .admincontrols, .screenonly, .goarrow,
  table.tbl th.act, table.tbl td.act, .rowprogress { display:none !important; }
  /* Action is gone on paper, so Due must not go on holding a gap where it was. */
  table.tbl th.duecol, table.tbl td.duecol { position: static !important; }
  table.tbl td.c-vendor { max-width: none; }
  .flagnote.go { border-color: var(--rule) !important; }
}

/* ── the bill: attachments ─────────────────────────────────────────────── */
.attachlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.attachrow{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--rule);
  border-radius:6px;background:var(--surface-2,var(--surface))}
.attachicon{font-family:var(--mono);font-size:9px;font-weight:600;letter-spacing:.03em;color:var(--ink-3);
  border:1px solid var(--rule-2);border-radius:3px;padding:2px 4px;flex:none}
.attachname{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}
.attachsize{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);flex:none}
.attachempty{padding:14px 10px;color:var(--ink-3);font-size:13px;border:1px dashed var(--rule-2);border-radius:6px}
.attachadd{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:10px}
.attachhint{font-size:11.5px;line-height:1.4;flex:1;min-width:220px}
/* The tab stop is the hidden <input>; the ring has to land on its label. */
.attachadd input:focus-visible + .btn{outline:2px solid var(--brand);outline-offset:2px}
@media (max-width:560px){
  .attachrow{flex-wrap:wrap}
  .attachname{flex-basis:100%;order:-1}
}

/* ── the service month: two lists, one field ───────────────────────────── */
.monthpick{display:flex;gap:6px}
.monthpick select{min-width:0;flex:1}
.monthkept{display:block;font-size:11.5px;line-height:1.4;margin-top:5px;color:var(--warn)}

/* ── native date fields, made to look like every other field ───────────── */
/*
 * A date input cannot be fully restyled — the picker belongs to the browser and
 * that is the right trade, because it is the control people already know and it
 * works on a phone. What CAN be made consistent is everything around it, and it
 * was not: the field sat with the browser's own font, its own metrics and, in
 * the dark theme, its own colours, next to eleven fields that follow the theme.
 *
 * `color-scheme` is the load-bearing line. Without it the calendar the browser
 * draws is white with black text on top of a dark page, and the digits inside
 * the field itself do not follow --ink either.
 */
.inp[type="date"]{
  color-scheme:light;
  font-family:var(--sans);
  font-size:13.5px;
  color:var(--ink);
  background:var(--surface);
}
:root[data-theme="dark"] .inp[type="date"]{color-scheme:dark}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .inp[type="date"]{color-scheme:dark}
}
/* The little calendar button: sized to a real target and tinted to the theme. */
.inp[type="date"]::-webkit-calendar-picker-indicator{
  cursor:pointer;
  opacity:.55;
  padding:3px;
  border-radius:3px;
  transition:opacity var(--t),background var(--t);
}
.inp[type="date"]::-webkit-calendar-picker-indicator:hover{opacity:1;background:var(--surface-3)}
.inp[type="date"]::-webkit-datetime-edit{padding:0;color:var(--ink)}
.inp[type="date"]::-webkit-datetime-edit-fields-wrapper{padding:0}
/* The segment being edited, tinted in the app's own colours. The browser's own
   selection indicator is left alone underneath — this is added to it, not put
   in place of it. */
.inp[type="date"]::-webkit-datetime-edit-day-field:focus,
.inp[type="date"]::-webkit-datetime-edit-month-field:focus,
.inp[type="date"]::-webkit-datetime-edit-year-field:focus{
  background:var(--brand-soft);color:var(--brand-ink);border-radius:2px;
}

/* Which build is running, at the foot of the account menu. */
.menubuild{padding:8px 12px;border-top:1px solid var(--rule);font-size:11px;line-height:1.4}
.menubuild .mono{font-family:var(--mono);color:var(--ink-2)}
