/* ===========================================================================
   TruckMe portal — the 2026 pass
   ---------------------------------------------------------------------------
   This file is loaded after style.css and is the whole redesign: the older
   sheet stays on disk untouched, so taking one <link> out of base.html puts
   the portal back exactly as it was.

   What was wrong, in the words of the people using it: the figures at the top
   ate half a screen before the first row of data, tables ran off the side,
   a row stood five lines tall, and the filters took more room than the thing
   they filtered. Underneath all four is one cause -- the layout measured the
   *window* and the work happens in a *pane*. So every rule here measures the
   container it is in, and the page reflows whether it has 1500px or 600px.

   Brand colours are the ones on the invoice a customer receives and are not
   touched: TRUCK.ME orange to act on, navy for structure.
   =========================================================================== */

:root {
  /* a shorter vertical rhythm: this is a working screen, not a landing page */
  --gap: 9px;
  --gap-lg: 13px;
  --pad-card: 11px 13px;
  --row-h: 30px;

  /* geometry, calmer than before -- big radii read as "app chrome" and this
     product is mostly tables */
  --r-lg: 12px;
  --r: 9px;
  --r-sm: 7px;

  --shadow-sm: 0 1px 1px rgba(16, 24, 40, .035);
  --shadow: 0 1px 2px rgba(16, 24, 40, .05);
  --shadow-lg: 0 10px 28px rgba(16, 24, 40, .13);

  --sidebar-w: 196px;

  /* One ramp for text, used everywhere instead of ad-hoc sizes.
     Sized for the screen the work is actually done on: a 15-inch laptop,
     where the old ramp -- 10px captions, 12px body -- was readable in a
     screenshot and tiring for a shift. Every step is a point larger, which
     is about a tenth: enough to stop squinting, not so much that the tables
     stop fitting. The column breakpoints further down moved with it. */
  --fs-micro: 11px;
  --fs-small: 12px;
  --fs-body: 13px;
  --fs-mid: 14px;
  --fs-head: 17px;
  --fs-figure: 19px;

  --rail-line: #eceef2;
}

body {
  font-size: var(--fs-mid);
  line-height: 1.35;
}

/* numbers are read in columns, so they line up in columns */
.num, .value, .mc-amt, .tile .value, .statbar .stat .v,
.grid td.num, .grid th.num, .r-amount {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ---------------------------------------------------------------------------
   The frame. `.content` and every card become measurable containers, which is
   what lets the same page sit in a full window and in half of one.
   --------------------------------------------------------------------------- */
.content {
  container: content / inline-size;
  padding: 10px 14px 20px;
  max-width: 1900px;
}
.card {
  container: card / inline-size;
  padding: var(--pad-card);
  margin-bottom: var(--gap);
  border-radius: var(--r-lg);
}
.card > :last-child { margin-bottom: 0; }

.page-head { margin-bottom: var(--gap); gap: var(--gap); align-items: center; }
.page-title { font-size: var(--fs-head); letter-spacing: -.01em; margin: 0; }
.page-sub {
  font-size: var(--fs-small); color: var(--text-muted); margin: 2px 0 0;
  max-width: 78ch;
}
.section-title { font-size: var(--fs-body); margin-bottom: 6px; }
.mt { margin-top: 6px; }

/* the sidebar is navigation, not furniture */
.sidebar nav a { padding: 5px 10px; font-size: var(--fs-body); border-radius: var(--r-sm); }
.sidebar nav a svg { width: 15px; height: 15px; }
.nav-group { font-size: var(--fs-micro); letter-spacing: .1em; margin: 10px 0 3px; }
.brand { padding: 10px 12px; }
.brand .name { font-size: var(--fs-mid); }
.brand .sub { font-size: var(--fs-micro); }
.topbar { height: 40px; font-size: var(--fs-small); }
.who { padding: 8px 10px; }

/* ---------------------------------------------------------------------------
   1. The figures: a rail, not a wall of cards
   ---------------------------------------------------------------------------
   Six tiles at 110px tall pushed the data below the fold on every screen.
   The same six numbers live in one strip a third of the height, divided by
   hairlines instead of gaps and borders. In a narrow pane the strip scrolls
   sideways as one object -- one small gesture, instead of a tower of cards
   the length of the page.
   --------------------------------------------------------------------------- */
.tiles {
  display: flex;
  gap: 0;
  margin-bottom: var(--gap);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}
.tile {
  flex: 1 1 0;
  min-width: 132px;
  padding: 7px 12px;
  border: 0;
  border-right: 1px solid var(--rail-line);
  border-radius: 0;
  box-shadow: none;
  background: transparent;
}
.tile:last-child { border-right: 0; }
.tile .label {
  font-size: var(--fs-micro); letter-spacing: .07em; gap: 5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile .label:before { width: 5px; height: 5px; flex-basis: 5px; }
.tile .value { font-size: var(--fs-figure); margin-top: 1px; letter-spacing: -.02em; }
.tile .sub {
  font-size: var(--fs-micro); margin-top: 1px; color: var(--text-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* the same idea where a page already used a stat bar */
.statbar { margin-bottom: var(--gap); border-radius: var(--r-lg); }
.statbar .stat { padding: 6px 11px; }
.statbar .stat .v { font-size: var(--fs-figure); }
.statbar .stat .k { font-size: var(--fs-micro); }

/* ---------------------------------------------------------------------------
   2 & 3. Tables: one line per row, and the width the container actually has
   ---------------------------------------------------------------------------
   A row was five lines tall because every cell stacked a second, dimmer line
   under the first. That second line is context, not data -- so it stays, on
   the same line, dimmed and truncated, and the row is back to one line. The
   table then fits, and a table that fits does not scroll sideways.
   --------------------------------------------------------------------------- */
.table-wrap {
  overflow: auto;
  max-height: 72vh;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
}
.in-pane .table-wrap { max-height: none; }

.grid { width: 100%; border-collapse: separate; border-spacing: 0; }

/* the heading stays while the rows go past it */
.grid thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--navy);
  color: #fff;
  font-size: var(--fs-micro);
  letter-spacing: .06em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 6px 8px;
  white-space: nowrap;
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, .18);
}
.grid tfoot th, .grid tfoot td {
  position: sticky; bottom: 0; z-index: 2;
  background: var(--surface-2);
  border-top: 1px solid var(--border-strong);
  font-size: var(--fs-small);
}

.grid td {
  padding: 4px 8px;
  font-size: var(--fs-body);
  border-bottom: 1px solid var(--rail-line);
  vertical-align: middle;
  height: var(--row-h);
}
.grid tbody tr:last-child td { border-bottom: 0; }
.grid tbody tr:nth-child(even) td { background: #fcfcfd; }
.grid tbody tr:hover td { background: var(--brand-soft); }

/* every cell is one line; the long ones cut with an ellipsis and keep the
   whole text in the tooltip the browser already shows */
.grid td, .grid th { white-space: nowrap; }
.grid td.small, .grid td:not(.num):not(.nowrap) {
  max-width: 28ch;
  overflow: hidden;
  text-overflow: ellipsis;
}
.grid td.num, .grid th.num { max-width: none; text-align: right; }

/* Every cell in this product stacks: a value, then a dimmer line of context
   under it, sometimes three. That is what made a row five lines tall. The
   context is worth keeping and is not worth a line of its own, so the stack
   lies down: same cell, same order, separated by a middot. The inline
   font-size the templates carry is overridden -- it was there to make the
   second line small, and the rule below already does that.

   The kinds of div are named one by one rather than "every div in a cell":
   the same cells also carry the dialogs that open from them, and those must
   keep the display they were given. */
.grid td > div.muted,
.grid td > div.nowrap,
.grid td > div.small,
.grid td > div:not([class]) {
  display: inline;
  font-size: var(--fs-small) !important;
}
.grid td > div.muted:before,
.grid td > div.small:before { content: " · "; color: var(--text-soft); }
.grid td > div.muted { color: var(--text-soft); }

/* a cell that genuinely needs its lines keeps them */
.grid td.stacked, .grid td.lines { white-space: normal; max-width: 34ch; }
.grid td.stacked > div, .grid td.lines > div { display: block; }
.grid td.stacked > div:before, .grid td.lines > div:before { content: ""; }

.grid .badge {
  padding: 1px 7px; font-size: var(--fs-micro); font-weight: 700;
  vertical-align: baseline;
}
.grid .btn.xs, .grid .btn.sm { padding: 2px 7px; font-size: var(--fs-micro); }
.grid .linkbtn { font-size: var(--fs-small); }
.grid input[type="checkbox"] { width: 13px; height: 13px; }

/* Column priority. Truncating text is not enough for a table of a dozen
   columns: at some width the only honest answer is that a column does not
   fit, and the question is which one goes. `.opt` is the first to go and
   `.opt2` the next, put on the heading and the cell alike, so a table
   narrows down to the columns somebody actually works from instead of
   sliding sideways under the hand. Nothing is lost -- widen the pane, or
   open the row, and it is back. */
/* Measured on the invoice table, which is the widest in the portal: its full
   set of columns needs 1292px of table, the next 999, then 709, then 592 --
   plus about seventy of page around them. The old thresholds were each a
   tier too late, so between them the table showed every column and then
   scrolled sideways to do it, which is the thing these classes exist to
   prevent. A narrower table drops an optional column a little earlier than
   it has to; that is what optional means. */
@container content (max-width: 1490px) { .grid .opt { display: none !important; } }
@container content (max-width: 1350px) {
  /* beats the rule that lays a cell's second line down inline */
  .grid .opt2 { display: none !important; }
  /* a heading may take two short lines rather than force a wide column:
     it is sticky, so the cost is paid once and not per row */
  .grid thead th { white-space: normal; line-height: 1.15; }
}
@container content (max-width: 920px) { .grid .opt3 { display: none !important; } }

/* the first column carries the identity of the row, so it stays put */
.grid.pin-first tbody td:first-child,
.grid.pin-first thead th:first-child { position: sticky; left: 0; z-index: 1; }
.grid.pin-first tbody td:first-child { background: var(--surface); }

/* ---------------------------------------------------------------------------
   4. Filters: a toolbar, one line high
   --------------------------------------------------------------------------- */
/* The bar above a table was three rows high -- tabs, then fields with a label
   over each, then the buttons. It is one row now: the field labels sit beside
   their boxes rather than above them, which is the whole difference. */
.filterbar {
  gap: 6px 10px;
  padding: 0 0 6px;
  margin-bottom: 6px;
  align-items: center;
}
.fb-tabs { padding: 2px; border-radius: var(--r-sm); gap: 2px; }
.fb-tab { padding: 4px 9px; font-size: var(--fs-body); border-radius: var(--r-sm); gap: 5px; }
.fb-tab .n { font-size: var(--fs-micro); padding: 0 5px; }
.fb-form { gap: 6px; align-items: center; align-content: flex-start; }
.fb-field { flex-direction: row; align-items: center; gap: 5px; }
.fb-field.wide { flex: 1 1 260px; }
.fb-field.wide input { width: 100%; }
.fb-field .k {
  font-size: var(--fs-micro); letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-soft); font-weight: 700; white-space: nowrap;
}
.fb-field input, .fb-field select {
  padding: 4px 7px; font-size: var(--fs-body); border-radius: var(--r-sm);
}
.fb-count { font-size: var(--fs-small); color: var(--text-muted); }

.filters { gap: 6px; align-items: flex-end; align-content: flex-start; }
.field { font-size: var(--fs-micro); gap: 1px; }
.field input, .field select, .field textarea {
  padding: 4px 8px; font-size: var(--fs-body); border-radius: var(--r-sm);
}
.chip { padding: 3px 8px; font-size: var(--fs-small); }

.btn { padding: 5px 11px; font-size: var(--fs-body); border-radius: var(--r-sm); }
.btn.sm { padding: 3px 9px; font-size: var(--fs-small); }
.btn.xs { padding: 2px 7px; font-size: var(--fs-micro); }
.badge { padding: 2px 8px; font-size: var(--fs-micro); font-weight: 700; }
.tabs a { padding: 4px 10px; font-size: var(--fs-body); }
.muted.small, .small { font-size: var(--fs-small); }

/* ---------------------------------------------------------------------------
   Vendor cards
   --------------------------------------------------------------------------- */
.vendor-grid { grid-template-columns: repeat(auto-fill, minmax(252px, 1fr)); gap: var(--gap); }
.vendor-card { padding: 8px 10px; border-radius: var(--r); }
.vc-name { font-size: var(--fs-body); }
.acct-figs { font-size: var(--fs-body); }
.acct-meta { font-size: var(--fs-micro); }

/* ---------------------------------------------------------------------------
   The container steps. Each one is the same page, told how much room it has.
   --------------------------------------------------------------------------- */

/* a pane of about half a wide window */
@container content (max-width: 1080px) {
  .tile { min-width: 118px; padding: 6px 10px; }
  .tile .value { font-size: 15px; }
  .grid td:not(.num):not(.nowrap) { max-width: 22ch; }
  .page-sub { max-width: 60ch; }
}

/* a narrow pane: the page stops pretending it is a desktop */
@container content (max-width: 760px) {
  .page-head { flex-direction: column; align-items: stretch; }
  .page-head .row { justify-content: flex-start; }
  .filterbar { align-items: flex-start; align-content: flex-start; gap: 6px; }
  .fb-tabs { overflow-x: auto; }
  .fb-form { width: 100%; }
  .fb-field { flex: 1 1 130px; }
  .fb-field.wide { flex: 1 1 100%; }
  .tiles { border-radius: var(--r); }
  .tile { min-width: 104px; }
  .tile .value { font-size: 14px; }
  .tile .sub { display: none; }        /* the number is the point down here */
  .grid td:not(.num):not(.nowrap) { max-width: 16ch; }
  .grid thead th, .grid td { padding: 3px 5px; }
  .grid td { font-size: var(--fs-small); }
  .content { padding: 8px 10px 16px; }
}

/* very narrow -- a phone, or a pane dragged down to a third */
@container content (max-width: 520px) {
  .tile .label { font-size: 9px; }
  .grid td:not(.num):not(.nowrap) { max-width: 12ch; }
  .page-sub { display: none; }
}

/* cards hold tables too, and they are narrower than the page */
@container card (max-width: 620px) {
  .grid td:not(.num):not(.nowrap) { max-width: 14ch; }
}

/* ---------------------------------------------------------------------------
   Split panes: the page inside one is already tight, so it starts there
   --------------------------------------------------------------------------- */
.in-pane .content { padding: 8px 10px 14px; }
.in-pane .page-head { margin-bottom: 6px; }
.in-pane .page-sub { display: none; }   /* the pane's own bar says where it is */

/* ---------------------------------------------------------------------------
   Print: the portal is exported as often as it is read
   --------------------------------------------------------------------------- */
@media print {
  .sidebar, .topbar, .filterbar, .banner-wall, .chat-handle { display: none !important; }
  .table-wrap { max-height: none; overflow: visible; border: 0; }
  .grid thead th { position: static; background: #fff; color: #000; border-bottom: 2px solid #000; }
}

/* ---------------------------------------------------------------------------
   The confirmed-balance tag
   ---------------------------------------------------------------------------
   The density pass made the rest of the card quieter and took this with it:
   a 9px dot that was plain at the old scale is easy to miss at the new one,
   and a green tint at ten per cent stopped reading as green at all. Nothing
   was ever removed -- it just stopped announcing itself, so here it does.

   Green means somebody with the whole picture says this balance is current,
   and only a balance like that reaches the wall screen. It is the difference
   between a figure to act on and last week's arithmetic, so it earns its
   contrast; the empty ring is deliberately quiet by comparison.
   --------------------------------------------------------------------------- */
.dot-ok, .dot-off {
  width: 11px; height: 11px; margin-right: 6px;
  position: relative; top: -1px;
}
.dot-ok {
  background: #16a34a;
  box-shadow: 0 0 0 3px rgba(22, 163, 74, .20);
}
.dot-off { border: 2px solid #ccd2dc; background: #fff; }

/* on the vendor card the tag is also the switch, so it reads as a state */
.tagbtn { padding: 3px 10px; font-size: var(--fs-small); font-weight: 650; }
.tagbtn.on {
  background: rgba(22, 163, 74, .13);
  color: #14682f;
  border-color: rgba(22, 163, 74, .38);
  font-weight: 700;
}
button.tagbtn.on:hover { background: rgba(22, 163, 74, .20); border-color: #16a34a; }
/* inside a green tag the dot has the tag's own colour behind it, so the ring
   would only muddy it */
.tagbtn.on .dot-ok { box-shadow: 0 0 0 2px rgba(22, 163, 74, .28); }

/* The account line on the dashboard. The dot sits inside the name's ellipsis
   box, so the box stays a plain line of text -- made a flex container it
   squeezed ORDINARY down to two letters -- and the name is given back the
   width the dot takes. */
/* every card its own height: the grid used to stretch a row to its tallest
   card, which is where the empty half-cards came from */
.vendor-grid { align-items: start; }

/* --- the card, read top to bottom ------------------------------------- */
.vendor-card { padding: 9px 11px; }
.vc-head { align-items: baseline; gap: 6px; }
.vc-name { font-size: 12.5px; font-weight: 700; letter-spacing: -.01em; }
.vc-head .oval {
  font-size: 9.5px; padding: 1px 6px; flex: 0 1 auto;
  max-width: 42%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* the account: its name is a label, not a heading */
.acct { margin-top: 7px; }
.acct.first { margin-top: 4px; }
.acct-line { display: block; }
.acct-name { display: block; margin-bottom: 1px; }

/* the one number on the card */
.acct-figs { display: block; font-size: 11px; line-height: 1.25; }
.acct-figs b {
  display: block; margin: 0;
  font-size: 16px; font-weight: 750; letter-spacing: -.02em; line-height: 1.15;
}
.fig-of { color: var(--text-soft); font-size: 10.5px; }

/* the bar is a hint, not a stripe */
.bar { height: 3px; margin-top: 4px; }

/* the detail, quiet and on its own lines */
.acct-meta { margin-top: 4px; font-size: 10px; gap: 4px 6px; line-height: 1.3; }
.acct-meta .badge { font-size: 9.5px; padding: 0 5px; }
.acct-meta .warm { color: var(--amber); font-weight: 650; }
.acct-line .dot-ok, .acct-line .dot-off {
  display: inline-block;
  vertical-align: middle;
  margin-right: 5px;
  top: -1px;
}

/* ---------------------------------------------------------------------------
   The vendor's head: one row instead of four stacked blocks
   ---------------------------------------------------------------------------
   Bank details, the private notes, the EFS spellings and the account card
   each took the full width and the page was three screens deep before the
   first invoice. They are not the same kind of thing: the account card is
   the money, the other three describe the shop. So the money stays on the
   left where it has always been, and the three fill the room beside it.
   --------------------------------------------------------------------------- */
.vc-split {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--gap);
  margin-top: var(--gap);
}
/* The top row is two cards, and which side they fall on is the point: what
   the shop has turned over and what we take is on the left, what is left on
   the line is on the right. They are read together -- "we billed this much
   and there is this much room" -- so they sit side by side rather than one
   above the other, and both are blocks rather than strips so the eye lands
   on a shape instead of running along a line. */
.vc-split > .statbar {
  order: -2;
  flex: 1 1 38%;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-self: stretch;
}
/* a grid draws its own lines: the right-hand column and the last row have
   nothing to separate them from */
.vc-split > .statbar .stat { border-right: 1px solid var(--border); }
.vc-split > .statbar .stat:nth-child(2n) { border-right: none; }
/* seven tiles in two columns leaves a hole; the last one fills the row */
.vc-split > .statbar .stat:last-child { grid-column: 1 / -1; border-right: none; }
.vc-split > .statbar .stat + .stat { border-top: 1px solid var(--border); }
.vc-split > .statbar .stat:nth-child(2) { border-top: none; }

.vc-split > #account-cards {
  order: -1;
  flex: 1 1 56%;
  margin-top: 0;
  /* A card has a heading, a figure and four short rows. Given a whole row to
     fill it stretched into a ribbon with the numbers at opposite ends, which
     is further for the eye to travel for less. Each one stops at a width it
     can be read in; several sit side by side, one just stays that size. */
  grid-template-columns: repeat(auto-fit, minmax(246px, 430px));
  justify-content: start;
}
.vc-split > .card,
.vc-split > .bankbar {
  flex: 1 1 300px;
  margin-top: 0;
  margin-bottom: 0;
  min-width: 0;
}
/* the notes box was four rows tall for a note nobody had written yet */
.vc-split #vendor-notes textarea { min-height: 62px; }
/* the bank block holds a name, an account number and two buttons: squeezed
   into a third of the row they collided, so it takes a wider share and wraps
   inside itself rather than on top of itself */
.vc-split > .bankbar {
  flex: 1 1 420px;
  flex-wrap: wrap;
  gap: 6px 10px;
  align-items: center;
  align-self: flex-start;   /* hug the content, do not stretch to the row */
}

/* The chat dock takes 386px of a vendor page, so a 1500px window leaves
   about 860px here -- the row has to work at that width, not only at a
   theoretical full one. Below 700px there is genuinely no room. */
/* The chat dock takes 386px, so a 1500px window leaves about 860px here --
   and side by side has to survive that, because that is the window the work
   is actually done in. Below 760 it genuinely stops fitting. */
@container content (max-width: 760px) {
  .vc-split > .statbar { flex: 1 1 100%; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .vc-split > #account-cards { flex: 1 1 100%; }
}
@container content (max-width: 480px) {
  .vc-split > .statbar { grid-template-columns: minmax(0, 1fr); }
  .vc-split > .statbar .stat { border-right: none; }
  .vc-split > .card, .vc-split > .bankbar { flex: 1 1 100%; }
}

/* the sync, while it is running */
.sync-progress[hidden] { display: none !important; }
.sync-progress {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-small); color: var(--text-muted);
  max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sync-spin {
  width: 11px; height: 11px; border-radius: 50%;
  border: 2px solid var(--brand); border-right-color: transparent;
  animation: sync-turn .8s linear infinite; flex: 0 0 auto;
}
@keyframes sync-turn { to { transform: rotate(360deg); } }

/* the notification sound switch */
.sound-toggle {
  border: 1px solid var(--border); background: var(--surface);
  border-radius: var(--r-sm); cursor: pointer;
  font-size: 12px; line-height: 1; padding: 4px 7px; margin-right: 8px;
}
.sound-toggle:hover { border-color: var(--brand); }
.sound-toggle.off { opacity: .5; }

/* ---------------------------------------------------------------------------
   The top bar at any zoom
   ---------------------------------------------------------------------------
   `.sync-info` is one flex row with no wrap: zoom in and the clocks push the
   Sync button and the progress strip off the end of a bar that does not
   scroll, so the thing telling you a sync is running is the first to go.
   The bar wraps now, and the strip is the piece that never gives way: it
   holds its place and drops the clocks instead.
   --------------------------------------------------------------------------- */
.topbar { height: auto; min-height: 40px; flex-wrap: wrap; gap: 4px 10px; padding: 4px 16px; }
.sync-info { flex-wrap: wrap; row-gap: 4px; min-width: 0; }
.sync-info > span, .sync-info > form { flex: 0 0 auto; }
/* what is running matters more than what time it is */
.sync-progress { flex: 0 0 auto; order: -1; }
#sync-form { order: -2; }
@media (max-width: 1100px) {
  .topbar .clocks { display: none; }
}

/* ---------------------------------------------------------------------------
   Settings that fold away
   ---------------------------------------------------------------------------
   A screen is for the thing it is named after. Vendor Configuration is for
   the list of shops, and two cards of settings that change once a quarter
   were standing in front of it. They fold, and the fold is remembered, so
   for anybody who never opens them the list starts at the top.
   --------------------------------------------------------------------------- */
.settings-fold { margin-bottom: var(--gap); }
.settings-fold > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 4px 10px 4px 22px;
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  font-size: var(--fs-body);
  font-weight: 650;
  color: var(--navy-ink);
}
.settings-fold > summary::-webkit-details-marker { display: none; }
.settings-fold > summary:before {
  content: "";
  position: absolute; left: 9px; top: 50%;
  width: 5px; height: 5px;
  border-right: 1.6px solid var(--text-soft);
  border-bottom: 1.6px solid var(--text-soft);
  transform: translateY(-70%) rotate(-45deg);
  transition: transform .12s ease;
}
.settings-fold[open] > summary:before { transform: translateY(-30%) rotate(45deg); }
.settings-fold > summary:hover { border-color: var(--brand); color: var(--brand); }
.settings-fold[open] > summary { margin-bottom: 6px; }

/* terms both accounts share, said once above them */
.acct-shared {
  margin-top: 3px;
  font-size: 10px;
  color: var(--text-muted);
  line-height: 1.3;
}
.acct-shared .muted { color: var(--text-soft); }
/* with the terms above, an account needs less room beneath it */
.vendor-card .acct + .acct { border-top: 1px solid var(--rail-line); padding-top: 6px; }

/* ---------------------------------------------------------------------------
   Two accounts, one set of terms
   ---------------------------------------------------------------------------
   The terms are already on the card, so each account is down to its name and
   its figure -- which is a line, not a block. The figure keeps its weight so
   the eye still lands on it; what changes is that it stops being the start of
   a paragraph.
   --------------------------------------------------------------------------- */
.acct.compact { margin-top: 5px; }
.acct.compact .acct-line {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
}
.acct.compact .acct-name { display: inline; margin: 0; flex: 0 0 auto; }
.acct.compact .acct-figs {
  display: flex; align-items: baseline; gap: 6px; text-align: right;
  flex: 0 1 auto; min-width: 0;
}
.acct.compact .acct-figs b { display: inline; font-size: 14px; line-height: 1.2; }
.acct.compact .fig-of { white-space: nowrap; }
/* the bar under a one-line account is a hairline */
.acct.compact .bar { height: 2px; margin-top: 3px; }
.acct.compact .acct-meta { margin-top: 2px; }

/* ===========================================================================
   The paying screen
   ---------------------------------------------------------------------------
   Somebody covering the payer for a week should not have to learn a desk.
   This is built the way a bank builds its own payment page: white, wide
   margins, one amount per row at a size nobody misreads, the details to send
   to spelled out, and a single button. The portal's own density rules are
   deliberately overruled here -- everywhere else the job is to fit a hundred
   rows on a screen; here the job is to make four payments without a mistake.
   =========================================================================== */
.pay-page { max-width: 860px; margin: 0 auto; padding: 8px 0 40px; }

.pay-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; margin-bottom: 18px; flex-wrap: wrap;
}
.pay-head h1 { font-size: 22px; font-weight: 700; margin: 0; letter-spacing: -.02em; }
.pay-head p { margin: 3px 0 0; color: var(--text-muted); font-size: 13px; }
.pay-done-note { font-size: 12px; color: var(--text-muted); text-align: right; }
.pay-done-note b { display: block; font-size: 17px; color: var(--green); }
.pay-done-note span { display: block; }

.pay-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 20px 22px;
  margin-bottom: 14px;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .05);
}
.pay-card.urgent { border-left: 4px solid var(--brand); }

.pay-top {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 20px; flex-wrap: wrap;
}
.pay-label {
  font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-soft); font-weight: 700;
}
.pay-who h2 { margin: 3px 0 0; font-size: 19px; font-weight: 700; letter-spacing: -.01em; }
.pay-sub { margin-top: 3px; font-size: 12px; color: var(--text-muted); }
.pay-amount { text-align: right; }
.pay-figure {
  font-size: 30px; font-weight: 750; letter-spacing: -.03em; line-height: 1.1;
  margin-top: 2px; font-variant-numeric: tabular-nums;
}
.pay-urgent { font-size: 11px; color: var(--brand); font-weight: 650; margin-top: 2px; }

.pay-to { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--rail-line); }
.pay-to-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px 18px;
}
.pay-to-grid span {
  display: block; font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-soft); font-weight: 700;
}
.pay-to-grid b { font-size: 13.5px; font-weight: 600; word-break: break-word; }
.pay-acct { font-variant-numeric: tabular-nums; letter-spacing: .02em; }
.pay-reveal {
  margin-top: 10px; background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--navy); font-size: 12px; font-weight: 650; text-decoration: underline;
}
.pay-reveal:disabled { color: var(--text-soft); text-decoration: none; cursor: default; }
.pay-nobank { font-size: 13px; color: var(--amber); }

.pay-do { margin-top: 16px; }
.pay-drop {
  display: block; text-align: center; cursor: pointer;
  border: 1.5px dashed var(--border-strong); border-radius: 12px;
  padding: 18px 14px; background: var(--surface-2);
}
.pay-drop:hover { border-color: var(--brand); background: var(--brand-soft); }
/* the input is still there and still does the work; it is the label that is
   shown, so the box says one thing in one voice */
.pay-drop input[type="file"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer;
}
.pay-drop { position: relative; }
/* once something is attached the box says so */
.pay-drop.has-file { border-style: solid; border-color: var(--green); background: var(--green-soft); }
.pay-drop.has-file .pay-drop-text:after { content: " ¹3"; color: var(--green); }
.pay-drop-text { display: block; font-size: 14px; font-weight: 650; }
.pay-drop-hint { display: block; font-size: 11.5px; color: var(--text-muted); margin-top: 2px; }

.pay-actions {
  display: flex; gap: 10px; align-items: center; margin-top: 14px; flex-wrap: wrap;
}
.pay-txn {
  flex: 1 1 220px; padding: 11px 13px; font-size: 14px;
  border: 1px solid var(--border-strong); border-radius: 9px;
  font-variant-numeric: tabular-nums;
}
.pay-button {
  flex: 0 0 auto; padding: 12px 26px; font-size: 15px; font-weight: 700;
  border: 0; border-radius: 9px; cursor: pointer;
  background: var(--brand); color: #fff;
}
.pay-button:hover { background: var(--brand-hover); }
.pay-button:disabled { background: var(--surface-3); color: var(--text-soft); cursor: default; }

.pay-empty {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 44px 22px; text-align: center;
}
.pay-empty h2 { margin: 0; font-size: 18px; font-weight: 700; }
.pay-empty p { margin: 6px 0 0; color: var(--text-muted); font-size: 13px; }

.pay-ledger { margin-top: 26px; }
.pay-ledger h3 {
  font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-soft); font-weight: 700; margin: 0 0 8px;
}
.pay-ledger-row {
  display: flex; align-items: baseline; gap: 12px;
  padding: 9px 2px; border-bottom: 1px solid var(--rail-line); font-size: 13px;
}
.pl-vendor { font-weight: 650; flex: 1 1 auto; }
.pl-what { color: var(--text-soft); font-size: 11.5px; }
.pl-amount { font-weight: 700; font-variant-numeric: tabular-nums; }

/* the seat has one screen, so it has no sidebar to navigate with */
body.pay-only .sidebar { display: none; }
body.pay-only .content { max-width: none; padding-top: 14px; }

/* the account the money leaves from, and the portal's own name for the shop */
.pay-from {
  margin-left: 8px; padding: 1px 7px; border-radius: var(--r-pill);
  background: var(--navy-soft); color: var(--navy-ink);
  font-size: 10px; letter-spacing: .04em; font-weight: 700;
  text-transform: none;
}
.pay-alias { font-size: 11.5px; color: var(--text-soft); margin-top: 1px; }

/* the one line the payer reads about this account */
.pay-hint-form { display: flex; gap: 5px; margin-top: 6px; }
.pay-hint-form input {
  flex: 1 1 auto; min-width: 0;
  padding: 3px 7px; font-size: 11px; border-radius: var(--r-sm);
  border: 1px solid var(--border);
}
.pay-hint-form input:focus { border-color: var(--brand); outline: none; }
.pay-hint-shown {
  margin-top: 6px; padding: 3px 8px; border-radius: var(--r-sm);
  background: var(--amber-soft); color: var(--amber);
  font-size: 11px; font-weight: 650;
}

/* the account's note, in the panel the payer opens */
.r-hint {
  display: inline-block; margin-top: 3px;
  padding: 2px 8px; border-radius: var(--r-sm);
  background: var(--amber-soft); color: var(--amber);
  font-size: 11px; font-weight: 700;
}

/* ---------- the document panel -------------------------------------------
   A shop's invoice is a PDF, and reading it means reading it against our own
   table. So it opens beside the page, not over it: the page keeps its scroll
   and its clicks, and the divider is draggable.                            */
:root { --doc-viewer-w: 560px; }

.doc-viewer {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: var(--doc-viewer-w);
  max-width: 100vw;
  display: flex;
  flex-direction: column;
  background: var(--card, #fff);
  border-left: 1px solid var(--line, #e3e6ea);
  box-shadow: -8px 0 24px rgba(16, 24, 40, .10);
  z-index: 70;
}
.doc-viewer[hidden] { display: none !important; }

/* the page gives up exactly the panel's width, so nothing sits underneath it */
body.dv-open { padding-right: var(--doc-viewer-w); }
body.dv-open .topbar,
body.dv-open .banner-wall { padding-right: var(--doc-viewer-w); }
body.dv-dragging { cursor: col-resize; user-select: none; }
body.dv-dragging .doc-viewer iframe { pointer-events: none; }  /* the drag would be eaten */

.dv-grip {
  position: absolute; left: -3px; top: 0; bottom: 0; width: 7px;
  cursor: col-resize; z-index: 2;
}
.dv-grip:hover { background: var(--accent, #2f6feb); opacity: .35; }

.dv-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px;
  border-bottom: 1px solid var(--line, #e3e6ea);
  background: var(--card-2, #f7f8fa);
  flex: 0 0 auto;
}
.dv-name {
  font-weight: 600; font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: 1 1 auto; min-width: 0;
}
.dv-tools { display: flex; gap: 4px; flex: 0 0 auto; align-items: center; }

.dv-body { flex: 1 1 auto; overflow: auto; background: var(--bg, #eef1f5); }
.dv-body iframe { width: 100%; height: 100%; border: 0; display: block; background: #fff; }

.dv-img { padding: 10px; text-align: center; }
.dv-img img { max-width: 100%; height: auto; cursor: zoom-in; border-radius: 6px; }
.dv-img[data-zoom="1"] { overflow: auto; }
.dv-img[data-zoom="1"] img { max-width: none; width: 190%; cursor: zoom-out; }

.dv-plain { padding: 24px; text-align: center; }
.dv-plain .btn { margin-top: 10px; }

/* The chat dock lives on the right too. Rather than sit on top of each other,
   the dock steps aside by exactly the panel's width: on a wide screen the
   chat, the document and the table are all readable at once, and the space
   the dock holds is already counted before the panel takes its share. */
body.dv-open:not(.dv-overlay) .chatdock { right: var(--doc-viewer-w); }

/* Not enough room for a document and a table at once -- a phone, or a narrow
   window on a small screen. The panel stops splitting and covers the page;
   the script decides when, because it is the sidebar that settles it. */
body.dv-overlay .doc-viewer { width: 100vw; }
body.dv-overlay.dv-open { padding-right: 0; }
body.dv-overlay.dv-open .topbar,
body.dv-overlay.dv-open .banner-wall { padding-right: 0; }
body.dv-overlay .dv-grip { display: none; }

@media (max-width: 820px) {
  .doc-viewer { width: 100vw; }
  body.dv-open { padding-right: 0; }
  body.dv-open .topbar, body.dv-open .banner-wall { padding-right: 0; }
  .dv-grip { display: none; }
}

/* ---------- notes on a chat message -------------------------------------
   A message is never edited; what we make of it stands beside it with a
   name on it.                                                              */
.cd-notes { margin: 4px 0 0 0; display: flex; flex-direction: column; gap: 3px; }
.cd-note {
  display: flex; align-items: baseline; gap: 6px;
  border-left: 2px solid var(--amber, #d79b2b);
  background: var(--amber-soft, rgba(215, 155, 43, .10));
  padding: 3px 7px; border-radius: 0 5px 5px 0;
  font-size: 12px; line-height: 1.35;
}
.cd-note .cd-note-who { font-weight: 600; flex: 0 0 auto; }
.cd-note .cd-note-body { flex: 1 1 auto; min-width: 0; word-break: break-word; }
.cd-note .cd-note-at { color: var(--muted, #78808c); flex: 0 0 auto; font-size: 11px; }
.cd-note .cd-note-x {
  border: 0; background: none; cursor: pointer; color: var(--muted, #78808c);
  padding: 0 2px; font-size: 13px; line-height: 1; flex: 0 0 auto;
}
.cd-note .cd-note-x:hover { color: var(--red, #c0392b); }

.cd-noteform { display: flex; gap: 5px; margin-top: 4px; }
.cd-noteform input {
  flex: 1 1 auto; min-width: 0; font-size: 12px; padding: 4px 7px;
  border: 1px solid var(--line, #e3e6ea); border-radius: 5px;
}

/* a message that came from the shop's own group says so */
.cd-tg {
  font-size: 10px; font-weight: 600; letter-spacing: .02em;
  color: var(--muted, #78808c); border: 1px solid var(--line, #e3e6ea);
  border-radius: 4px; padding: 0 4px; margin-left: 5px;
}

/* ---------- the portal's own notes, out of the way ----------------------
   A busy room is mostly the portal talking to itself: statements created,
   payments sent, invoices moved. One click in the dock header puts them
   away; nothing is hidden on the server and the switch is per person.     */
.cd-icon.off { opacity: .45; text-decoration: line-through; }

/* ---------- what the payer copies ---------------------------------------
   The name on the transfer and the amount are typed into a bank by hand,
   twice a night. They look like text until the pointer is on them.       */
.mc-copy.q-copy {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: inherit;
  cursor: copy; border-radius: 3px;
}
.q-copy:hover, .mc-copy:hover { background: var(--amber-soft, rgba(215,155,43,.14)); }
.mc-copy.copied, .q-copy.copied { background: var(--green-soft, rgba(18,161,80,.16)); }
.grid .mc-copy { cursor: copy; }

/* ---------- MEMO -------------------------------------------------------
   Not advice to read: a value to put in a field at the bank. It looks like
   something you take, and one click takes it.                            */
.pay-memo {
  display: inline-flex; align-items: baseline; gap: 5px;
  background: var(--amber-soft, rgba(215,155,43,.14));
  border: 1px dashed var(--amber, #d79b2b);
  border-radius: 5px; padding: 1px 7px; margin-top: 3px;
  font: inherit; font-size: 12px; color: inherit; cursor: copy;
  max-width: 100%; text-align: left;
}
.pay-memo .pm-k {
  font-size: 10px; font-weight: 700; letter-spacing: .04em;
  color: var(--amber-ink, #8a5a00); flex: 0 0 auto;
}
.pay-memo:hover { background: var(--amber-soft, rgba(215,155,43,.26)); }
.pay-memo.copied { background: var(--green-soft, rgba(18,161,80,.18));
                   border-color: var(--green, #12a150); border-style: solid; }
.pay-memo-line { margin: 6px 0; }

/* the hint beside it is a button now, but it must still read as a note */
.pay-from.mc-copy, .r-hint .mc-copy {
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  text-align: left; cursor: copy;
}
/* the two account fields sit one under the other rather than fighting for width */
.pay-hint-form { display: flex; flex-wrap: wrap; gap: 4px; }
.pay-hint-form input { flex: 1 1 150px; min-width: 0; }

/* ---------- folds ------------------------------------------------------
   The shop page carried four cards of prose above the invoices: the name
   the bank knows, the notes, the EFS spellings, and two boxes for a note
   the payer reads. Each is set once and read rarely, and together they
   pushed the table most of a screen down. The heading now carries the
   value, and the form is one click behind it.                             */
details.fold { padding: 0; }
details.fold > summary {
  display: flex; align-items: baseline; gap: 8px;
  padding: 8px 12px; cursor: pointer; list-style: none;
  font-size: 13px; border-radius: inherit;
}
details.fold > summary::-webkit-details-marker { display: none; }
details.fold > summary::after {
  content: "▾"; margin-left: auto; color: var(--muted, #78808c);
  font-size: 11px; flex: 0 0 auto; transition: transform .12s;
}
details.fold[open] > summary::after { transform: rotate(180deg); }
details.fold > summary:hover { background: var(--card-2, #f7f8fa); }
details.fold > :not(summary) { padding: 0 12px 12px; }
.fold-k {
  font-weight: 700; font-size: 10px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted, #78808c); flex: 0 0 auto;
}
.fold-v {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fold-why { margin: 0 0 8px; }

/* the same, smaller, inside an account card */
.pay-hint-fold > summary {
  list-style: none; cursor: pointer; font-size: 12px;
  padding: 3px 0; display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline;
}
.pay-hint-fold > summary::-webkit-details-marker { display: none; }
.pay-hint-fold > summary:hover { color: var(--accent, #2f6feb); }
.pay-memo-shown { font-size: 12px; }
.pay-memo-shown b {
  font-size: 10px; letter-spacing: .04em; color: var(--amber-ink, #8a5a00);
}

/* ---------- banners, at the size of what they say -----------------------
   A payment raises one, and on a busy evening thirty-eight of them queue
   up. Three were shown at a time at a hundred and thirty pixels each: four
   hundred pixels of chrome, above every page, for three lines of text.

   The words are unchanged -- kicker, title, body, link, the X. They are
   laid along one line instead of stacked, and the title stops being a
   nineteen-pixel heading.                                                 */
.banner-wall { gap: 6px; margin-bottom: 10px; }
.banner {
  align-items: center;
  gap: 10px;
  border-radius: 8px;
  padding: 7px 12px;
  box-shadow: none;
  border-left-width: 4px;
}
.banner-body {
  display: flex; align-items: baseline; gap: 10px;
  flex-wrap: wrap; min-width: 0;
}
.banner-kicker { font-size: 10px; letter-spacing: .08em; flex: 0 0 auto; }
.banner h2 {
  margin: 0; font-size: 13px; font-weight: 600; line-height: 1.3;
  flex: 0 1 auto; min-width: 0;
}
.banner p {
  margin: 0; font-size: 12px; flex: 1 1 200px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.banner .btn { margin-top: 0; flex: 0 0 auto; }
.banner-close { width: 24px; height: 24px; border-radius: 6px; font-size: 15px; }

/* two accounts belong side by side: they were seven pixels short of fitting
   and stacked instead, which cost a card's full height on every shop */
.vendor-grid { grid-template-columns: repeat(auto-fill, minmax(228px, 1fr)); }

/* the big figure stays big when it becomes something you can take */
.r-amount .mc-copy {
  background: none; border: 0; padding: 0 2px; font: inherit; color: inherit;
  cursor: copy; border-radius: 4px; display: block;
}

/* ---------- a number read off the screenshot ----------------------------
   Filled in for her, and visibly so: it is a reading, not a fact, until
   she has looked at it.                                                   */
.ocr-note { display: block; font-size: 11px; margin-top: 2px; color: var(--muted, #78808c); }
.ocr-note.ok { color: var(--green, #12a150); }
input.ocr-filled {
  background: var(--green-soft, rgba(18,161,80,.10));
  border-color: var(--green, #12a150);
}

/* ---------- two clocks ---------------------------------------------------
   Tashkent is what people read; Central is what the money is counted on.
   Both are shown, the second one quieter, and it steps onto its own line
   rather than widening a table that was made to fit without scrolling.   */
.when { white-space: nowrap; }
.tz-alt {
  display: inline-block;
  margin-left: 6px;
  color: var(--muted, #78808c);
  font-size: 11px;
}
.tz-alt::before { content: "\00b7"; margin-right: 5px; opacity: .6; }
.grid .when { white-space: normal; }
.grid .tz-alt { display: block; margin-left: 0; line-height: 1.2; }
.grid .tz-alt::before { content: none; }

/* ---------------------------------------------------------------------------
   Filters that fold away
   ---------------------------------------------------------------------------
   Status, Account and Marks were three rows of ticks -- twenty-odd chips
   taking four lines to say "nothing is filtered". Folded, they say it in one
   line and open to exactly what they were. Several can still be picked at
   once, which is why these are <details> and not a <select>: a select that
   holds more than one choice is a thing nobody can work.
   --------------------------------------------------------------------------- */
.dropfilters { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.dropfilter { position: relative; }
.dropfilter > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 4px 10px; border: 1px solid var(--border);
  border-radius: 999px; background: var(--surface);
  font-size: var(--fs-small); white-space: nowrap;
}
.dropfilter > summary::-webkit-details-marker { display: none; }
.dropfilter > summary::after { content: "\25BE"; font-size: 9px; color: var(--text-muted); }
.dropfilter[open] > summary { border-color: var(--accent); }
.dropfilter > summary:hover { background: var(--surface-2); }
.df-k { font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .06em;
        color: var(--text-soft); font-weight: 700; }
.df-v { color: var(--text-muted); }
.df-v.on { color: var(--accent); font-weight: 700; }
/* A list, one choice per line. Chips in a block read as a paragraph to be
   parsed; a column is read by running down it, which is what picking from
   a list is. Long lists scroll rather than growing past the window. */
.df-panel {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0;
  display: flex; flex-direction: column; gap: 0;
  width: 232px; max-height: 320px; overflow-y: auto;
  padding: 4px; border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface); box-shadow: var(--shadow-lg);
  scrollbar-width: thin;
}
.df-panel .chip {
  display: flex; align-items: center; gap: 8px;
  width: 100%; margin: 0; padding: 6px 8px;
  border: none; border-radius: var(--r-sm); background: none;
  font-size: var(--fs-body); white-space: normal; text-align: left;
  cursor: pointer;
}
.df-panel .chip:hover { background: var(--surface-2); }
.df-panel .chip.on { background: var(--brand-soft); color: var(--brand-ink); font-weight: 600; }
.df-panel .chip input[type=checkbox] { flex: 0 0 auto; margin: 0; }
/* Pinning the last one to the right pushed a 460px panel off the left edge,
   because these triggers all sit near the start of the row. They open from
   their own left edge and are kept inside the window by the width alone. */
/* a fixed, readable column width; nothing to collapse */
@container content (max-width: 520px) {
  .df-panel { width: min(232px, 80vw); }
}

/* ---------------------------------------------------------------------------
   Adjustments and notes: the form waits behind a +
   ---------------------------------------------------------------------------
   Two statements in three carry neither, and the three empty boxes for each
   were the tallest thing on a page whose subject is the invoice table. The
   + sits on the heading, opens the same form in place, and the rows above
   keep their own Save and × -- editing was always there, it was just hard
   to see under a form that looked like the only thing to do.
   --------------------------------------------------------------------------- */
/* The + belongs under the word it adds to, not at the far end of a line
   away from it -- that is the thing being added to, and the eye goes from
   the heading straight down to the list. */
.extras-head { display: block; }
.addfold { position: relative; display: inline-block; margin: 4px 0 2px; }
.addfold > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; line-height: 1;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text-soft);
  font-size: 15px; font-weight: 700;
}
.addfold > summary::-webkit-details-marker { display: none; }
.addfold > summary:hover { background: var(--surface-2); color: var(--accent); }
.addfold[open] > summary { background: var(--accent); border-color: var(--accent); color: #fff; }
/* .extra-form sets display:flex, and author CSS beats the browser's own rule
   for a closed <details> -- so the form has to be told to go away itself */
.addfold:not([open]) > .extra-form.add { display: none; }
.addfold > .extra-form.add {
  position: absolute; z-index: 40; top: calc(100% + 5px); left: 0;
  width: max-content; max-width: min(560px, max(260px, calc(100vw - 440px)));
  padding: 9px; border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface); box-shadow: var(--shadow);
}
/* the row's own buttons: quiet until the row is under the cursor */
.extra-row .btn.ghost.sm { opacity: .55; transition: opacity .12s; }
.extra-row:hover .btn.ghost.sm, .extra-row:focus-within .btn.ghost.sm { opacity: 1; }
.extra-row .btn.ghost.sm.danger { color: var(--red); }
@container content (max-width: 620px) {
  .addfold > .extra-form.add { max-width: 88vw; }
}

/* ---------------------------------------------------------------------------
   EFS at laptop width
   ---------------------------------------------------------------------------
   Nine tabs and a column of shop addresses pushed this page sideways on a
   15-inch screen -- and a point of extra type made it worse. The tab strip
   scrolls on its own whenever it has to rather than only on a phone, and an
   address wraps instead of setting the width of the page.
   --------------------------------------------------------------------------- */
.fb-tabs { overflow-x: auto; max-width: 100%; scrollbar-width: thin; }
.fb-tab { flex: 0 0 auto; }
.mg-row, .mc-place, .place-row { min-width: 0; }
.mg-row .muted, .mc-place .muted { overflow-wrap: anywhere; }

/* the shop a request belongs to, read from across the desk */
.ticket-shop { margin: 4px 0 1px; font-size: var(--fs-head); font-weight: 700; line-height: 1.2; }
.ticket-shop .link { color: var(--brand); }
.ticket-side .card .row { flex-wrap: wrap; gap: 6px; }
.ticket-side .card .row input[type=text] { flex: 1 1 100%; }

/* A ticket has two or three figures, not seven: stretched across the page
   they left a hand's width of nothing between them. They sit together at
   the left and keep their own width. */
.ticket-page .statbar { justify-content: flex-start; }
.ticket-page .statbar .stat { flex: 0 1 auto; min-width: 150px; }

/* ---------------------------------------------------------------------------
   The balance block, tightened
   ---------------------------------------------------------------------------
   Its sizes were set by hand and did not move when the type ramp went up, so
   a card that already carried a 23px figure, a bar and five rows became the
   tallest thing on a shop's page. The figure is still the biggest thing in
   the block -- it is the answer somebody came for -- just no longer bigger
   than the page's own headings by half.
   --------------------------------------------------------------------------- */
.balance { padding: 7px 9px; margin: 6px 0; }
.balance .b-value { font-size: var(--fs-figure); line-height: 1.1; }
.balance .b-label { font-size: var(--fs-micro); }
.balance .b-pct { font-size: var(--fs-mid); }
.balance .b-sub { font-size: var(--fs-micro); }
.balance .bar { margin: 5px 0 6px; }
.balance .b-rows { gap: 1px; }
.balance .b-rows li { font-size: var(--fs-small); }
.balance .b-rows .b-last { margin-top: 2px; padding-top: 3px; }
/* the schedule line and the tag above it were carrying card-sized padding */
.vc-card .vc-schedule, .acct .vc-schedule { font-size: var(--fs-small); margin: 3px 0; }

/* ---------------------------------------------------------------------------
   A shop's page: the name first, the rest behind one mark
   --------------------------------------------------------------------------- */
.page-head .page-title { font-size: 22px; line-height: 1.15; }
.shopmenu { position: relative; flex: 0 0 auto; }
.shopmenu > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 28px; border: 1px solid var(--border);
  border-radius: var(--r-sm); background: var(--surface);
  color: var(--text-soft); font-size: 17px; line-height: 1;
}
.shopmenu > summary::-webkit-details-marker { display: none; }
.shopmenu > summary:hover { background: var(--surface-2); color: var(--text); }
.shopmenu[open] > summary { background: var(--surface-2); color: var(--text); }
.sm-panel {
  position: absolute; z-index: 45; top: calc(100% + 5px); right: 0;
  display: flex; flex-direction: column; align-items: stretch; gap: 2px;
  width: 420px; max-width: min(420px, calc(100vw - 60px));
  max-height: min(78vh, 680px); overflow-y: auto; scrollbar-width: thin;
  padding: 5px;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface); box-shadow: var(--shadow-lg);
}
/* every action reads as one line of a list, whatever element it is */
.sm-panel .btn, .sm-panel form { width: 100%; }
.sm-panel .btn {
  justify-content: flex-start; text-align: left;
  border: none; background: none; box-shadow: none;
  padding: 7px 9px; font-size: var(--fs-body); font-weight: 500;
  color: var(--text); border-radius: var(--r-sm);
}
.sm-panel .btn:hover { background: var(--surface-2); }

/* the balance card hugs its content instead of stretching to the figures */
.vc-split > #account-cards { align-self: flex-start; }


/* the shop's own blocks, living in the menu now */
.sm-blocks { display: flex; flex-direction: column; gap: 5px; margin-top: 5px;
             padding-top: 5px; border-top: 1px solid var(--border); }
.sm-blocks > .bankbar, .sm-blocks > .fold {
  flex: 1 1 auto; width: 100%; margin: 0; min-width: 0;
}
.sm-blocks .bankbar { flex-wrap: wrap; gap: 6px 10px; }
.sm-blocks .fold > summary { padding: 6px 8px; }
.sm-blocks .fold-v { white-space: normal; }

/* An empty Adjustments or Notes block is two words and a lot of air. With
   nothing in it, it is a heading and a +, and takes the room of one line. */
.extras { margin-top: 8px; }
.extras + .extras { margin-top: 10px; }
.extras-head { margin-bottom: 2px; }
.extra-list:empty { display: none; }
.extras .addfold { margin: 2px 0 0; }

/* ---------------------------------------------------------------------------
   The page's own heading stays put while the table scrolls
   ---------------------------------------------------------------------------
   A shop's page is a long table; by the tenth row nobody can see whose
   invoices they are reading. The name rides along, and with it the one fact
   that governs every figure underneath -- the rebate.
   --------------------------------------------------------------------------- */
/* Sticky is not possible here yet: .page-head sits in a container that is
   only as tall as itself, so there is nothing for it to travel along. Making
   it ride would mean changing which element scrolls, and that element is the
   one every container query in this file is measured against. Left alone
   until that can be done carefully. */
.page-head { padding: 2px 0 6px; }
.page-head .page-title { font-size: 26px; font-weight: 800; }
/* beside the name, in the same breath and the same size: the rebate is part
   of what this shop is, not a footnote under it */
.title-rebate {
  display: inline; margin-left: 12px;
  font-size: inherit; font-weight: 700; color: var(--brand);
}
.title-rebate .muted { color: var(--text-muted); font-weight: 500; }
/* the ticket's own heading is the shop, and it rides along the same way */
.ticket-page .ticket-shop { font-size: 26px; font-weight: 800; }

/* the mirrored heading, once the real one has gone up the page */
.sticky-title {
  position: fixed; z-index: 60; left: var(--sidebar-w); right: 0; top: 40px;
  display: none; align-items: baseline; gap: 10px;
  padding: 7px 18px;
  background: var(--surface); border-bottom: 1px solid var(--border);
  box-shadow: 0 6px 12px -10px rgba(16, 24, 40, .5);
}
.sticky-title.on { display: flex; }
.sticky-title b { font-size: 17px; font-weight: 800; letter-spacing: -.01em; }
.sticky-title .st-sub { font-size: var(--fs-body); font-weight: 700; color: var(--brand); }
body.has-chatdock .sticky-title { right: 386px; }
@container content (max-width: 700px) { .sticky-title { left: 0; } }

/* a destructive action asks before it takes room, and again before it acts */
.askfirst { margin-top: 6px; }
.askfirst > summary {
  list-style: none; cursor: pointer; display: inline-flex; width: 100%;
  justify-content: center;
}
.askfirst > summary::-webkit-details-marker { display: none; }
/* author display beats the browser's own hiding for a closed <details> */
.askfirst:not([open]) > form { display: none; }
.askfirst > form {
  display: flex; flex-direction: column; gap: 6px; margin-top: 6px;
  padding: 8px; border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface-2);
}
.askfirst > form input[type=text] { width: 100%; }
.askfirst .btn.danger { color: var(--red); }

/* the one card of things a request can be told to do */
.side-actions { display: flex; flex-direction: column; gap: 6px; }
.side-actions form { width: 100%; }
.side-actions .btn { width: 100%; justify-content: center; }
.side-actions .row { flex-wrap: wrap; gap: 6px; }

/* the i that holds what used to be a sentence beside every control */
.hintdot { position: relative; display: inline-block; vertical-align: middle; }
.hintdot > summary {
  list-style: none; cursor: help; user-select: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 50%;
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text-soft); font-size: 10px; font-weight: 700; font-style: italic;
  line-height: 1;
}
.hintdot > summary::-webkit-details-marker { display: none; }
.hintdot > summary:hover { color: var(--brand); border-color: var(--brand); }
.hintdot[open] > summary { background: var(--brand); border-color: var(--brand); color: #fff; }
.hintdot:not([open]) > .hint-body { display: none; }
.hint-body {
  position: absolute; z-index: 55; top: calc(100% + 5px); left: 0;
  width: max-content; max-width: 320px;
  padding: 7px 9px; border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); box-shadow: var(--shadow-lg);
  font-size: var(--fs-small); color: var(--text-muted); white-space: normal;
}

/* the panel that opens on one invoice, tightened */
.corr-box { padding: 7px 9px; margin: 6px 0; }
.corr-box + .corr-box { margin-top: 6px; }
.pop-body .section-title, .corr-box .section-title { margin-bottom: 3px; }
.pop-body .field, .corr-box .field { margin-top: 5px; }
.pop-body .field.mt, .corr-box .field.mt { margin-top: 6px; }
.pop-body .btn.sm.mt { margin-top: 6px; }
.pop-body hr, .corr-box hr { margin: 7px 0; }

/* CMP changed this line: a mark, not a word. In a column of invoice numbers
   a badge saying "source changed" was wider than the number it belonged to;
   the dot carries the same meaning and the note is on hover. */
.src-dot {
  display: inline-block; margin-left: 4px; color: var(--amber);
  font-size: 9px; vertical-align: middle; cursor: help;
}

/* the invoice picker opens as a panel like the others, but it carries a
   table, so it is given room rather than a 420px column */
.addfold.add-lines > .table-wrap,
.addfold.add-lines > form {
  position: absolute; z-index: 45; top: calc(100% + 5px); left: 0;
  width: max-content; max-width: min(900px, calc(100vw - 420px));
  max-height: 60vh; overflow: auto;
  padding: 9px; border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface); box-shadow: var(--shadow-lg);
}
.addfold.add-lines:not([open]) > form { display: none; }

/* two figures, side by side at the left, not stretched across the width */
.tiles.two { display: flex; flex-wrap: wrap; gap: var(--gap); justify-content: flex-start; }
.tiles.two .tile { flex: 0 1 280px; }

/* a cell you can simply type into: it looks like text until you touch it */
.inline-set { margin: 0; }
.inline-set input[type=text] {
  width: 100%; min-width: 150px; padding: 3px 6px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: none; font: inherit; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.inline-set input[type=text]::placeholder { color: var(--text-soft); }
.inline-set input[type=text]:hover { border-color: var(--border); background: var(--surface-2); }
.inline-set input[type=text]:focus {
  border-color: var(--brand); background: var(--surface); outline: none;
}

/* a value says it can be taken, and says when it has been */
.grid tbody td:not(:has(input[type=checkbox])) { cursor: copy; }
.grid tbody td a, .grid tbody td button, .grid tbody td label { cursor: pointer; }
/* Pressed is copied. No bubble, no word swapped in and out, no wait: the
   value is in monospace so it reads as a value, and clicking it takes it. */
.mc-copy, .q-copy, .mc-copy.q-copy, .pay-from.mc-copy, .pay-memo,
.grid .mc-copy, .mc-pop .mc-copy {
  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;
  letter-spacing: -.01em;
}
.copyname { font-family: inherit; }   /* an icon button has no text to set */

/* a chosen row says so by being one, not by a tick in a column of its own */
#invoice-table tbody tr.picked,
tr[data-invoice].picked { background: var(--brand-soft); }
#invoice-table tbody tr.picked td { border-color: #f6d9cb; }
#invoice-table tbody tr { user-select: none; }
#invoice-table tbody td:first-child input[type=checkbox],
#invoice-table thead th:first-child input[type=checkbox] { cursor: pointer; }
/* the empty parts of a row are the handle */
#invoice-table tbody td:empty { cursor: pointer; }

/* The dashboard's five figures, beside the title instead of under it.
   A band of boxes cost an inch of a 15" screen to say what fits on one
   line; the shops begin that much higher now. Each figure keeps its link
   and its explanation, which moved into the tooltip. */
.dash-head { align-items: flex-end; gap: 18px; flex-wrap: wrap; }
.dash-head .dh-title { flex: 0 1 auto; margin-right: 10px; }
.headstats {
  display: flex; align-items: flex-end; gap: 0; flex: 0 1 auto;
  flex-wrap: wrap; justify-content: flex-start; padding-bottom: 2px;
  margin-right: auto;            /* beside the title, not out at the edge */
  padding-left: 4px;
}
.headstats .hs {
  display: flex; flex-direction: column; gap: 1px; text-decoration: none;
  color: var(--text); padding: 0 13px; border-left: 1px solid var(--border);
  line-height: 1.25;
}
.headstats .hs:first-child { border-left: 0; padding-left: 0; }
.headstats .hs-k {
  font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-soft); font-weight: 700; white-space: nowrap;
}
.headstats .hs b { font-size: 14.5px; white-space: nowrap; }
.headstats a.hs:hover b { color: var(--brand); }
.headstats .hs.hot b { color: var(--red); }
.headstats .hs-export { margin-left: 14px; }
@container content (max-width: 1350px) { .headstats .hs.opt2 { display: none; } }
@container content (max-width: 1150px) { .headstats .hs.opt { display: none; } }

/* One ordinary account does not need a line of the card to say "Ordinary".
   What that line really carried was the mark -- green when somebody has
   vouched for the balance -- so the mark became the corner of the card. A
   shop with two accounts still labels both; there the name is information. */
.vendor-card { position: relative; overflow: hidden; }
.vc-corner {
  position: absolute; top: 0; left: 0; width: 0; height: 0;
  border-style: solid; border-width: 13px 13px 0 0;
  border-color: var(--green) transparent transparent transparent;
}
.vc-corner.off { border-top-color: var(--border-strong); }
.vendor-card.lone .acct-figs { margin-left: 0; }
.vendor-card.lone .acct-line { justify-content: space-between; }

/* The shop's figures sat in tiles built for a full-width strip; beside the
   account card they only have to be read, not filled. Tighter rows, the
   same type ramp. */
.vc-split > .statbar .stat { padding: 5px 10px; }
.vc-split > .statbar .stat .v { font-size: var(--fs-head); line-height: 1.2; }
.vc-split > .statbar .stat .k { margin-bottom: 0; }
.vc-split > .statbar .stat .s { line-height: 1.25; }

/* The paid-how popover sat inside the table's scroll box, which clipped it:
   on a short table the proof at the bottom of it simply was not there. It is
   placed by hand now, like the CMP/Zoho one, so nothing can cut it off. */
.mc-pop { position: fixed; top: 0; left: 0; right: auto; }
.mc-pop.left { left: 0; right: auto; }

/* One design code for "has anybody vouched for this balance": a triangle in
   the top-left corner of whatever it describes. The card's corner when the
   shop has a single account; the account block's own corner when it has
   several, because there the answer can differ between them. */
.vendor-card .acct { position: relative; padding-left: 13px; }
.vc-corner.sm { border-width: 9px 9px 0 0; }
.vendor-card .acct .vc-corner.sm { top: 1px; left: 0; }

/* ---------------------------------------------------------------------------
   The dashboard as a table. Cards were readable at twenty shops and stop
   being readable somewhere around forty; the list is going past that. One row
   per account, a shop with several of them kept in one block under its name,
   and every column sortable from its own heading.
   --------------------------------------------------------------------------- */
.shoptable { padding: 0; overflow: hidden; }
.grid.shops { width: 100%; }
.grid.shops thead th { position: sticky; top: 0; z-index: 2; }
.grid.shops th .sortlink {
  color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 4px;
}
.grid.shops th .sortlink:hover { color: var(--brand); }
.grid.shops th .sortlink.on { color: var(--text); }
.grid.shops th .caret { font-size: 8px; opacity: .8; }
/* a shop is a block of rows: no rule inside it, one between the shops */
.grid.shops tbody.shop > tr > td { border-bottom: 0; }
.grid.shops tbody.shop + tbody.shop { border-top: 1px solid var(--border); }
.grid.shops tbody.shop:hover > tr > td { background: var(--surface-2); }
.grid.shops tbody.shop.attention .shopname { color: var(--brand-ink); }
.grid.shops td { vertical-align: middle; }
.shopcell { white-space: nowrap; }
.shopcell .shopname { font-weight: 650; color: var(--navy); text-decoration: none; }
.shopcell .shopname:hover { color: var(--brand); }
.shopcell .acctname { color: var(--text-muted); font-size: var(--fs-small); margin-left: 7px; }
.grid.shops tr.more .shopcell { padding-left: 16px; }
/* the little bracket that ties an account to the shop above it */
.shopcell .tie {
  display: inline-block; width: 11px; height: 8px; margin: 0 6px 2px 2px;
  border-left: 1px solid var(--border-strong); border-bottom: 1px solid var(--border-strong);
}
.shopcell .vc-corner.sm { position: static; display: inline-block; margin-right: 7px; vertical-align: 1px; }
.grid.shops .cap { display: block; font-size: var(--fs-micro); color: var(--text-soft); font-weight: 400; }
.grid.shops b.hot { color: var(--red); }
.grid.shops b.warm { color: var(--amber); }
.grid.shops .warm { color: var(--amber); }
.usedcell { white-space: nowrap; }
.usedcell .ln {
  display: inline-block; width: 78px; height: 5px; border-radius: 3px;
  background: var(--surface-3); overflow: hidden; vertical-align: middle;
}
.usedcell .ln i { display: block; height: 100%; background: var(--text-soft); }
.usedcell .ln.warn i { background: var(--amber); }
.usedcell .ln.crit i { background: var(--red); }
.usedcell .pct {
  display: inline-block; min-width: 44px; text-align: right; margin-left: 7px;
  font-size: var(--fs-small); color: var(--text-muted); font-variant-numeric: tabular-nums;
}
.usedcell .pct.crit { color: var(--red); font-weight: 600; }

/* The copy button in front of a shop's name: an icon, not a chip. The table
   itself no longer copies on click -- a press there is for sorting or for
   opening the shop, and the one value worth taking is this one. */
.shopcell .copyname {
  border: 0; background: none; padding: 0 6px 0 0; color: var(--text-soft);
  cursor: pointer; vertical-align: -2px; line-height: 0;
}
.shopcell .copyname:hover { color: var(--brand); }
.shopcell .copyname.copied { color: var(--green); }

/* Search and the filters on one line. The box takes whatever room the
   pickers leave; below a narrow container the row wraps as it always did. */
.filterbar { padding: 8px 10px; }
.filters.one-line { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0; }
.filters.one-line .searchbar.inline {
  flex: 1 1 240px; margin: 0; min-width: 180px; max-width: 460px; padding: 2px 10px;
}
.filters.one-line .field.bare { margin: 0; }
.filters.one-line .field.bare > select { margin-top: 0; }
.filters.one-line .toggle-group { flex: 0 0 auto; }
.filters.one-line .btn { flex: 0 0 auto; }

/* The "copy" cursor belongs on a table whose cells copy themselves. The shop
   list is not one of them any more -- only the button in front of the name
   copies -- so its cells point like any other text. */
table.grid.shops tbody td { cursor: default; }
table.grid.shops tbody td a,
table.grid.shops tbody td button { cursor: pointer; }
.grid.shops th .sortlink { cursor: pointer; }

/* ---------------------------------------------------------------------------
   The shop's header as one card: each account's balance stacked, and the
   shop's own figures once underneath. It replaced a card beside a panel that
   repeated half of each other, and it is about a third of the height.
   --------------------------------------------------------------------------- */
/* A card has a size. Given the whole row it stretched into a ribbon with
   the figures at opposite ends -- further for the eye to travel, for less.
   This is the width the content actually needs. */
/* Two accounts stand side by side instead of one under the other: they are
   read against each other anyway, and stacked they cost a second screenful.
   The shop's own figures stay a single row under both. */
.shopcard {
  padding: 12px 14px; max-width: 940px;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  gap: 10px 18px; align-items: start;
}
.shopcard > .statbar { grid-column: 1 / -1; }
/* the blocks inside are not cards of their own -- one border, not three */
.shopcard .vendor-card {
  border: 0; box-shadow: none; padding: 0; background: none; border-radius: 0;
}
.shopcard .vendor-card .vc-head { justify-content: flex-start; gap: 8px; }
.shopcard .vendor-card .oval { flex: 0 0 auto; }
/* what can be done with this account, on one line under the figures */
.shopcard .vc-flags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.shopcard .vc-flags form { flex: 0 0 auto; margin: 0; }
/* a rule between the columns, not above the second block */
.shopcard .acctblock + .acctblock { padding-left: 18px; border-left: 1px solid var(--border); }
@media (max-width: 820px) {
  .shopcard .acctblock + .acctblock {
    padding-left: 0; border-left: 0; padding-top: 10px; border-top: 1px solid var(--border);
  }
}
.shopcard .vc-head { margin-bottom: 2px; }
.shopcard .vc-schedule { margin: 0 0 6px; }
.shopcard .balance { margin: 0; }
/* the subtraction on one line, under the big figure */
.b-line {
  display: flex; flex-wrap: wrap; gap: 4px 16px; align-items: baseline;
  font-size: var(--fs-small); color: var(--text); margin-top: 6px;
  padding-top: 6px; border-top: 1px solid rgba(0, 0, 0, .06);
}
.b-line b { font-weight: 650; }
.b-line b.minus { color: var(--red); }
.b-line .muted { color: var(--text-muted); }
/* the shop's figures: one row inside the same card, not a grid of tiles */
.shopcard > .statbar {
  margin: 10px 0 0; padding-top: 9px; border-top: 1px solid var(--border);
  border-radius: 0; box-shadow: none; background: none; display: flex;
  flex-wrap: wrap; gap: 0;
}
.shopcard > .statbar .stat {
  padding: 0 14px; border-left: 1px solid var(--border); border-top: 0;
  flex: 0 1 auto;
}
.shopcard > .statbar .stat:first-child { border-left: 0; padding-left: 0; }
.shopcard > .statbar .stat .v { font-size: var(--fs-head); }
.shopcard > .statbar .stat .v.small-v { font-size: var(--fs-body); }

/* The tick column is gone from sight. The checkboxes stay in the markup --
   they are what the form posts -- but a row is taken by clicking it and a
   stretch by holding the button down, so nobody has to hit a 13px box. */
#invoice-table .pickcell { display: none; }
#invoice-table tbody tr, #invoice-table tbody tr td { cursor: default; }
#invoice-table tbody tr td a, #invoice-table tbody tr td button { cursor: pointer; }
#invoice-table tbody tr.picked > td { background: var(--brand-soft); }
#invoice-table tbody tr.picked > td:first-child { box-shadow: inset 3px 0 0 var(--brand); }
.copyname {
  border: 0; background: none; padding: 0 4px; color: var(--text-soft);
  cursor: pointer; vertical-align: -1px; line-height: 0;
}
.copyname:hover { color: var(--brand); }
.copyname.copied { color: var(--green); }

/* How long it has been waiting, under when it arrived: on a queue the
   question is the gap, not the stamp. */
.grid.queue .waited { color: var(--amber); font-weight: 600; }
/* The file picker is out of the way: the field takes a paste and nothing
   else, so a stray click cannot open a file dialog over the form. */
input[type=file].paste-only {
  position: absolute; width: 1px; height: 1px; opacity: 0;
  pointer-events: none; z-index: -1;
}

/* Green while there is room on the line, so the three states read at a
   glance instead of being worked out from the figure. */
.balance.ok { background: var(--green-soft); border-color: #cfe7d6; }
.balance.ok .b-value { color: var(--green); }
.balance.ok .b-line { border-top-color: rgba(21, 128, 61, .18); }
.balance.ok .bar > div { background: var(--green); }
.statbar .stat .v.small-v.stacked { display: flex; flex-direction: column; gap: 1px; }
.statbar .stat .v.small-v.stacked .one { white-space: nowrap; font-size: var(--fs-small); }

/* The shop's "..." menu was a flex item beside the title, and as soon as the
   title line was long it wrapped onto a row of its own -- a whole strip of
   empty page for one 30px button. It sits in the corner of the header now
   and takes no height at all. */
.page-head[data-sticky-title] { position: relative; }
.page-head[data-sticky-title] > .shopmenu { position: absolute; top: 0; right: 0; }
.page-head[data-sticky-title] > div:first-child { padding-right: 42px; }

/* Where a screenshot is pasted. The file picker is out of reach, so this is
   the whole of the field: a dashed box that says what to press and shows
   what landed in it. A click on it opens nothing. */
.pastezone {
  border: 1px dashed var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface-2); color: var(--text-muted);
  padding: 10px 12px; font-size: var(--fs-small); text-align: center;
  cursor: default; user-select: none;
}
.pastezone b {
  display: inline-block; border: 1px solid var(--border-strong); border-radius: 5px;
  background: var(--surface); padding: 0 6px; margin-right: 6px;
  font-size: var(--fs-micro); color: var(--text);
}
.pastezone.ready { border-color: var(--green); background: var(--green-soft); color: var(--green); }
.field > .k {
  display: block; font-size: var(--fs-micro); letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-muted); font-weight: 700; margin-bottom: 3px;
}

/* The desk panel lives in a row of the queue, under the request it belongs
   to: no padding of its own, no stripe, nothing between the row and it. */
.grid.queue tr.dp-holder > td { padding: 0; background: none; border: 0; }
.grid.queue tr.dp-holder .desk-panel { margin: 0 0 6px; border-radius: 0 0 var(--r) var(--r); }
.grid.queue tbody tr.q-row.open > td { box-shadow: inset 3px 0 0 var(--brand); }
.headstats .hs.good b { color: var(--green); }

/* The panel that opens under a request is a continuation of its row, not a
   second card with a border of its own: one orange mark on the row, a plain
   edge on the panel. */
.grid.queue tbody tr.q-row.open > td { box-shadow: none; }
.grid.queue tbody tr.q-row.open > td:first-child { box-shadow: inset 3px 0 0 var(--brand); }
.grid.queue tr.dp-holder .desk-panel {
  border-color: var(--border); border-top: 0; box-shadow: none;
}
/* a name is read, not typed into a bank: no dashed box around it */
.mc-copy.plain {
  border: 0; background: none; padding: 0; font-weight: 700;
}
.mc-copy.plain:hover { background: none; color: var(--brand); }

/* Both clocks on one line in the queue: the row is about the payment, not
   about timekeeping, and the pair still reads as one stamp. */
.grid.queue .waitcell .tz-alt { display: inline; margin-left: 4px; }
.grid.queue .waitcell .tz-alt::before { content: "\00b7"; margin-right: 4px; opacity: .6; }
.grid.queue .waitcell { line-height: 1.35; }

/* The panel slides in rather than appearing: the eye follows it down to the
   row it belongs to. */
@keyframes dp-in {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}
.grid.queue tr.dp-holder > td { overflow: hidden; }
.grid.queue tr.dp-holder .desk-panel { animation: dp-in .18s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .grid.queue tr.dp-holder .desk-panel { animation: none; }
}
@keyframes dp-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-8px); }
}
.grid.queue tr.dp-holder .desk-panel.dp-out { animation: dp-out .16s ease-in forwards; }
@media (prefers-reduced-motion: reduce) {
  .grid.queue tr.dp-holder .desk-panel.dp-out { animation: none; opacity: 0; }
}

/* ===========================================================================
   Interface guidelines pass: the things a portal is judged on when somebody
   uses it with a keyboard, on a touch screen, or with motion turned down.
   Audited against Vercel's Web Interface Guidelines -- the portal had no
   visible focus anywhere, which is the one that matters most.
   =========================================================================== */

/* Where the keyboard is. Only for keyboard focus: a mouse click does not
   draw a ring, so nothing changes for the people who never leave the mouse. */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 4px;
}
.sidebar nav a:focus-visible { outline-offset: -2px; }
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline-offset: 1px;
}
/* three places turned the ring off outright; they keep their own focus
   colour and get the ring back for the keyboard */
.pay-hint-form input:focus-visible,
.inline-set input[type=text]:focus-visible,
.searchbar .sb-input:focus-visible,
.sb-input:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* A tap is a tap: no 300ms wait for a double-tap that never comes. */
button, a, summary, label, .btn, .chip, .toggle-group a, .tabs a {
  touch-action: manipulation;
}

/* A scroll that reaches its end stops there instead of dragging the page
   behind it -- tables, popovers, the chat list and the menu panels. */
.table-wrap, .mc-pop, .sm-panel, .df-panel, .tchat-list, .cd-list,
.desk-panel .dp-chat, .modal .box { overscroll-behavior: contain; }

/* Motion is a courtesy, not a requirement. One rule for the whole portal,
   so nothing new has to remember it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Figures line up under each other wherever they are counted. */
.num, .grid .num, .statbar .v, .headstats b, .b-value, .mc-amt, .hs b {
  font-variant-numeric: tabular-nums;
}

/* A flex child with long text has to be allowed to shrink, or it pushes its
   neighbours off the row -- this is what made the shop panel collapse once. */
.row > *, .page-head > div, .filters > *, .desk-split > div { min-width: 0; }

/* ===========================================================================
   Motion, said once. The portal had five different durations and no shared
   easing, which is the difference between an interface that feels made and
   one that feels assembled. Two tokens: things that answer a press are fast,
   things that appear take a moment longer. Only transform and opacity are
   animated -- they cost nothing to the layout, so a press interrupts them
   cleanly instead of fighting them.
   =========================================================================== */
:root {
  --ease: cubic-bezier(.2, .7, .3, 1);
  --fast: 120ms;
  --mid: 180ms;
}

.btn, .chip, .tagbtn, .toggle-group a, .tabs a, .mc-copy, .copyname,
.sidebar nav a, .grid.queue tbody tr.q-row, .grid.shops tbody.shop,
#invoice-table tbody tr {
  transition: background-color var(--fast) var(--ease),
              border-color var(--fast) var(--ease),
              color var(--fast) var(--ease);
}
/* a press moves: the smallest feedback there is, and the one nobody has to
   be taught */
.btn:active, .chip:active, .tagbtn:active, .copyname:active, .mc-copy:active {
  transform: translateY(1px);
}
.btn, .chip, .tagbtn, .copyname, .mc-copy { will-change: auto; }

/* things that appear: the same rise and fade everywhere */
@keyframes rise-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.mc-pop:not([hidden]), .src-pop:not([hidden]), .sm-panel, .df-panel,
.modal.open .box, .toast {
  animation: rise-in var(--mid) var(--ease);
}
/* the progress bar fills instead of jumping */
.bar > div { transition: width var(--mid) var(--ease); }

/* The first thing a keyboard reaches on every page: past the menu, into the
   page. Invisible until it is focused, which is the only time it is wanted. */
.skiplink {
  position: fixed; top: -60px; left: 12px; z-index: 1000;
  background: var(--navy); color: #fff; padding: 8px 14px;
  border-radius: 0 0 var(--r-sm) var(--r-sm); font-size: var(--fs-small);
  text-decoration: none; transition: top var(--fast) var(--ease);
}
.skiplink:focus { top: 0; }
main.content:focus { outline: none; }
/* an anchor inside the page glides instead of jumping */
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* The error page: the number is scenery, the ways out are the content. */
.errorpage { max-width: 560px; padding: 24px 0 8px; position: relative; }
.errorpage .err-code {
  font-size: 92px; font-weight: 800; line-height: 1; letter-spacing: -.04em;
  color: var(--surface-3); margin-bottom: -14px; user-select: none;
}
.errorpage .page-title { position: relative; }
/* The sentence that says what exactly was refused is the reason anybody is
   on this page -- it reads as the message, not as a caption under a heading. */
.errorpage .page-sub {
  margin-top: 6px; font-size: var(--fs-body); color: var(--text);
  max-width: 52ch; line-height: 1.45;
}
.err-ways { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.err-note { margin-top: 16px; max-width: 46ch; }

/* A page's own actions keep the right edge; the figures sit next to the
   title, where they are read with it. */
.dash-head .hs-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.dash-head .hs-actions .btn { white-space: nowrap; }

/* ===========================================================================
   The portal's own type. "Inter" was named in the stack and never shipped,
   so every screen was rendering in Segoe UI -- the Windows default, which is
   exactly what makes an interface look like nobody chose anything.

   Geist is served from the portal itself: no CDN, no internet, nothing to
   fail on a laptop that is only on the office network. Both files are
   variable, so one request covers every weight from 300 to 800, and the
   monospace cut is the one the copyable values use -- the figures that get
   typed into a bank read better when the digits are the same width.

   Geist and Geist Mono, SIL Open Font License 1.1 (static/fonts/LICENSE.txt)
   =========================================================================== */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist.woff2") format("woff2-variations"),
       url("/static/fonts/Geist.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("/static/fonts/GeistMono.woff2") format("woff2-variations"),
       url("/static/fonts/GeistMono.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
:root {
  --font: "Geist", -apple-system, "Segoe UI Variable Text", "Segoe UI", Roboto, Arial, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;
}
body { font-family: var(--font); }
/* every value that gets copied or compared is monospaced on purpose */
.mono, .mc-copy, .q-copy, .mc-copy.q-copy, .pay-from.mc-copy, .pay-memo,
.grid .mc-copy, .mc-pop .mc-copy { font-family: var(--font-mono); }

/* A heading that leaves one word alone on the last line reads as an
   accident; the browser can break it better than a fixed width can. */
h1, h2, h3, .page-title, .section-title { text-wrap: balance; }
p, .page-sub, .muted.small { text-wrap: pretty; }
/* a paragraph of prose stops at a readable measure; tables are not prose */
.page-sub, .err-note { max-width: 78ch; }

/* Geist sets wider than the Windows default it replaced, and two of the menu
   items started folding onto a second line. The rail grows by sixteen pixels
   and every destination stays one line, which is what makes a menu scannable. */
:root { --sidebar-w: 212px; }
.sidebar nav a { white-space: nowrap; }

/* One line of filters, not two: the search takes what is left after the
   pickers rather than pushing them onto a row of their own. */
.filters.one-line { flex-wrap: nowrap; }
.filters.one-line .searchbar.inline { flex: 1 1 160px; min-width: 120px; max-width: 320px; }
.filters.one-line .field.bare > select { max-width: 150px; }
.filters.one-line .btn { padding-left: 10px; padding-right: 10px; }
@container content (max-width: 1150px) {
  .filters.one-line { flex-wrap: wrap; }
}

/* Two dates beside the period chips: the same row, the same height. */
.tabs .dates {
  display: flex; align-items: center; gap: 6px; margin-left: auto;
  font-size: var(--fs-small); color: var(--text-muted);
}
.tabs .dates label { display: flex; align-items: center; gap: 5px; }
.tabs .dates input[type=date] {
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 3px 7px; font: inherit; font-size: var(--fs-small); color: var(--text);
  background: var(--surface);
}
.tabs .dates input[type=date]:hover { border-color: var(--border-strong); }

/* The sum of the rows, under the rows. */
.grid tfoot .r-total > td {
  border-top: 2px solid var(--border-strong); background: var(--surface-2);
  padding-top: 7px; padding-bottom: 7px;
}

/* What paid it, in the read-only panel: the number, who confirmed it, and
   the screenshot small enough to sit in the column and big enough to read. */
.dp-proof .bl {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: var(--fs-small); padding: 3px 0; border-bottom: 1px solid var(--border);
}
.dp-proof .bl span { color: var(--text-muted); }
.dp-proof .dp-shot { display: block; margin-top: 8px; }
.dp-proof .dp-shot img {
  width: 100%; max-height: 190px; object-fit: contain; cursor: zoom-in;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-2);
}

/* The proof under the payment report: a number to copy and the screenshot. */
.report-paid { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); }
.rp-line { display: flex; align-items: baseline; gap: 8px; padding: 2px 0; }
.rp-shots { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.rp-shots img {
  max-width: 320px; max-height: 220px; object-fit: contain; cursor: zoom-in;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-2);
}

/* The pager on a long trail: where you are, one step either way, the ends,
   and a box for the page somebody actually means. A row of a hundred and
   sixty-four numbers is not navigation. */
.pager {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border);
  font-size: var(--fs-small);
}
.pager-count { font-variant-numeric: tabular-nums; }
.pager-steps { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.pager .step {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 28px; padding: 0 9px;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text-muted); text-decoration: none;
  font-variant-numeric: tabular-nums;
  transition: background-color var(--fast) var(--ease), border-color var(--fast) var(--ease),
              color var(--fast) var(--ease);
}
.pager a.step:hover { border-color: var(--border-strong); color: var(--text); }
.pager .step.current { background: var(--navy); border-color: var(--navy); color: #fff; font-weight: 650; }
.pager .step.off { opacity: .4; }
.pager-jump { display: flex; align-items: center; gap: 6px; color: var(--text-muted); }
.pager-jump input[type=number] {
  width: 68px; padding: 3px 7px; font: inherit; font-size: var(--fs-small);
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface);
}
.filterbar .dates { display: flex; align-items: center; gap: 6px; color: var(--text-muted); }
.filterbar .dates label { display: flex; align-items: center; gap: 5px; }
.filterbar .dates input[type=date] {
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 3px 7px; font: inherit; font-size: var(--fs-small); background: var(--surface);
}
.filterbar .dates .micro { font-size: var(--fs-micro); white-space: nowrap; }

/* the facts under an error: what was asked, by whom, and the reference that
   matches the log line -- small, quiet, and selectable for pasting */
.err-facts { display: grid; gap: 2px; margin: 18px 0 0; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2);
  max-width: 560px; font-size: var(--fs-small); }
.err-facts > div { display: flex; gap: 10px; align-items: baseline; }
.err-facts dt { flex: 0 0 104px; color: var(--text-soft); font-size: var(--fs-micro);
  text-transform: uppercase; letter-spacing: .05em; font-weight: 700; }
.err-facts dd { margin: 0; color: var(--text); word-break: break-all; }
.err-facts .mono { font-family: var(--font-mono); }

/* A mirrored spreadsheet tab. The table is somebody else's shape -- it can be
   any number of columns wide -- so it scrolls sideways inside its card rather
   than squeezing columns until nothing is readable. */
.sheet { min-width: 100%; white-space: nowrap; }
.sheet th { position: sticky; top: 0; z-index: 1; background: var(--surface-2); }
.sheet td { max-width: 320px; overflow: hidden; text-overflow: ellipsis; }
.sheet td.num { font-variant-numeric: tabular-nums; }
.sheet tbody tr:hover td { background: var(--surface-2); }

/* When the copy is stale because the link stopped answering, the page says so
   above the table instead of quietly showing old figures as if they were new. */
.warnbox { border-color: #f0dcba; background: var(--amber-soft); color: var(--amber);
  display: grid; gap: 2px; }
.warnbox .muted { color: var(--amber); opacity: .8; }

/* The switch between the two looks. It sits with the other personal settings
   in the header and shows which way it is set rather than only what it does. */

/* The rebate in a shop's heading is a control for finance and plain text for
   everybody else: no button chrome, just the figure saying it can be touched. */
.title-rebate .pct.editable { cursor: pointer; border-radius: var(--r-sm); }
.title-rebate .pct.editable:hover { background: var(--brand-soft); }
.title-rebate .pct.editable:focus-visible { outline: none; }
.pct-input {
  font: inherit; color: inherit; width: 4.5em;
  padding: 0 4px; border: 1px solid var(--brand); border-radius: var(--r-sm);
  background: var(--surface);
}

/* The left of the invoice table reads as one group -- date, invoice, PO, whose
   truck it was. Each of those columns takes the width of its own content and
   no more, so the slack goes to the figures on the right instead of opening a
   canyon between the invoice number and the PO beside it. */
.grid th.tightcol, .grid td.tightcol { width: 1%; white-space: nowrap; }
.grid td.company { max-width: 22ch; overflow: hidden; text-overflow: ellipsis; }

/* The company's mark, in place of the two letters the portal drew for itself
   while it waited for one. An image, so it carries no box of its own: the
   gradient square the initials needed would now be a frame around a logo. */
.sidebar .brand img.mark {
  width: 38px; height: auto;
  flex: 0 0 38px;
  background: none; border-radius: 0;
  object-fit: contain;
}
.login-card .lockup {
  width: 232px; height: auto;
  margin-bottom: 18px;
  background: none; border-radius: 0;
}

/* The note on a money code is short by nature -- the longest in the warehouse
   is thirty characters, and it is usually the PO the code will be matched on.
   Kept narrow so it never pushes the buttons off the row. */
.efsnote { max-width: 20ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
