/* Agrofish portal - redesign design system (July 2026).
   The spec lives in static/mockups/; this file is those styles made reusable.
   Tokens + document components shared by supplier / operator / customer pages. */

@font-face { font-family:'Manrope'; font-display:swap; src:url('fonts/manrope-400.woff2') format('woff2'); font-weight:400; }
@font-face { font-family:'Manrope'; font-display:swap; src:url('fonts/manrope-500.woff2') format('woff2'); font-weight:500; }
@font-face { font-family:'Manrope'; font-display:swap; src:url('fonts/manrope-600.woff2') format('woff2'); font-weight:600; }
@font-face { font-family:'Manrope'; font-display:swap; src:url('fonts/manrope-700.woff2') format('woff2'); font-weight:700; }
@font-face { font-family:'Manrope'; font-display:swap; src:url('fonts/manrope-800.woff2') format('woff2'); font-weight:800; }

/* FIGURES ARE NOT MANROPE (JRE, 2026-08-04). This portal is mostly numbers -
   prices, kilos, boxes, in columns somebody reads fast - and Manrope draws a
   "1" as a bare stem with no foot. Under `tabular-nums` every digit gets the
   same slot, so that stem sits in a wide empty box and punches holes through
   a figure like 1.148,00 or 111,17; its 0/6/8/9 are near-circles that close up
   at 13px. Inter was drawn for interface data and fixes both.

   The swap is done with `unicode-range`, NOT by tagging number cells: the
   browser reaches for this family only for the characters listed here and
   falls back to Manrope for every letter, so ALL text stays Manrope and no
   template needs a class. Adding one is the wrong fix - a number that nobody
   remembered to tag would silently keep the old figures.

   What is in the range, and why: the digits; the comma and full stop, or
   13.711,96 would mix two fonts inside one number; +, -, / because sizes and
   dates are written 300-400, 2+, 100/500, 31/07; % for margins; and the
   currency BLOCK (not just the euro sign) so rule 11 holds - a tenant on
   pounds gets the same treatment without a CSS edit.

   Weights: Inter ships here at 400-700, Manrope goes to 800, so 800 maps onto
   inter-700. Checked against the 800-weight totals on /today and /orders.
   `pricelist_sheet.html` is untouched on purpose - it sets Calibri itself
   because it is the listino in the CUSTOMER's house style, not our UI. */
@font-face { font-family:'Figures'; font-display:swap; src:url('fonts/inter-400.woff2') format('woff2'); font-weight:400;
  unicode-range:U+0030-0039,U+002B-002F,U+0025,U+0024,U+00A2-00A5,U+20A0-20BF; }
@font-face { font-family:'Figures'; font-display:swap; src:url('fonts/inter-500.woff2') format('woff2'); font-weight:500;
  unicode-range:U+0030-0039,U+002B-002F,U+0025,U+0024,U+00A2-00A5,U+20A0-20BF; }
@font-face { font-family:'Figures'; font-display:swap; src:url('fonts/inter-600.woff2') format('woff2'); font-weight:600;
  unicode-range:U+0030-0039,U+002B-002F,U+0025,U+0024,U+00A2-00A5,U+20A0-20BF; }
@font-face { font-family:'Figures'; font-display:swap; src:url('fonts/inter-700.woff2') format('woff2'); font-weight:700;
  unicode-range:U+0030-0039,U+002B-002F,U+0025,U+0024,U+00A2-00A5,U+20A0-20BF; }
@font-face { font-family:'Figures'; font-display:swap; src:url('fonts/inter-700.woff2') format('woff2'); font-weight:800;
  unicode-range:U+0030-0039,U+002B-002F,U+0025,U+0024,U+00A2-00A5,U+20A0-20BF; }

:root{
  --paper:#F5F8F9; --sheet:#FFFFFF;
  /* --ink-ghost was #93A7B5: 2.49:1 on white, used 38 times - including the
     10.5px mobile card labels that are the ONLY thing naming a value on a
     phone, read in daylight on a quay. #5F7487 keeps the recessed look and
     passes WCAG AA body text (4.84:1). Do not lighten it back below 4.5. */
  --ink:#142A3B; --ink-soft:#5A7183; --ink-ghost:#5F7487;
  --line:#D9E4EA; --line-strong:#B9CCD8;
  --azure:#1668B8; --azure-deep:#0E3D74; --azure-light:#A9D4F8;
  /* the bright Nodient blue the top bar's block icons wear on the navy
     (decant.trading's brand-blue, 2026-11-02); a theme may set its own */
  --nav-icon:#298EF6;
  --teal:#0F9B8E; --teal-wash:#E7F5F3;
  --amber:#C1731F; --amber-wash:#FBF1E4;
  --red:#B3402F; --red-wash:#FAEBE8;
  --wa:#1FA855; --wa-wash:#E7F5EC;

  /* CORNERS. One scale, four steps, used everywhere - no page invents its own
     radius any more (JRE, 2026-07-31: sharper, but not square). The portal was
     18/11/10 for cards/controls/panels with full 999px pills, which reads
     soft; these are the same shapes with the roundness taken off. Circles
     (50%) are not corners and are left alone. Retune the whole portal here. */
  --r-lg:8px;      /* cards, panels, sheets            (was 14-22) */
  --r-md:6px;      /* buttons, drop zones, notices     (was 9-13)  */
  --r-sm:4px;      /* inputs, selects, small controls  (was 6-8)   */
  --r-pill:4px;    /* chips and badges                 (was 999)   */
  --radius:var(--r-md);

  /* MOTION. One curve and one clock for everything that appears (2026-08-19,
     the fluid-interface audit - docs/FEEL.md). The curve decelerates and does
     NOT overshoot: bounce is for gestures that arrive with momentum, and
     nothing in this portal is thrown. A press has no curve at all - it is
     answered on pointer-down, instantly, which is the whole point of it. */
  --ease-out:cubic-bezier(.2,.7,.3,1);
  --t-pop:.16s;
}
*{ box-sizing:border-box; margin:0; }
/* scrollbar-gutter: a page tall enough to scroll gives ~15px to the
   scrollbar on any browser with classic (non-overlay) bars, and every
   centred layout - the top bar's menu first of all - shifts by that much
   between a short page and a long one. Reserve the gutter always, so the
   bar stands still when Dashboard follows Orders. */
html{ -webkit-text-size-adjust:100%; scrollbar-gutter:stable; }
body{
  font-family:'Figures','Manrope',system-ui,sans-serif;
  background:var(--paper); color:var(--ink);
  font-size:15px; line-height:1.45;
}
button{ font:inherit; cursor:pointer; }
.num{ font-variant-numeric:tabular-nums; }
.eyebrow{
  font-size:11px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-ghost);
}

/* ---------- identity bar ---------- */
.idbar{
  max-width:880px; margin:0 auto; padding:20px 24px 0;
  display:flex; align-items:center; justify-content:space-between; gap:16px;
}
.idbar .brand{ display:flex; align-items:baseline; gap:10px; }
.idbar .brand b{ font-weight:800; font-size:17px; letter-spacing:-.01em; color:var(--azure-deep); }
.idbar .brand span{ color:var(--ink-soft); font-size:13px; }
.idbar .right{ display:flex; align-items:center; gap:18px; }
.idbar .me{ font-size:13px; color:var(--ink-soft); display:flex; align-items:center; gap:8px; }
.idbar .me b{ color:var(--ink); font-weight:700; }
.idbar .me .dot{ width:8px; height:8px; border-radius:50%; background:var(--teal); }

.langs{ display:flex; gap:2px; background:#fff; border:1px solid var(--line); border-radius:var(--r-sm); padding:2px; }
.langs button{
  font-size:12px; font-weight:700; letter-spacing:.04em;
  color:var(--ink-ghost); background:none; border:none;
  padding:4px 9px; border-radius:var(--r-sm);
}
.langs button.on{ color:#fff; background:var(--azure); }
.langs button:not(.on):hover{ color:var(--ink); }

/* ---------- the trade document ---------- */
.doc{
  max-width:880px; margin:20px auto 0;
  background:var(--sheet);
  border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:0 1px 2px rgba(20,42,59,.04), 0 8px 28px rgba(20,42,59,.05);
  overflow:hidden;
}
.masthead{
  padding:28px 32px 22px;
  border-bottom:1px solid var(--line);
  display:grid; grid-template-columns:1fr auto;
  gap:8px 24px; align-items:end;
}
.masthead h1{ font-size:38px; font-weight:800; letter-spacing:-.03em; line-height:1.05; margin-top:2px; }
.masthead h1 small{ font-weight:500; font-size:20px; color:var(--ink-soft); letter-spacing:-.01em; }
.masthead .fields{ display:flex; gap:28px; text-align:right; }
.masthead .field .eyebrow{ margin-bottom:2px; }
.masthead .field b{ font-size:15px; font-weight:700; white-space:nowrap; }
.masthead .cutoff{ color:var(--amber); }

.docnote{
  padding:12px 32px;
  background:rgba(22,104,184,.03);
  border-bottom:1px solid var(--line);
  font-size:13.5px; color:var(--ink-soft);
  display:flex; align-items:center; gap:10px;
}
.docnote svg{ flex:none; }
.docnote b{ color:var(--ink); font-weight:700; }

/* ---------- document table ---------- */
.doc table{ width:100%; border-collapse:collapse; }
.doc thead th{
  font-size:11px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color:var(--ink-ghost); text-align:left;
  padding:14px 12px 10px;
  border-bottom:1px solid var(--line-strong);
  background:var(--sheet);
  position:sticky; top:0; z-index:2;
}
.doc thead th:first-child{ padding-left:32px; }
.doc thead th.r{ text-align:right; }
.doc tbody td{
  padding:0 12px;
  border-bottom:1px solid var(--line);
  height:52px; vertical-align:middle;
}
.doc tbody td:first-child{ padding-left:20px; }
.doc tbody tr:last-child td{ border-bottom:none; }
.doc td.r{ text-align:right; white-space:nowrap; }

.prod{ display:flex; align-items:center; gap:12px; }
.prod .tick{ width:4px; height:28px; border-radius:2px; background:transparent; flex:none; transition:background .15s; }
tr.changed .prod .tick{ background:var(--teal); }
.prod .name{ font-weight:700; font-size:14.5px; letter-spacing:-.005em; }
.prod .sub{ font-size:12px; color:var(--ink-ghost); }

input.cell{
  font:inherit; font-weight:600; font-variant-numeric:tabular-nums;
  color:var(--ink);
  width:96px; height:36px;
  padding:0 10px; text-align:right;
  background:var(--paper);
  border:1px solid transparent; border-radius:var(--r-sm);
  transition:border-color .12s, background .12s;
}
input.cell::placeholder{ color:var(--ink-ghost); font-weight:500; }
input.cell:hover{ border-color:var(--line-strong); }
input.cell:focus{
  outline:none; background:#fff;
  border-color:var(--azure);
  box-shadow:0 0 0 3px rgba(22,104,184,.12);
}
input.cell.carried{ color:var(--ink-ghost); }
tr.changed input.cell.touched{ color:var(--ink); background:var(--teal-wash); }

/* The same field, as a dropdown. Every rule above is `input.cell`, so a
   <select class="cell"> fell through to native OS chrome - a black-bordered
   box with a system arrow, sitting among the portal's own soft fields.
   Left-aligned because a select holds a name, not a number. */
select.cell{
  font:inherit; font-weight:600; color:var(--ink);
  height:36px; padding:0 30px 0 10px; text-align:left;
  border:1px solid transparent; border-radius:var(--r-sm);
  -webkit-appearance:none; appearance:none; cursor:pointer;
  background-color:var(--paper);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.6 6 6.4 11 1.6' fill='none' stroke='%237A8C99' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 10px center;
  background-size:11px 7px;
  transition:border-color .12s, background-color .12s;
}
select.cell:hover{ border-color:var(--line-strong); }
select.cell:focus{
  outline:none; background-color:#fff;
  border-color:var(--azure);
  box-shadow:0 0 0 3px rgba(22,104,184,.12);
}
select.cell.small{ height:32px; font-size:13px; }
.unit{ font-size:12px; color:var(--ink-ghost); margin-left:6px; }
/* THE CURRENCY LEADS ITS BOX (JRE, 2026-08-07). Every printed figure on these
   pages is "€ 19,95" - `fmt_money` puts the symbol in front - so a typed one
   reading "19.95 €" was the only money on the page written the other way
   round. Units that are not a currency (kg) stay behind their number, which
   is how they are said. */
.unit.pre{ margin-left:0; margin-right:6px; }
.rowmeta{ font-size:12.5px; color:var(--ink-ghost); white-space:nowrap; }
tr.changed .rowmeta.status{ color:var(--teal); font-weight:600; }

tr.noStock td{ opacity:.45; }
.toggle-stock{
  font-size:12.5px; font-weight:600;
  color:var(--ink-ghost); background:none; border:none;
  padding:6px 8px; border-radius:var(--r-sm); white-space:nowrap;
}
.toggle-stock:hover{ color:var(--amber); background:var(--amber-wash); }
tr.noStock .toggle-stock{ color:var(--amber); opacity:1; }

/* ---------- buttons ---------- */
.btn{
  font-size:13.5px; font-weight:700;
  border-radius:var(--r-sm); padding:9px 16px;
  border:1px solid var(--line-strong); background:#fff; color:var(--ink);
  transition:border-color .12s, background .12s, color .12s, transform .09s ease;
  white-space:nowrap; text-decoration:none; display:inline-block;
}
.btn:hover{ border-color:var(--azure); color:var(--azure); }
/* the press is acknowledged the instant it lands, not on release */
.btn:active{ transform:scale(.97); }
.btn.primary{
  background:var(--azure); border-color:var(--azure); color:#fff;
  box-shadow:0 2px 8px rgba(22,104,184,.25);
}
.btn.primary:hover{ background:var(--azure-deep); color:#fff; }
.btn.ghost{ border-color:transparent; color:var(--ink-soft); }
.btn.ghost:hover{ color:var(--azure); }
.btn.dashed{
  color:var(--azure); background:none; border:1px dashed var(--line-strong);
}
.btn.dashed:hover{ border-color:var(--azure); background:rgba(22,104,184,.04); }

/* ---------- sticky action bar ---------- */
.sendbar{
  position:fixed; left:0; right:0; bottom:0; z-index:10;
  background:rgba(255,255,255,.92);
  backdrop-filter:blur(8px);
  border-top:1px solid var(--line);
}
.sendbar .inner{
  max-width:880px; margin:0 auto; padding:14px 24px;
  display:flex; align-items:center; justify-content:space-between; gap:16px;
}
.sendbar .delta{ font-size:14px; color:var(--ink-soft); }
.sendbar .delta b{ color:var(--teal); font-weight:800; }
.sendbar .delta.zero b{ color:var(--ink-soft); font-weight:600; }
.send{
  font-size:15px; font-weight:800; letter-spacing:-.01em;
  color:#fff; background:var(--azure);
  border:none; border-radius:var(--r-md);
  padding:13px 26px;
  box-shadow:0 2px 8px rgba(22,104,184,.35);
  transition:background .12s, transform .06s;
}
.send:hover{ background:var(--azure-deep); }
.send:active{ transform:translateY(1px); }

/* ---------- receipt ---------- */
.receipt{
  display:flex; flex-direction:column; align-items:center; gap:10px;
  padding:64px 32px 72px; text-align:center;
}
.receipt .check{
  width:56px; height:56px; border-radius:50%;
  background:var(--teal-wash); color:var(--teal);
  display:grid; place-items:center; font-size:26px; font-weight:800;
}
.receipt h2{ font-size:24px; font-weight:800; letter-spacing:-.02em; }
.receipt p{ color:var(--ink-soft); max-width:420px; }
.receipt p b{ color:var(--ink); }
.receipt .edit{
  font-size:14px; font-weight:700; color:var(--azure);
  background:none; border:none; margin-top:6px;
}

/* ---------- info card below the doc ---------- */
.aside-card{
  max-width:880px; margin:20px auto 0; padding:22px 32px;
  border:1px dashed var(--line-strong); border-radius:var(--radius);
  color:var(--ink-soft); font-size:14px;
  display:flex; align-items:center; gap:14px;
}
.aside-card b{ color:var(--ink); }
.aside-card a{ color:var(--azure); font-weight:700; text-decoration:none; margin-left:auto; white-space:nowrap; }
.aside-card .ico{
  width:38px; height:38px; border-radius:var(--r-md); flex:none;
  background:var(--sheet); border:1px solid var(--line);
  display:grid; place-items:center; font-size:17px;
}

/* ---------- flash (inline confirmation after a POST+redirect) ----------
   Was copy-pasted into operator_history / operator_orders and USED but never
   defined in operator_today, so a message there rendered unstyled. Same
   values as those copies, so nothing shifts. */
.rb .warntext{ color:var(--amber); }

.rb .flash{ padding:11px 16px; border-radius:var(--r-md); font-size:13.5px; font-weight:700;
  margin-bottom:16px; }
.rb .flash.ok{ background:var(--teal-wash); color:#0B7268; }
.rb .flash.err{ background:var(--red-wash); color:var(--red); }

/* ---------- toast ---------- */
.toast{
  position:fixed; bottom:22px; left:50%;
  background:var(--ink); color:#fff;
  font-size:13.5px; font-weight:600;
  padding:10px 18px; border-radius:var(--r-md);
  opacity:0; pointer-events:none;
  /* it rises IN from below and leaves back DOWN the same path - a transition,
     not a one-way keyframe, so enter and exit are mirrors of each other */
  transform:translateX(-50%) translateY(8px);
  transition:opacity .18s ease, transform var(--t-pop) var(--ease-out);
  z-index:50;
}
.toast.show{ opacity:1; transform:translateX(-50%); }

/* ---------- date chips ---------- */
.datechips{ max-width:880px; margin:14px auto 0; padding:0 24px; display:flex; gap:8px; flex-wrap:wrap; }
.datechips a{
  font-size:12.5px; font-weight:700; text-decoration:none;
  color:var(--ink-soft); background:#fff;
  border:1px solid var(--line); border-radius:var(--r-pill); padding:6px 13px;
}
.datechips a.on{ color:#fff; background:var(--azure); border-color:var(--azure); }
.datechips a:not(.on):hover{ border-color:var(--line-strong); color:var(--ink); }

@media (max-width:720px){
  .masthead{ grid-template-columns:1fr; }
  .masthead .fields{ text-align:left; }
  input.cell{ width:76px; }
}
@media (prefers-reduced-motion:reduce){ *{ transition:none!important; animation:none!important; } }


/* ==================================================================
   OPERATOR SHELL - top nav + day-strip, shared by Prices/Offer/Orders
   ================================================================== */
.opbar{
  max-width:1060px; margin:0 auto; padding:18px 24px 0;
  display:flex; align-items:center; justify-content:space-between; gap:16px;
}
.opbar .brand{ display:flex; align-items:baseline; gap:10px; }
.opbar .brand b{ font-weight:800; font-size:17px; letter-spacing:-.01em; color:var(--azure-deep); }
.opbar .brand span{ color:var(--ink-soft); font-size:13px; }
.opnav{ display:flex; gap:4px; }
.opnav a{
  font-size:13.5px; font-weight:700; text-decoration:none; color:var(--ink-soft);
  padding:7px 14px; border-radius:var(--r-sm);
}
.opnav a:hover{ color:var(--ink); background:var(--paper); }
.opnav a.on{ color:var(--azure); background:rgba(22,104,184,.08); }
.opright{ display:flex; align-items:center; gap:8px; }

.who{ display:flex; gap:3px; background:#fff; border:1px solid var(--line); border-radius:var(--r-pill); padding:3px; }
.who button{
  display:flex; align-items:center; gap:7px;
  font:inherit; font-size:12.5px; font-weight:700;
  color:var(--ink-soft); background:none; border:none; cursor:pointer;
  padding:4px 12px 4px 5px; border-radius:var(--r-pill);
}
.who button.on{ color:var(--ink); background:var(--paper); box-shadow:inset 0 0 0 1px var(--line); }
.who .ini{ width:22px; height:22px; border-radius:50%; flex:none; display:grid; place-items:center; font-size:11px; font-weight:800; color:#fff; }
.who button:nth-child(1) .ini{ background:var(--teal); }
.who button:nth-child(2) .ini{ background:var(--azure); }
.who button:not(.on){ opacity:.6; }
.who button:not(.on):hover{ opacity:1; }

.daystrip{ max-width:1060px; margin:16px auto 0; padding:0 24px; }
.daycard{
  background:var(--sheet); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:0 1px 2px rgba(20,42,59,.04);
  padding:16px 22px; display:flex; align-items:center; gap:26px;
}
.daycard .date .eyebrow{ margin-bottom:1px; }
.daycard .date b{ font-size:19px; font-weight:800; letter-spacing:-.02em; white-space:nowrap; }
.daycard .date span{ color:var(--ink-soft); font-size:13px; white-space:nowrap; }
.stages{ display:flex; gap:8px; flex:1; }
.stage{
  display:flex; align-items:center; gap:10px;
  padding:9px 14px; border-radius:var(--r-md); border:1px solid var(--line);
  text-decoration:none; color:var(--ink); font-size:13.5px; flex:1; min-width:0;
  transition:border-color .12s, background .12s;
}
.stage:hover{ border-color:var(--line-strong); }
.stage.on{ border-color:var(--azure); background:rgba(22,104,184,.05); box-shadow:inset 0 0 0 1px var(--azure); }
.stage .st-ic{ width:26px; height:26px; border-radius:var(--r-sm); flex:none; display:grid; place-items:center; font-size:13px; font-weight:800; }
.stage .st-ic.busy{ background:var(--amber-wash); color:var(--amber); }
.stage .st-ic.todo{ background:var(--paper); color:var(--ink-ghost); }
.stage .st-ic.ok{ background:var(--teal-wash); color:var(--teal); }
.stage b{ font-weight:800; display:block; line-height:1.2; }
.stage small{ color:var(--ink-soft); font-size:12px; display:block; line-height:1.2; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.daycut{ text-align:right; white-space:nowrap; }
/* calm brand color, NOT amber: the time-to-target is information, not an alarm */
.daycut b{ font-size:19px; font-weight:800; color:var(--azure-deep); font-variant-numeric:tabular-nums; display:block; line-height:1.1; }
.daycut.over b{ color:var(--ink-ghost); }

.opmain{ max-width:1060px; margin:22px auto 0; padding:0 24px; }
.pagehead{ display:flex; align-items:baseline; justify-content:space-between; gap:16px; margin-bottom:14px; }
.pagehead h1{ font-size:26px; font-weight:800; letter-spacing:-.025em; }
.pagehead .sub{ color:var(--ink-soft); font-size:14px; }
.pagehead .sub b{ color:var(--ink); }

@media (max-width:820px){
  .opbar{ flex-wrap:wrap; }
  .opnav{ order:3; width:100%; }
  .daycard{ flex-wrap:wrap; }
  .stages{ order:3; width:100%; }
}
@media (max-width:600px){
  .stages{ flex-direction:column; }
  .daycut{ text-align:left; }
}


/* ==================================================================
   REBRAND SHELL (July 2026)
   Dashboard grammar from static/rebrand/: top bar, voyage rail, stat
   tiles, card/table/pill set. Everything is scoped under `.rb` on
   <body> so pages that have not been ported yet keep the old styles
   untouched. Port a page = add class="rb" + new markup; when the last
   page has moved over, the sections above this one can go.

   Tokens are the ones already declared at the top of this file, so a
   TENANT.theme override of --azure / --teal still reaches every
   component here. Only genuinely new roles are added below.
   ================================================================== */

.rb{
  --harbor:#0B1F33;
  --ground:#EEF2F7;
  --sunken:#F4F7FA;
  --azure-wash:#E7F0F9;
  --line-soft:#EDF2F7;
  /* the operator shell's own names for the same three steps - kept so its
     rules read as "card" and "control", not as numbers */
  --r-card:var(--r-lg); --r-ctrl:var(--r-md);
  --navbar:58px; --topbar:69px;     /* == the real .topnav and .top heights, so
                                       the sticky stack (nav / page head /
                                       sendbar) lands flush instead of behind */
  --shadow:0 1px 2px rgba(20,42,59,.04), 0 14px 30px -18px rgba(20,42,59,.22);
  --shadow-lg:0 2px 4px rgba(20,42,59,.05), 0 24px 48px -24px rgba(20,42,59,.30);
  /* for surfaces that sit in a row of their own kind (the stat strip): three
     cards each casting --shadow reads as three weights, not one strip */
  --shadow-soft:0 1px 2px rgba(20,42,59,.03), 0 8px 20px -14px rgba(20,42,59,.16);
  background:var(--ground);
}
.rb *,.rb *::before,.rb *::after{ box-sizing:border-box; }
.rb a{ color:var(--azure); text-decoration:none; }
.rb a:hover{ text-decoration:underline; }
.rb :focus-visible{ outline:2px solid var(--azure); outline-offset:2px; border-radius:var(--r-sm); }
.rb .eyebrow{ font-size:10.5px; font-weight:700; letter-spacing:.1em;
  text-transform:uppercase; color:var(--ink-ghost); }
.rb .muted{ color:var(--ink-soft); }
.rb .tiny{ font-size:12px; }
.rb .n{ text-align:right; font-variant-numeric:tabular-nums; }

/* ---------- buttons ---------- */
.rb .btn{
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  font:inherit; font-size:13px; font-weight:700; letter-spacing:-.005em;
  padding:9px 16px; border-radius:var(--r-ctrl); border:1px solid transparent;
  background:var(--azure); color:#fff; cursor:pointer; white-space:nowrap;
  transition:filter .15s ease, background .15s ease, transform .09s ease;
}
.rb .btn:hover{ filter:brightness(1.07); text-decoration:none; color:#fff; }
/* Feedback lives on the PRESS, not the release (the fluid-interface audit,
   2026-08-19 - docs/FEEL.md): the button answers the finger the instant it
   lands. 3% is a press, not a bounce; the disabled forms stay still. */
.rb .btn:active:not([disabled]):not(.is-off){ transform:scale(.97); }
.rb .btn[disabled],.rb .btn.is-off{ opacity:.45; cursor:not-allowed; filter:none; }
.rb .btn.teal{ background:var(--teal); }
.rb .btn.amber{ background:var(--amber); }
.rb .btn.harbor{ background:var(--harbor); }
.rb .btn.ghost{ background:#fff; color:var(--ink); border-color:var(--line); }
.rb .btn.ghost:hover{ background:var(--sunken); filter:none; color:var(--ink); }
.rb .btn.quiet{ background:transparent; color:var(--ink-soft); border-color:transparent; }
.rb .btn.quiet:hover{ background:var(--sunken); filter:none; color:var(--ink); }
.rb .btn.sm{ padding:6px 11px; font-size:12px; border-radius:var(--r-md); }
.rb .btn.block{ width:100%; }
.rb .btn svg{ width:15px; height:15px; flex:none; }

/* ---------- pills ---------- */
.rb .pill{
  display:inline-flex; align-items:center; gap:5px;
  font-size:11.5px; font-weight:600; padding:4px 10px; border-radius:var(--r-pill);
  background:var(--sunken); color:var(--ink-soft); white-space:nowrap;
}
/* No leading dot on a pill (JRE, 2026-07-31). The wash and the colour already
   say what the pill is; the dot was a second, quieter copy of the same signal.
   `.pill.plain` used to be the opt-out and is now simply the default - the
   class is still accepted so the templates that carry it keep reading right. */
.rb .pill.ok{ background:var(--teal-wash);  color:#0B7268; }
.rb .pill.wait{ background:var(--amber-wash); color:#8F5415; }
.rb .pill.bad{ background:var(--red-wash);   color:var(--red); }
.rb .pill.info{ background:var(--azure-wash); color:#10538F; }
.rb .pill.wa{ background:var(--wa-wash); color:#157F41; }

/* ---------- fields ---------- */
.rb .field{
  font:inherit; font-size:13.5px; color:var(--ink); width:100%;
  padding:10px 12px; border:1px solid var(--line); border-radius:var(--r-ctrl);
  background:#fff; transition:border-color .15s, box-shadow .15s;
}
.rb .field::placeholder{ color:var(--ink-ghost); }
.rb .field:focus{ outline:none; border-color:var(--azure);
  box-shadow:0 0 0 3px var(--azure-wash); }
.rb .field.sm{ padding:6px 9px; font-size:13px; border-radius:var(--r-sm); }
.rb .field.num{ width:86px; text-align:right; font-variant-numeric:tabular-nums; font-weight:600; }
/* A PRICE IS THE POINT OF THE PAGE, so it may not be the smallest text on it
   (JRE, 2026-07-30). The number cells inherited .field.sm at 13px while the
   body runs at 15px, which made the one figure a supplier is actually here to
   check smaller than the label next to it - on a phone, in a cold room, at
   arm's length. Bigger, and one step heavier so the carried-over grey still
   reads as a number rather than a smudge. */
.rb .field.sm.num{ font-size:15.5px; padding:7px 10px; }
.rb .field.changed{ border-color:var(--teal); background:var(--teal-wash); font-weight:700; }
.rb .field.todo{ border-color:var(--amber); background:var(--amber-wash); }
.rb textarea.field{ min-height:92px; resize:vertical; line-height:1.5; }
.rb .dropzone{ border:1.5px dashed var(--line); border-radius:var(--r-lg);
  padding:26px 20px; text-align:center; background:var(--sunken); }
/* A <label> is inline by default, and an inline box with block children draws
   its border in broken fragments down the left instead of around the content -
   which is exactly how the invoice dropzone rendered when it started using
   this class (JRE, 2026-07-28: "factuur sturen knop is niet goed"). The rule
   existed, but only inside the price sheet's own <style>. It belongs here,
   with the class it fixes. */
.rb label.dropzone{ display:block; cursor:pointer;
  transition:border-color .15s, background .15s; }
.rb label.dropzone:hover{ border-color:var(--azure); background:var(--azure-wash); }
/* Dragging a file over it. Solid rather than dashed: while a file is on its
   way in, the box is a target being aimed at, not an invitation. Same azure
   as :hover, one step stronger, so the two states read as one gesture. */
.rb label.dropzone.over{ border-style:solid; border-color:var(--azure);
  background:var(--azure-wash); box-shadow:0 0 0 3px var(--azure-wash); }
.rb label.dropzone.over .dz-ico{ border-color:var(--azure); }
.rb .dz-ico{ width:40px; height:40px; border-radius:var(--r-md); margin:0 auto 10px;
  display:grid; place-items:center; background:#fff; border:1px solid var(--line);
  color:var(--azure); }

/* ---------- the upload progress ring (static/upload-progress.js) ----------
   While a file is on the wire the box shows nothing else: its own caption and
   icon are hidden, so there is no second thing to press and no sentence left
   standing that has stopped being true. */
.rb label.dropzone.sending{ cursor:default; border-style:solid;
  border-color:var(--azure); background:var(--azure-wash); }
.rb label.dropzone.sending > *:not(.uplring){ display:none; }
.rb .uplring{ display:grid; place-items:center; gap:8px;
  grid-template-rows:auto auto; justify-items:center; }
.rb .uplring-track{ fill:none; stroke:var(--line); stroke-width:3.5; }
.rb .uplring-val{ fill:none; stroke:var(--azure); stroke-width:3.5;
  stroke-linecap:round; }
/* The percentage sits IN the ring, so the two read as one object: both are
   placed in the SAME grid cell and centred there, with the caption below in
   row 2. It used to be `position:absolute; top:0; left:0`, which anchors to
   the WRAPPER - and the wrapper is as wide as the caption, so the number sat
   at the far left of a wide box while the ring stayed in the middle (JRE,
   2026-08-05). Do not pin it to a corner again: the two are concentric or
   they are not one object. */
.rb .uplring-svg{ grid-area:1 / 1; width:56px; height:56px;
  transform:rotate(-90deg); }
.rb .uplring-pct{ grid-area:1 / 1; font-size:13px; font-weight:800;
  color:var(--ink); font-variant-numeric:tabular-nums; }
.rb .uplring-cap{ grid-area:2 / 1; font-size:12.5px; font-weight:700;
  color:var(--azure); }
/* No spinner and no sweep anywhere: the ring is always a real position now,
   so a second animation on top of it only made the arc look like it was
   fighting itself (JRE, 2026-07-30). The one movement is the arc growing,
   eased over slightly longer than the estimate's own tick so consecutive
   steps join into one glide instead of 8 visible jumps a second. */
.rb .uplring-val{ transition:stroke-dashoffset .16s ease-out; }
@media (prefers-reduced-motion:reduce){
  .rb .uplring-val{ transition:none; }
}

/* The same ring on a BUTTON (`[data-upl-zone]`, e.g. Match Invoice on Orders).
   A document read by the AI reader is ten to thirty seconds of a page that
   looks frozen, and the one control there is a file input: pressing again
   reads the same invoice a second time and proposes it twice (JRE,
   2026-09-20: "add a loading screen circle so we can clearly see its
   loading"). So the button becomes the ring while it runs.

   It is the ring laid on its SIDE rather than a second animation: 15px of
   the same arc, and the percentage moves out of the middle into the caption
   line, because a number inside a 15px circle is not a number. Everything
   else - what the arc means, where it stops, that it only reaches 100 when
   the server has answered - is the block above unchanged. */
.rb .uplbtn.sending{ cursor:default; pointer-events:none;
  border-color:var(--azure); color:var(--azure); background:var(--azure-wash); }
.rb .uplbtn.sending > *:not(.uplring){ display:none; }
.rb .uplring.mini{ grid-template-rows:auto; grid-template-columns:auto auto auto;
  gap:7px; align-items:center; }
.rb .uplring.mini .uplring-svg{ grid-area:1 / 1; width:15px; height:15px; }
.rb .uplring.mini .uplring-cap{ grid-area:1 / 2; font-size:inherit; }
.rb .uplring.mini .uplring-pct{ grid-area:1 / 3; font-size:inherit; color:var(--azure); }
.rb .uplring.mini .uplring-track,.rb .uplring.mini .uplring-val{ stroke-width:5; }

.rb .dz-title{ font-size:14.5px; font-weight:700; letter-spacing:-.01em; }
.rb .dz-sub{ font-size:12.5px; color:var(--ink-soft); margin-top:3px; }

/* ---------- cards ---------- */
.rb .card{
  background:var(--sheet); border:1px solid var(--line);
  border-radius:var(--r-card); box-shadow:var(--shadow); overflow:hidden;
}
/* only stacked cards straight in the content column: inside .g2 / .g-side /
   .stack the grid gap already spaces them, and this margin would shove the
   second card down by an extra 16px */
.rb .content > .card + .card{ margin-top:16px; }
.rb .card-head{ display:flex; align-items:center; gap:12px; padding:18px 22px 14px; }
.rb .card-head > .ch{ flex:1; min-width:0; }
.rb .card-head h2,.rb .card-head h3{
  font-size:15px; font-weight:700; letter-spacing:-.015em; line-height:1.2; }
.rb .card-head .sub{ font-size:12.5px; color:var(--ink-ghost); margin-top:2px; }
.rb .card-head .acts{ display:flex; align-items:center; gap:8px; flex:none; }
.rb .card-head .acts form.inl{ display:inline; margin:0; }
.rb .card-body{ padding:4px 22px 20px; }
.rb .card-body.flush{ padding:0; }
.rb .card-foot{
  padding:14px 22px; border-top:1px solid var(--line); background:var(--sunken);
  display:flex; align-items:center; justify-content:space-between; gap:12px;
}
.rb .card.warn{ border-color:#EBC79B; }
.rb .card.bad{ border-color:#E4B0A6; }

/* ---------- a card that folds ----------
   A register long enough to bury the page under it: the supplier table is
   80 rows and Settings has four more cards below it. The card head IS the
   control - the whole strip, not a hidden toggle off to one side - so the
   heading and its explanation stay readable while the card is shut, and the
   count in `.acts` keeps saying how much is folded away. A <details> rather
   than scripted state, same reasoning as the account menu: it opens with a
   click or a key, it prints open, and it works before any JS runs. */
.rb details.foldcard > summary{ cursor:pointer; list-style:none; }
.rb details.foldcard > summary::-webkit-details-marker{ display:none; }
.rb details.foldcard > summary:hover h2{ color:var(--azure); }
.rb details.foldcard > summary:focus-visible{ outline:2px solid var(--azure);
  outline-offset:-3px; }
/* The chevron is last in the flex row, so it sits at the far right whatever
   the head carries. It is a flex item, which is what makes it transformable. */
.rb details.foldcard > summary::after{ content:'⌄'; flex:none; align-self:center;
  color:var(--ink-ghost); font-size:15px; line-height:1; margin-top:-5px;
  transition:transform .15s; }
.rb details.foldcard[open] > summary::after{ transform:rotate(180deg);
  margin-top:3px; }

/* ---------- summary strip ----------
   The quiet form of the stat tiles (JRE, 2026-07-26). On a page whose real
   work is the table or the list underneath, full-size tiles spend the top
   third of the screen on three or four numbers. Same facts, same wording,
   one row. Tiles stay for pages where the number IS the point.
   `.fig` not `.n`: `.n` is the right-aligning table class further down.

   White, not navy (JRE, 2026-07-27) - the same call as the lead tile, for
   the same reason. It is a quiet summary of a page whose work is below it;
   a dark slab across the top was the loudest thing on screen. Weights match
   the stat tiles exactly: 700 figure, 500 label, ghost note. */
.rb .sumstrip{ display:flex; flex-wrap:wrap; align-items:stretch; gap:0;
  background:var(--sheet);
  border:1px solid var(--line-soft); border-radius:var(--r-card); color:var(--ink);
  box-shadow:var(--shadow-soft);
  padding:2px 0; margin-bottom:16px; }
.rb .sumstrip .f{ flex:1 1 160px; padding:12px 18px;
  border-left:1px solid var(--line-soft); }
.rb .sumstrip .f:first-child{ border-left:none; }
.rb .sumstrip .fig{ font-size:20px; font-weight:700; letter-spacing:0; color:var(--ink);
  font-variant-numeric:tabular-nums; line-height:1.15; }
.rb .sumstrip .fig .of{ font-size:13px; font-weight:500; color:var(--ink-ghost); }
/* on white the page amber reads straight; the lifted #F0B45E it used to need
   against navy is now just a washed-out yellow, so it goes back to the token */
.rb .sumstrip .fig.warn{ color:var(--amber); }
.rb .sumstrip .k{ font-size:12px; font-weight:500; color:var(--ink-soft); margin-top:1px; }
/* `.h`, the third line under each figure, went on 2026-08-04 (JRE): a tile is
   a number and what it is. Its rule goes with it rather than waiting for a
   page to use it again - the same day took `.stat-note` out of the tiles. */

/* ---------- stat tiles ----------
   Lightened (JRE, 2026-07-27). The strip read heavy because nearly every
   string in it sat at 700/800 - a 30px number at 800 next to an 11.5px pill
   at 800 gives the eye no rest, so all three tiles shouted equally. The fix
   is contrast, not size: the number keeps the weight, everything around it
   steps down. Manrope stays; its 800 is simply too dense for small text.
   Do not push these back up one at a time - that is how it got here. */
/* 230, not 196 (JRE, 2026-08-01): at 196 a ~1100px row squeezed five tiles to
   207px, which is under what an icon and a figure need side by side - so the
   tiles quietly stacked again at exactly the laptop widths this is read on.
   Wrapping to a second row is the better trade: four wide tiles beat five
   narrow ones with the number pushed under its icon. */
.rb .stats{ display:grid; gap:16px; margin-bottom:20px;
  grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); }
/* One row, not two (JRE, 2026-07-27). The icon used to sit on its own 36px
   line above the number with a gap under it - a third of the tile's height
   spent on decoration. It now sits beside the number, and the delta pill
   takes a third column, so the tile is only as tall as its text.

   `.stat-top` is display:contents so its two children become grid items of
   the tile itself: the markup in operator_today/-history still nests them,
   and neither template had to change. */
.rb .stat{
  position:relative;
  background:var(--sheet); border:1px solid var(--line-soft); border-radius:var(--r-card);
  box-shadow:var(--shadow-soft); padding:14px 18px;
  display:grid; grid-template-columns:auto 1fr;
  column-gap:14px; align-items:start;
  container-type:inline-size;
}
.rb .stat-top{ display:contents; }
.rb .stat-ico,.rb .ring{ grid-column:1; grid-row:1; }
.rb .stat > div:not(.stat-top){ grid-column:2; grid-row:1; min-width:0; }
/* out of flow, or it takes a column off the text and the longer labels
   ("Nothing goes live without your approval") wrap onto a second line -
   which put the height straight back into every tile in the row */
.rb .delta{ position:absolute; top:14px; right:18px; }

/* The icon stays BESIDE the figure at every width (JRE, 2026-08-01). It used
   to fall back to icon-above under 420px, which is every tile on Numbers
   (five across = 266px each), so the layout the rule above describes was the
   one nobody ever saw - a 36px chip on its own line with the number under it.

   What actually breaks at narrow widths is the FIGURE, not the arrangement:
   "€ 104.336,49" is ~193px at 30px and the column is 178px at five across.
   So the figure steps down with the tile instead of the icon stepping up,
   and it never wraps mid-amount. Measured against the widest real figure on
   the page, not a guessed budget. */
/* The delta pill is out of flow at the top right, so it overlaps the figure
   rather than pushing it - only a tile that HAS one pays for it. */
.rb .stat:has(.delta) > div:not(.stat-top){ padding-right:78px; }
/* Below that, reserving 78px would leave the figure ~15px - smaller than its
   own label. The pill drops to its own line under the text instead: the
   comparison is the least urgent thing in the tile, so it is the thing that
   moves. Costs ~26px of height, and only on a page that shows deltas. */
@container (max-width:320px){
  .rb .stat:has(.delta) > div:not(.stat-top){ padding-right:0; }
  .rb .delta{ position:static; grid-column:2; grid-row:2;
    justify-self:start; margin-top:9px; }
}
/* The figure's own step-down sits AFTER its base rule further down this file:
   a container query carries no extra specificity, so a later plain
   `.stat-val{font-size:30px}` simply wins over one written up here. */
/* A phone column is wide enough for the full figure again - the tile is the
   screen, not a fifth of a row. Only genuinely tiny containers stack. */
@container (max-width:184px){
  .rb .stat{ grid-template-columns:1fr; row-gap:10px; }
  .rb .stat-ico,.rb .ring{ grid-column:1; grid-row:1; }
  .rb .stat > div:not(.stat-top){ grid-column:1; grid-row:2; }
}
.rb .stat-ico{ width:36px; height:36px; border-radius:var(--r-md); flex:none;
  display:grid; place-items:center; background:var(--sunken); color:var(--ink-ghost); }
.rb .stat-ico svg{ width:18px; height:18px; }
.rb .stat-ico.azure{ background:var(--azure-wash); color:var(--azure); }
.rb .stat-ico.teal{ background:var(--teal-wash); color:#0B7268; }
.rb .stat-ico.amber{ background:var(--amber-wash); color:#8F5415; }
/* Tracking is NEUTRAL on the figures, not negative (JRE, 2026-07-27: "not
   spaced enough"). Tightening is a display-type trick for words; these are
   tabular numerals, whose side bearings are already drawn to a fixed advance
   so columns of them line up. Pulling -.02em out of every pair crowded the
   characters that most need the air - the thousands dots and the currency
   symbol - and squeezed the space inside a unit like " kg" as well.
   Costs no height, which is the point: the tiles stay as short as they are. */
.rb .stat-val{ font-size:30px; font-weight:700; letter-spacing:0; line-height:1;
  font-variant-numeric:tabular-nums; white-space:nowrap; }
/* Steps measured against the widest real figure on Numbers ("€ 104.336,49",
   ~193px at 30px) and the room it has: the figure's column is the container
   minus the 36px icon and the 14px gap.

   NOTE the widths below are CONTENT-box: `container-type:inline-size` queries
   the container's content box, so a 266px tile with 18px padding answers 230.
   Reading them as tile widths is how the first attempt came out a step too
   small on every tile. */
@container (max-width:243px){ .rb .stat-val{ font-size:25px; } }   /* 161px wide */
@container (max-width:210px){ .rb .stat-val{ font-size:21px; } }   /* 135px wide */
/* no margin here: the templates carry their own spacing literally, and they
   are not the same - " kg" takes a space, "%" and "/20" do not */
.rb .stat-val .of{ font-size:16px; font-weight:500; color:var(--ink-ghost); letter-spacing:0; }
.rb .stat-label{ font-size:13px; color:var(--ink-soft); font-weight:500;
  margin-top:5px; line-height:1.3; }
/* `.stat-note` was a third line under the label and went on 2026-08-04 (JRE),
   the same day `.sumstrip .h` did: a tile is a number and what it is. The
   comparison it carried lives on in the `.delta` chip above the figure. */
.rb .delta{ display:inline-flex; align-items:center; gap:3px;
  font-size:11.5px; font-weight:600; padding:4px 9px; border-radius:var(--r-pill);
  background:var(--sunken); color:var(--ink-soft); font-variant-numeric:tabular-nums; }
.rb .delta.up{ background:var(--teal-wash); color:#0B7268; }
.rb .delta.down{ background:var(--red-wash); color:var(--red); }
.rb .delta.warn{ background:var(--amber-wash); color:#8F5415; }

/* `.lead` no longer paints the tile navy (JRE, 2026-07-27). A filled dark
   block plus its drop shadow made the first tile the loudest object on the
   page and the two beside it look unfinished - it was emphasis applied
   twice over, to a number that already leads by being the money. The class
   stays as a hook (both templates set it) and now marks the tile with the
   azure icon only. Do not restore the fill: if the money needs to stand
   out further, do it with the icon or the number, not with a slab. */
.rb .stat.lead .stat-ico{ background:var(--azure-wash); color:var(--azure); }

/* sized to the icon tiles next to it, not bigger (JRE, 2026-07-27): the ring
   used to be 58px against their 36px, and since the tiles are grid items on
   one row it alone set the height of every card in the strip. The dasharray
   in the markup is in viewBox units (r=16, so 100.5), so it is unaffected by
   what this renders at - only stroke-width needs a look when resizing. */
.rb .ring{ display:flex; align-items:center; }
.rb .ring svg{ width:40px; height:40px; flex:none; transform:rotate(-90deg); }
.rb .ring .track{ fill:none; stroke:var(--line); stroke-width:6; }
.rb .ring .val{ fill:none; stroke:var(--azure); stroke-width:6; stroke-linecap:round; }

/* ---------- tables ---------- */
.rb .tbl{ width:100%; border-collapse:collapse; font-size:13.5px; }
.rb .tbl thead th{
  text-align:left; font-size:10.5px; font-weight:700; text-transform:uppercase;
  letter-spacing:.085em; color:var(--ink-ghost); padding:10px 22px;
  background:var(--sunken); border-top:1px solid var(--line);
  border-bottom:1px solid var(--line); white-space:nowrap;
}
.rb .tbl thead th.n{ text-align:right; }
.rb .tbl tbody td{ padding:13px 22px; border-bottom:1px solid var(--line-soft); vertical-align:middle; }
.rb .tbl tbody tr:last-child td{ border-bottom:none; }
.rb .tbl tbody tr.hot{ background:var(--amber-wash); }
.rb .tbl tbody tr.off{ color:var(--ink-ghost); }
.rb .tbl tfoot td{ padding:14px 22px; border-top:1px solid var(--line);
  font-weight:800; background:var(--sunken); }
.rb .tbl .who{ font-weight:700; display:block; background:none; border:none; padding:0; }
/* The customer name is a link into their own shop view. It stays ink, not
   azure: a whole column of blue names reads as a list of actions, and the
   name is still primarily a label. The underline appears on hover, which is
   where the "this opens something" signal is actually needed. */
.rb .tbl a.who{ color:inherit; text-decoration:none; }
.rb .tbl a.who:hover{ text-decoration:underline; text-underline-offset:2px; }
.rb .tbl .meta{ font-weight:500; font-size:12px; color:var(--ink-ghost); margin-top:2px; }
.rb .tbl .acts{ display:flex; gap:6px; justify-content:flex-end; }
/* Chase suppliers who are not on the board (Prices, 2026-09-16): one line
   under the table, label + a checkbox list behind a select-looking summary
   + button. */
.rb .chaserow{ display:flex; align-items:flex-start; gap:10px; flex-wrap:wrap;
  padding:10px 14px 12px; font-size:13px; color:var(--ink-soft); }
.rb .chaserow > .lbl, .rb .chaserow > .btn{ line-height:32px; }
.rb .chaserow > .btn{ height:32px; }
.rb .chasepick{ flex:1 1 240px; max-width:420px; }
/* the summary wears the select's clothes (select.cell above): a summary is
   not a select, so none of those rules reach it */
.rb .chasepick > summary{ list-style:none; cursor:pointer; box-sizing:border-box;
  height:32px; line-height:30px; padding:0 30px 0 10px; font-size:13px; font-weight:600;
  color:var(--ink-ghost); background-color:var(--paper);
  border:1px solid transparent; border-radius:var(--r-sm);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.6 6 6.4 11 1.6' fill='none' stroke='%237A8C99' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 10px center; background-size:11px 7px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.rb .chasepick > summary::-webkit-details-marker{ display:none; }
.rb .chasepick > summary:hover{ border-color:var(--line-strong); }
.rb .chasepick > summary.has{ color:var(--ink); }
.rb .chasepick[open] > summary{ background-color:#fff; border-color:var(--azure); }
/* in the flow, not floating: the card clips anything that hangs outside it */
.rb .chasepick .pane{ margin-top:6px; background:#fff;
  border:1px solid var(--line-strong); border-radius:var(--r-md); padding:8px; }
.rb .chasepick .pane > input{ width:100%; box-sizing:border-box; margin-bottom:6px; text-align:left; }
.rb .chasepick .opts{ max-height:260px; overflow:auto; }
.rb .chasepick .opt{ display:flex; align-items:center; gap:8px; padding:6px 6px;
  border-radius:var(--r-sm); cursor:pointer; color:var(--ink); }
.rb .chasepick .opt:hover{ background:var(--paper); }
.rb .chasepick .opt[hidden]{ display:none; }
.rb .chasepick .opt .nm{ flex:1; font-weight:600; }
.rb .chasepick .opt .meta{ font-size:12px; }
/* the chase MAILS (2026-09-16): one button per supplier under the picker,
   and beside a row's Chase when the browser blocked the window. A flex row,
   so [hidden] has to say display:none again */
.rb .chasemails{ flex:1 1 100%; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.rb .chasemails[hidden]{ display:none; }
.rb .chasemails .nomail{ font-size:12.5px; color:var(--ink-ghost); }
.rb .chasemail.done{ opacity:.6; }
.rb .chasemail.done::after{ content:" ✓"; }
.rb .was{ color:var(--ink-ghost); text-decoration:line-through; font-size:12.5px; }

/* ---------- shell ----------
   The menu is a horizontal bar across the top, not a left rail (JRE,
   2026-07-26). Three things stick, in this order, and every offset below
   is derived from --navbar/--topbar so they stay flush:
     .topnav  (z 60) at 0
     .top     (z 50) under the nav      -- page title + status
     .sendbar (z 40) under both         -- see the send bar section */
.rb .shell{ display:flex; flex-direction:column; min-height:100vh; }

.rb .topnav{
  position:sticky; top:0; z-index:60; flex:none;
  background:var(--harbor); color:#fff;
}
.rb .topnav-in{
  position:relative; min-height:var(--navbar);
  display:flex; align-items:center; gap:16px; padding:0 20px;
}
.rb .nav-id{ display:flex; align-items:center; gap:10px; flex:none; }
.rb .nav-id:hover{ text-decoration:none; }
.rb .nav-mark{
  width:32px; height:32px; border-radius:var(--r-md); flex:none; overflow:hidden;
  background:var(--teal); color:#fff; display:grid; place-items:center;
  font-size:14px; font-weight:800; letter-spacing:-.02em;
}
/* a real logo gets white behind it and is never cropped; the letter
   fallback keeps the solid tile */
.rb .nav-mark.has-logo{ background:#fff; }
.rb .nav-mark img{ width:100%; height:100%; object-fit:contain; }
.rb .nav-words b{ font-size:15px; font-weight:800; letter-spacing:-.02em;
  line-height:1.1; display:block; white-space:nowrap; }

/* a look-only key (2026-10-27): says so beside the brand */
.rb .nav-ro{ margin-left:10px; padding:3px 9px; border-radius:999px; font-size:12px;
  font-weight:700; white-space:nowrap; color:#fff; background:var(--amber, #C1731F); }

/* the menu takes the middle and centres itself between brand and account */
.rb .nav-menu{ display:flex; align-items:center; justify-content:center;
  gap:2px; flex:1; min-width:0; }
.rb .nav-menu a{
  position:relative;
  display:flex; align-items:center; gap:7px; padding:8px 12px; border-radius:var(--r-md);
  font-size:13.5px; font-weight:600; color:rgba(255,255,255,.66); white-space:nowrap;
  transition:background .15s, color .15s;
}
.rb .nav-menu a:hover{ background:rgba(255,255,255,.07); color:#fff; text-decoration:none; }
.rb .nav-menu a.on{ background:var(--azure); color:#fff; }
/* The Nodient block icons (2026-11-02), 18px beside the label as on
   decant.trading, in the bright blue whatever the label's state; the gap
   gave up the pixel the icon took, so the row is as wide as before. On the
   open door's azure pill a blue block would vanish: there the icon is
   white and its cut-outs (var(--ico-cut) in the symbols) azure. */
.rb .nav-menu a svg{ width:18px; height:18px; flex:none; color:var(--nav-icon); }
.rb .nav-menu a.on svg{ color:#fff; --ico-cut:var(--azure); }
/* Every door stands in the same place on every page (JRE, 2026-11-02: "the
   position of each menu item should stay the same"). The menu is centred, so
   a count that took width moved all six doors whenever it came, went or grew
   a digit: the Outbox drew no Prices and Orders counts, and a day picked on
   Orders carries other figures than the next delivery. The count sits on the
   door's top right corner instead, takes no width, and looks the same on the
   open door. Light blue, not grey: the counters have to read from across
   the room (JRE, 2026-09-02). */
.rb .nav-menu .count{ position:absolute; top:-7px; right:-7px; z-index:1;
  min-width:17px; height:17px; padding:0 5px;
  display:flex; align-items:center; justify-content:center;
  font-size:10.5px; font-weight:800; line-height:1;
  border-radius:var(--r-pill); background:var(--azure-light); color:var(--azure-deep);
  box-shadow:0 0 0 2px var(--harbor);
  font-variant-numeric:tabular-nums; }
.rb .nav-menu .count.alert{ background:var(--amber); color:#fff; }
/* stands in for the sidebar's Workday / Records headings */
.rb .nav-sep{ width:1px; height:20px; flex:none; margin:0 7px;
  background:rgba(255,255,255,.16); }

/* "View as": open a supplier's or a customer's own page from any operator
   page. Pushed to the right end with margin-left:auto so it reads as a tool
   rather than a ninth destination in the menu. Two selects, not a menu: the
   lists are long and a native select is the one control that searches by
   typing on every device without any JS of ours. */
/* flex:none, NOT margin-left:auto - .nav-menu is flex:1 and already eats the
   free space, so an auto margin has nothing left to push with. The menu is
   also allowed to overflow its box, which is what made the last item spill
   into this control: hence the separator and the tight widths here. */
.rb .nav-viewas{ display:flex; align-items:center; gap:8px; flex:none; }
/* the notifications bell (2026-09-14): a tour-button-sized control, drawn
   only while the browser has not been asked yet; live.js hides it after. */
/* centred in a box as tall as the "?" beside it (2026-11-02). Said with
   .va-link in the selector: .va-link comes later in this file with the same
   weight, so its 5px 12px padding beat the 0 7px here, and the inline svg
   on the text baseline left the bell high in its box. */
.rb .va-link.va-bell{ display:inline-flex; align-items:center; padding:6px 7px; }
.rb .va-bell svg{ width:16px; height:16px; display:block; }
/* A hidden bell KEEPS ITS PLACE (2026-11-03). The server cannot know what
   the browser answered, so the bell is drawn `hidden` and live.js shows it
   after the page has painted. With display:none it took its 32px (and a gap)
   only then: on every click in the bar the centred doors jumped 24px to the
   left and the "?" 48px, the "the whole bar shoots left and right" of
   Huseyin's report. Same answer as the "?" slot below: the box is always
   there, only what is in it shows or not. visibility:hidden also keeps it
   out of the tab order and the accessibility tree.
   tests/test_topnav_steady.py holds every hidden control in the bar to this. */
.rb .va-bell[hidden]{ display:inline-flex; visibility:hidden; }
/* The back office's language at the end of the bar (2026-10-29): the shared
   flag control (_langsel.html), drawn as one more quiet pill on the dark bar. */
.rb .nav-lang{ display:flex; align-items:center; margin:0; flex:none; }
.rb .nav-lang .langsel-face{ min-height:0; height:29px; padding:0 8px 0 9px; gap:6px;
  background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.14);
  border-radius:var(--r-pill); color:rgba(255,255,255,.92); }
.rb .nav-lang .langsel-face:hover{ background:rgba(255,255,255,.11); border-color:rgba(255,255,255,.22); }
.rb .nav-lang .langsel-face:focus-visible{ outline:2px solid rgba(255,255,255,.5);
  outline-offset:1px; box-shadow:none; border-color:rgba(255,255,255,.22); }
.rb .nav-lang .langsel .flag{ width:20px; height:13px; }
.rb .nav-lang .langsel-arrow{ width:12px; height:12px; }
.rb .va{ display:flex; align-items:center; gap:7px; }
.rb .va-lbl{ font-size:12px; font-weight:700; color:rgba(255,255,255,.55);
  white-space:nowrap; }
.rb .va select{
  font:inherit; font-size:12.5px; font-weight:600; max-width:124px;
  color:rgba(255,255,255,.92); background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.14); border-radius:var(--r-pill);
  padding:5px 9px; cursor:pointer; }
.rb .va select:hover{ background:rgba(255,255,255,.11); }
.rb .va select:focus-visible{ outline:2px solid rgba(255,255,255,.5);
  outline-offset:1px; }
/* the option list is a native popup: it renders on the OS surface, so it
   needs its own colours or it inherits white-on-white */
.rb .va select option{ color:var(--ink); background:#fff; }
/* the generic previews (2026-08-05): two links wearing the same pill the
   selects did, so the bar reads unchanged */
.rb .va-link{
  font-size:12.5px; font-weight:600; text-decoration:none;
  color:rgba(255,255,255,.92); background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.14); border-radius:var(--r-pill);
  padding:5px 12px; white-space:nowrap; }
.rb .va-link:hover{ background:rgba(255,255,255,.11); }
.rb .va-link:focus-visible{ outline:2px solid rgba(255,255,255,.5);
  outline-offset:1px; }
/* Starting this page's walkthrough (a <button> since 2026-08-05, so it
   resets the UA font/cursor itself). Quiet for the same reason the "?" on the
   supplier's own page is (.rb .btn.tourback) - a door for when you want it,
   not an action. Only the reason is shared: this one is 27px on a dark bar of
   links, that one is a 44px control standing beside two other 44px controls.

   SQUARE, not round (JRE, 2026-08-05). A circle made it a different KIND of
   control from the two previews it stands next to, when it is the same kind:
   a small door in the same bar. So it sets no radius of its own at all and
   keeps the one .va-link already gives it - whatever the tenant's theme makes
   that. Naming a value here (6px looked right) put it 2px off its neighbours
   under this theme, which is exactly the drift the shared class prevents. */
.rb .va-tour{ width:27px; padding:5px 0; text-align:center; font-weight:800;
  font:inherit; font-size:12.5px; cursor:pointer; }
/* a page with no walkthrough keeps the "?"'s place empty, so the centred
   menu does not move a step to the right there (2026-11-02) */
.rb .va-slot{ flex:none; width:27px; visibility:hidden; }

/* one shared account, replacing the old two-person toggle */
.rb .nav-acct{ display:flex; align-items:center; gap:9px; flex:none;
  padding:5px 13px 5px 5px; border-radius:var(--r-pill);
  background:rgba(255,255,255,.06);
  font-size:12.5px; font-weight:700; color:rgba(255,255,255,.9); }
.rb .nav-ini{ width:24px; height:24px; border-radius:50%; flex:none;
  background:var(--teal); color:#fff; display:grid; place-items:center;
  font-size:11px; font-weight:800; }

.rb .nav-burger{
  display:none; width:34px; height:34px; flex:none; border-radius:var(--r-md); border:none;
  cursor:pointer; background:rgba(255,255,255,.08); color:rgba(255,255,255,.8);
  place-items:center;
}
.rb .nav-burger:hover{ background:rgba(255,255,255,.16); color:#fff; }
.rb .nav-burger svg{ width:18px; height:18px; }

/* ---------- the bar on a narrow screen ----------
   Two steps down: first the words go (icons + account initial keep the
   whole menu on one line), then the menu folds behind the burger. */
@media (max-width:1180px){
  .rb .topnav-in{ gap:12px; padding:0 14px; }
  .rb .nav-words,
  .rb .nav-acct .lbl{ display:none; }
  .rb .nav-acct{ padding:5px; }
  .rb .nav-menu a{ padding:8px 10px; gap:7px; }
  .rb .nav-sep{ margin:0 4px; }
  .rb .va select{ max-width:104px; }
}
/* The bar carries eight destinations plus this control, so the words go
   early: the two placeholders already read "supplier…" and "customer…". */
@media (max-width:1460px){ .rb .va-lbl{ display:none; } }
/* Six doors and the language flag (2026-10-29) no longer fit beside the
   brand's name below 1300px, and the centred menu spilled over the logo on
   the left and the previews on the right. The name goes first, then (from
   1180 down to the burger) the icons, which keeps every word she reads. */
@media (max-width:1300px){ .rb .nav-words{ display:none; } }
@media (min-width:901px) and (max-width:1240px){
  .rb .nav-menu a svg{ display:none; }
  .rb .nav-menu a{ padding:8px 9px; }
}
@media (min-width:901px) and (max-width:1010px){
  .rb .topnav-in{ gap:6px; }
  .rb .nav-menu a{ padding:8px 5px; }
  .rb .nav-sep{ margin:0 2px; }
}
@media (max-width:900px){
  .rb .nav-words{ display:block; }
  /* The burger takes the right end here, and two selects beside it on a
     phone leaves the brand nowhere. Looking at a supplier's page as they see
     it is a desk task; the Suppliers and Customers pages still carry the same
     links, so nothing becomes unreachable. */
  .rb .nav-viewas{ display:none; }
  /* the menu leaves the flow, so brand and the pair on the right have to be
     pushed apart by hand */
  .rb .nav-acct{ order:2; margin-left:auto; }
  .rb .nav-burger{ display:grid; order:3; }
  .rb .nav-menu{
    display:none; position:absolute; top:100%; left:0; right:0; z-index:60;
    flex-direction:column; align-items:stretch; justify-content:flex-start;
    gap:2px; padding:8px; background:var(--harbor);
    border-top:1px solid rgba(255,255,255,.09);
    box-shadow:0 18px 30px -18px rgba(11,31,51,.9);
  }
  .rb .nav-menu.open{ display:flex; }
  .rb .nav-menu a{ padding:11px 12px; font-size:14px; }
  /* the phone's menu is a column: the count stands at the end of its row */
  .rb .nav-menu .count{ position:static; margin-left:auto; height:auto;
    padding:1px 7px; font-size:11px; box-shadow:none; }
  .rb .nav-sep{ width:auto; height:1px; margin:5px 12px; }
}

.rb .main{ flex:1; min-width:0; display:flex; flex-direction:column; }
/* scoped to .main on purpose: a bare `.top` inside the customer masthead
   would paint a white bar over it and hide the white heading */
.rb .main > .top{
  min-height:var(--topbar); flex:none; background:#fff; border-bottom:1px solid var(--line);
  display:flex; align-items:center; gap:16px; padding:10px 24px;
  position:sticky; top:var(--navbar); z-index:50;
}
.rb .top h1{ font-size:20px; font-weight:700; letter-spacing:-.02em; }
.rb .top .page-sub{ font-size:12.5px; color:var(--ink-ghost); margin-top:1px; }
.rb .top .right{ margin-left:auto; display:flex; align-items:center; gap:10px; }
.rb .content{ padding:22px 24px 40px; flex:1; }

/* ---------- SIGNATURE: the voyage rail ----------
   Physical journey only (departure -> delivery). The four work
   categories sit below it as equal status chips with no connectors:
   they are status, never steps (docs/DESIGN.md, freedom over
   sequence). The target time is a quiet tick and a quiet counter,
   never a deadline and never a lock. */
.rb .rail{ background:#fff; border-bottom:1px solid var(--line); padding:16px 24px 14px; }
.rb .rail-aim{ flex:none; text-align:right; white-space:nowrap; }
.rb .rail-aim b{ font-size:17px; font-weight:800; color:var(--azure-deep);
  font-variant-numeric:tabular-nums; display:block; line-height:1.1; }
.rb .rail-aim.over b{ color:var(--ink-ghost); }

.rb .rail-stations{ display:flex; flex-wrap:wrap; gap:8px; margin-top:14px;
  padding-top:13px; border-top:1px solid var(--line-soft); }
.rb .station{
  display:inline-flex; align-items:center; gap:8px; padding:7px 14px;
  border-radius:var(--r-pill); border:1px solid var(--line); background:#fff;
  font-size:12.5px; font-weight:700; color:var(--ink-soft);
  transition:border-color .15s, background .15s;
}
.rb .station:hover{ background:var(--sunken); text-decoration:none; color:var(--ink-soft); }
.rb .station .led{ width:7px; height:7px; border-radius:50%; background:var(--ink-ghost); flex:none; }
.rb .station .n{ font-variant-numeric:tabular-nums; color:var(--ink); font-weight:800; }
.rb .station small{ font-weight:600; color:var(--ink-ghost); font-size:12px; }
.rb .station.on small{ color:rgba(255,255,255,.65); }
.rb .station.ok .led{ background:var(--teal); }
.rb .station.busy .led{ background:var(--amber); }
.rb .station.on{ border-color:var(--harbor); background:var(--harbor); color:#fff; }
.rb .station.on .n{ color:#fff; }
.rb .station.on .led{ background:#fff; }
.rb .rail-hint{ margin-left:auto; align-self:center; font-size:12px; color:var(--ink-ghost); }

/* day navigation: arrows step through delivery days, the select is the only
   route back to a past day */
.rb .rail-days{ display:flex; gap:6px; align-items:center; flex-wrap:wrap; }
.rb .daystep{
  width:32px; height:32px; flex:none; border-radius:var(--r-md);
  border:1px solid var(--line); background:#fff; color:var(--ink-soft);
  display:grid; place-items:center; font-size:19px; font-weight:400; line-height:1;
  padding-bottom:2px;
}
.rb .daystep:hover{ background:var(--sunken); text-decoration:none;
  border-color:var(--line-strong); color:var(--ink); }
.rb .daystep.off{ opacity:.3; cursor:default; }
.rb .daypick{
  font:inherit; font-size:13.5px; font-weight:600; color:var(--ink);
  height:32px; padding:0 30px 0 12px; border:1px solid var(--line); border-radius:var(--r-md);
  background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235C7185' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 10px center;
  -webkit-appearance:none; appearance:none; cursor:pointer;
}
.rb .daypick:hover{ border-color:var(--line-strong); }
.rb .daypick:focus{ outline:none; border-color:var(--azure); box-shadow:0 0 0 3px var(--azure-wash); }
.rb .daypast{ background:var(--amber-wash); color:#8F5415; }
/* the way into /days, the full archive behind the picker's short window;
   shaped like the daystep arrows so it reads as more day-picking, not a tab */
.rb .dayhist{
  height:32px; flex:none; display:inline-flex; align-items:center; padding:0 12px;
  border:1px solid var(--line); border-radius:var(--r-md); background:#fff;
  font-size:12.5px; font-weight:600; color:var(--ink-soft);
}
.rb .dayhist:hover{ background:var(--sunken); text-decoration:none;
  border-color:var(--line-strong); color:var(--ink); }
/* the picked delivery day, big, at the right end of the strip (JRE,
   2026-09-17). One line on the controls' own 32px axis, so the strip keeps
   its height: the eyebrow sits on the date's baseline, not above it. */
.rb .raildate{
  margin-left:auto; display:flex; align-items:baseline; gap:10px;
  white-space:nowrap; min-width:0;
}
.rb .raildate b{
  font-size:26px; font-weight:800; letter-spacing:-.02em; line-height:32px;
  color:var(--ink);
}
.rb .raildate .yr{ font-weight:600; color:var(--ink-ghost); }

/* THE DAY IN THE PAGE HEADER (Orders, JRE 2026-11-02). `_shell_rail.html`
   with `rail_inline`: title, day controls, the day's figures and the big
   date in the one sticky header, each part parted by a hairline. Where the
   width runs out the parts wrap onto a second line of the same header. */
.rb .main > .top.tophead{ gap:0; row-gap:10px; flex-wrap:wrap; padding:12px 24px; }
.rb .tophead > .ttl{ display:flex; align-items:baseline; gap:10px; flex:none; padding-right:22px; }
.rb .tophead > .ttl .page-sub{ margin-top:0; }
.rb .tophead .hdays{ flex:none; height:40px; padding-left:22px; border-left:1px solid var(--line); }
.rb .tophead .hstats{ margin-left:auto; display:flex; align-items:center; gap:14px; flex:none; padding:0 22px; }
.rb .tophead .hfig{ font-size:18px; font-weight:700; letter-spacing:0; color:var(--ink);
  font-variant-numeric:tabular-nums; white-space:nowrap; cursor:default; }
.rb .tophead .hreq{ font-size:12.5px; font-weight:700; white-space:nowrap; padding:5px 10px;
  border-radius:999px; background:var(--sunken); color:var(--ink-soft); }
.rb .tophead .hreq.warn{ background:var(--amber-wash); color:#8F5415; }
.rb .tophead .hreq:hover{ text-decoration:none; filter:brightness(.97); }
.rb .tophead .raildate{ margin-left:0; padding-left:22px; border-left:1px solid var(--line); flex:none; }
.rb .tophead .raildate b{ font-size:24px; }
@media (max-width:1180px){
  /* narrower: the amount goes beside the title, the date takes the end
     (or the next line) */
  .rb .tophead .hstats{ order:1; margin-left:0; padding:0 22px 0 0; }
  .rb .tophead .hdays{ order:2; }
  .rb .tophead .raildate{ order:3; margin-left:auto; }
}
@media (max-width:620px){
  /* three lines on a phone: the header scrolls away with the page */
  .rb .main > .top.tophead{ position:static; padding:12px 16px; }
  .rb .tophead > .ttl{ padding-right:12px; }
  .rb .tophead .hstats{ margin-left:auto; padding:0; }
  .rb .tophead .hdays{ flex:1 1 100%; min-width:0; padding-left:0; border-left:0; height:auto; }
  .rb .tophead .raildate{ flex-basis:100%; margin-left:0; padding-left:0; border-left:0; }
}

/* ---------- needs-you list ---------- */
/* Centred on the row's axis (JRE, 2026-09-14): the row is one line since
   2026-08-04, and with the icon and the tag pinned to the top while the
   button sat on the line's centre, the three read as three heights. */
.rb .need{ display:flex; align-items:center; gap:13px; padding:15px 22px; border-bottom:1px solid var(--line-soft); }
.rb .need:last-child{ border-bottom:none; }
/* Needs-you rows sit in a wrapper carrying their work category, so the stack
   can be narrowed to one kind of decision. The separator has to follow the
   LAST VISIBLE row, not the last row in the markup - with a filter on, both
   :last-child rules point at something that may be hidden, so the script
   marks the real last one. */
.rb .need-row[hidden]{ display:none; }
.rb .need-row:last-child > .need,
.rb .need-row.is-last > .need{ border-bottom:none; }
.rb .need-row:not(.is-last) > .need{ border-bottom:1px solid var(--line-soft); }

.rb .stackfilter{ display:flex; flex-wrap:wrap; gap:6px; padding-top:0; }
.rb .stackfilter .n{ opacity:.62; font-variant-numeric:tabular-nums; }
.rb .stackfilter .btn[disabled]{ opacity:.4; cursor:default; }
.rb .need-ico{ width:36px; height:36px; border-radius:var(--r-md); flex:none;
  display:grid; place-items:center; background:var(--sunken); color:var(--ink-soft); }
.rb .need-ico svg{ width:17px; height:17px; }
.rb .need-ico.amber{ background:var(--amber-wash); color:#8F5415; }
.rb .need-ico.azure{ background:var(--azure-wash); color:var(--azure); }
.rb .need-ico.teal{ background:var(--teal-wash); color:#0B7268; }
.rb .need-ico.red{ background:var(--red-wash); color:var(--red); }
.rb .need-body{ flex:1; min-width:0; }
.rb .need-title{ font-size:13.5px; font-weight:700; letter-spacing:-.01em; }
.rb .need-desc{ font-size:12.5px; color:var(--ink-soft); margin-top:2px; }
.rb .need-actions{ display:flex; flex-wrap:wrap; gap:6px; margin-top:10px; align-items:center; }
/* the category tag sits on the row's centre line and never stretches with it */
.rb .need > .pill{ align-self:center; flex:none; }
.rb .need-actions form{ display:inline-flex; margin:0; }

/* the folded "waiting on a supplier" pile at the foot of the bids card:
   not her decision yet, so one quiet line - open only when an answer
   arrives by phone */
.rb .waitfold{ border-top:1px solid var(--line-soft); background:var(--paper); }
.rb .waitfold summary{ display:flex; align-items:baseline; gap:10px; cursor:pointer;
  padding:11px 22px; font-size:12.5px; font-weight:700; color:var(--ink-soft);
  list-style:none; }
.rb .waitfold summary::-webkit-details-marker{ display:none; }
.rb .waitfold summary::before{ content:'▸'; color:var(--ink-ghost); font-size:11px; }
.rb .waitfold[open] summary::before{ content:'▾'; }
.rb .wrow{ display:flex; flex-wrap:wrap; align-items:center; gap:8px 14px;
  padding:9px 22px 9px 40px; border-top:1px solid var(--line-soft);
  font-size:12.5px; color:var(--ink-soft); }
.rb .wgroup{ display:flex; flex-wrap:wrap; align-items:center; gap:6px 10px;
  padding:10px 22px 4px 40px; border-top:1px solid var(--line-soft); font-size:12.5px; }
.rb .wgroup .wsup{ font-weight:800; color:var(--ink); }
.rb .wgroup form{ margin:0 0 0 auto; }
.rb .wgroup + .wrow{ border-top:none; padding-top:4px; }
.rb .wrow-what{ flex:1; min-width:220px; }
.rb .wrow-what b{ color:var(--ink); font-weight:700; }
.rb .wrow-what .wsup{ color:var(--ink-ghost); }
.rb .wrow-yes{ display:inline-flex; align-items:center; gap:6px; margin:0; }
.rb .wrow form{ margin:0; }
.rb .wrow .qty{ width:64px; }

/* ---------- a counterparty row ----------
   ONE row shape for a supplier, drawn by two pages (JRE, 2026-08-04).
   /suppliers draws them to be OPENED, Settings draws them to be EDITED, and
   they used to be two different rows saying the same thing: a 60px line on
   the tab against a seven-column Settings table whose row came to ~150px of
   logo block, cut-off, language and a link cell three lines deep. At eighty
   suppliers that is a page you scroll rather than a page you read.

   Same classes now, so the two cannot drift apart in look; each page adds
   only the controls on its own right-hand side. */
.rb .sup-row{ display:flex; align-items:center; gap:14px; padding:13px 22px;
  border-top:1px solid var(--line); }
.rb .sup-row:first-child{ border-top:0; }
.rb .sup-row:hover{ background:var(--sunken); }
/* The mark is the supplier's own logo when we have one - the same one their
   page and their listino carry, so the list looks like the thing it opens
   rather than like a spreadsheet of names. */
.rb .sup-mark{ width:34px; height:34px; border-radius:9px; flex:none;
  background:var(--sunken); display:grid; place-items:center; overflow:hidden;
  font-weight:800; font-size:13px; color:var(--ink-soft); }
.rb .sup-mark img{ width:100%; height:100%; object-fit:contain; }
/* On Settings the mark IS the logo control - the tile you look at is the tile
   you replace. That is what let the upload block (a thumbnail, "Replace
   logo", "Remove") leave the row without the feature leaving with it. */
.rb label.sup-mark{ cursor:pointer; }
.rb label.sup-mark:hover{ box-shadow:0 0 0 2px var(--azure); }
/* min-width:0, or a long name refuses to shrink and pushes the controls out */
.rb .sup-id{ min-width:0; }
.rb .sup-name{ font-weight:700; font-size:14.5px; letter-spacing:-.01em; }
.rb .sup-meta{ font-size:12px; color:var(--ink-soft); margin-top:1px; }
.rb .sup-right{ margin-left:auto; display:flex; align-items:center; gap:10px; }
.rb .sup-state{ font-size:12px; font-weight:700; padding:3px 11px;
  border-radius:var(--r-pill); background:var(--sunken); color:var(--ink-soft); }
.rb .sup-state.live{ background:var(--teal-wash); color:var(--teal); }
.rb .sup-state.wait{ background:var(--amber-wash); color:var(--amber); }
.rb .sup-state.none{ background:transparent; color:var(--ink-ghost); }

/* The rare row actions behind one ⋯: withdraw a leaked link, issue a new one,
   drop a logo. Each is a once-a-quarter errand that was costing every row two
   permanent lines. Same <details> idiom as the account menu - it opens,
   closes on Escape and is keyboard-reachable without a line of script. */
.rb .rowmenu{ position:relative; display:inline-flex; }
.rb .rowmenu > summary{ display:inline-grid; place-items:center;
  width:28px; height:28px; border-radius:var(--r-sm); cursor:pointer;
  list-style:none; color:var(--ink-ghost); font-weight:800; }
.rb .rowmenu > summary::-webkit-details-marker{ display:none; }
.rb .rowmenu > summary:hover{ background:var(--sunken); color:var(--ink); }
.rb .rowmenu > summary:focus-visible{ outline:2px solid var(--azure); outline-offset:1px; }
.rb .rowpop{ position:absolute; top:calc(100% + 5px); right:0; z-index:60;
  width:196px; padding:6px; background:#fff; border:1px solid var(--line);
  border-radius:var(--r-md); box-shadow:0 14px 34px rgba(20,42,59,.16); }
.rb .rowpop form{ margin:0; }
.rb .rowpop button, .rb .rowpop label{ display:block; width:100%;
  padding:7px 9px; background:none; border:0; border-radius:var(--r-sm);
  font:inherit; font-size:12.5px; font-weight:700; color:var(--ink-soft);
  text-align:left; cursor:pointer; }
.rb .rowpop button:hover, .rb .rowpop label:hover{ background:var(--sunken);
  color:var(--ink); }
.rb .rowpop button.danger{ color:var(--red); }

/* ---------- sticky send bar (overrides the fixed bottom one) ---------- */
/* the "nothing to send" state: a line, not a bar */
.rb .sentline-bar{ display:flex; align-items:center; gap:8px; padding:9px 24px;
  background:var(--sheet); border-bottom:1px solid var(--line);
  font-size:12.5px; font-weight:600; color:var(--ink-soft); }
.rb .sentline-bar .ck{ width:18px; height:18px; border-radius:50%; flex:none;
  background:var(--teal-wash); color:var(--teal); display:grid; place-items:center;
  font-size:11px; font-weight:800; }

.rb .sendbar{
  position:sticky; top:calc(var(--navbar) + var(--topbar));
  bottom:auto; left:auto; right:auto; z-index:40;
  background:var(--harbor); color:#fff; backdrop-filter:none; border-top:none;
  padding:13px 24px; display:flex; align-items:center; gap:16px;
}
.rb .sendbar .txt{ flex:1; font-size:13.5px; }
.rb .sendbar .sub{ font-size:12px; color:rgba(255,255,255,.55); margin-top:2px; }

/* ---------- layout helpers ---------- */
.rb .g2{ display:grid; grid-template-columns:1fr 1fr; gap:16px; align-items:start; }
.rb .g-side{ display:grid; grid-template-columns:1.55fr 1fr; gap:16px; align-items:start; }
/* A grid item is min-width:auto by default, which means "never narrower than
   my content" - so one wide table inside a column pushed the COLUMN past its
   track instead of scrolling inside its own card, and took the page sideways
   with it. The single most useful line in this file on a phone. */
.rb .g2 > *, .rb .g-side > *, .rb .stack > *{ min-width:0; }
.rb .stack{ display:flex; flex-direction:column; gap:16px; }
.rb .row{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }

/* ---------- document shell (supplier) ----------
   Capped again (JRE, 2026-07-28: "i take back the fill up all the space
   idea"). Full width stretched four columns across a whole desktop, which
   put the price at the far end of a very long row - the opposite of what the
   ledger fix was for. 940px: wider than the original 820 so the packing
   table's four columns are not cramped, narrow enough that a row still
   scans in one eye movement. */
.rb .doc-shell{ max-width:940px; margin:0 auto; padding:26px 24px 60px; }

/* Tighter rows on the supplier pages (same note). These are documents to
   read down, not forms to fill: the 13px/22px of the operator tables paid
   for touch targets that a read-only packing list does not need. Scoped to
   .doc-shell so the operator side keeps its own rhythm. */
.rb .doc-shell .tbl tbody td{ padding:9px 16px; }
.rb .doc-shell .tbl thead th{ padding:8px 16px; }
.rb .doc-shell .tbl tfoot td{ padding:11px 16px; }
/* the row still has to be pressable where it holds an input */
.rb .doc-shell .tbl td:has(input){ padding-top:6px; padding-bottom:6px; }

/* The supplier pages' shared header: their own logo left, language + actions
   right. Replaced the navy masthead (JRE, 2026-07-28); the packing page and
   the price sheet both use it, so it lives here rather than per template. */
.rb .doc-head{ display:flex; align-items:center; gap:16px; flex-wrap:wrap;
  margin-bottom:18px; }
.rb .doc-head .suplogo{ height:56px; max-width:230px; object-fit:contain;
  object-position:left center; }
.rb .doc-head .supname{ font-size:22px; font-weight:800; letter-spacing:-.02em; }
.rb .doc-head .doc-acts{ display:flex; align-items:center; gap:8px;
  margin-left:auto; flex-wrap:wrap; }
/* The language picker. A drawn flag, not an emoji one (JRE, 2026-07-31):
   see _flags.html for why. The sprite itself never renders. */
.flagsprite{ position:absolute; width:0; height:0; overflow:hidden; }
.rb .langsel .flag{ width:24px; height:16px; border-radius:2px; flex:0 0 auto;
  display:block; box-shadow:0 0 0 1px rgba(20,42,59,.14) inset; }
/* a language with no drawn flag keeps its letters rather than a hole */
.rb .langsel .flag.code{ display:grid; place-items:center; font-size:10px;
  font-weight:800; letter-spacing:.02em; color:var(--ink-soft);
  background:var(--sunken); box-shadow:none; }

.rb .langsel{ position:relative; display:inline-flex; }
.rb .langsel-face{ display:inline-flex; align-items:center; gap:7px;
  min-height:44px; padding:0 9px 0 11px; background:#fff; color:var(--ink-soft);
  border:1px solid var(--line); border-radius:var(--r-ctrl); cursor:pointer; }
.rb .langsel-face:hover{ border-color:var(--line-strong); }
.rb .langsel-face:focus-visible{ outline:none; border-color:var(--azure);
  box-shadow:0 0 0 3px var(--azure-wash); }
.rb .langsel-arrow{ width:14px; height:14px; transition:transform .15s var(--ease-out); }
.rb .langsel-face[aria-expanded="true"] .langsel-arrow{ transform:rotate(180deg); }

/* FIXED, not absolute, and placed by _langsel.html against the button's own
   rectangle. An absolutely positioned panel is clipped by any ancestor that
   hides its overflow, and `.card` does for its rounded corners - the picker
   on Settings sits in one, so the list drew as a sliver sheared off at the
   card's edge with the flags outside the page entirely (JRE, 2026-08-06).
   Same lesson, same answer as `.combo-pop`; the difference is that this list
   stays in the DOM where it was declared, so a page can still reach its rows
   with its own selectors (the shop's 44px touch rows). A fixed element
   escapes ancestor overflow on its own - it only needs moving to <body> if an
   ancestor makes a containing block (transform/filter/contain), and none of
   these pages do. */
.rb .langsel-list{ position:fixed; top:0; left:0; z-index:60;
  margin:0; padding:5px; list-style:none; min-width:172px; background:#fff;
  border:1px solid var(--line); border-radius:var(--r-md);
  box-shadow:0 14px 34px rgba(20,42,59,.16); }
.rb .langsel-list li{ display:flex; align-items:center; gap:9px;
  padding:8px 9px; border-radius:var(--r-sm); font-size:14px; font-weight:700;
  color:var(--ink); cursor:pointer; }
.rb .langsel-list li:hover{ background:var(--sunken); }
.rb .langsel-list li:focus-visible{ outline:none; background:var(--azure-wash);
  box-shadow:0 0 0 2px var(--azure) inset; }
.rb .langsel-list .lname{ flex:1 1 auto; white-space:nowrap; }
.rb .langsel-tick{ width:15px; height:15px; color:var(--teal); opacity:0; }
.rb .langsel-list li[aria-selected="true"] .langsel-tick{ opacity:1; }

/* A partner's own sign-in, beside the flag. Same 44px face as the language
   picker so the header reads as one row of small controls, and the same
   quietness as the walkthrough door: this is looked for once, on the morning
   it is needed, and never competes with Send.

   A <details> rather than scripted state - it opens, closes on Escape and is
   keyboard-reachable without a line of JavaScript, on both portals. */
.rb .acctmenu{ position:relative; display:inline-flex; }
.rb .acctmenu summary{ display:inline-flex; align-items:center; justify-content:center;
  width:44px; min-width:44px; height:44px; background:#fff; color:var(--ink-soft);
  border:1px solid var(--line); border-radius:var(--r-ctrl); cursor:pointer;
  list-style:none; }
.rb .acctmenu summary::-webkit-details-marker{ display:none; }
.rb .acctmenu summary:hover{ border-color:var(--line-strong); color:var(--azure); }
.rb .acctmenu summary:focus-visible{ outline:none; border-color:var(--azure);
  box-shadow:0 0 0 3px var(--azure-wash); }
.rb .acctmenu summary svg{ width:19px; height:19px; }
.rb .acctpop{ position:absolute; top:calc(100% + 6px); right:0; z-index:60;
  width:250px; padding:13px; background:#fff; border:1px solid var(--line);
  border-radius:var(--r-md); box-shadow:0 14px 34px rgba(20,42,59,.16); }
.rb .acctpop label{ display:block; margin-top:8px; font-size:11px; font-weight:700;
  letter-spacing:.08em; text-transform:uppercase; color:var(--ink-ghost); }
.rb .acctpop input{ font:inherit; width:100%; margin-top:4px; padding:9px 11px;
  border:1px solid var(--line-strong); border-radius:var(--r-sm); }
.rb .acctpop .btn{ width:100%; margin-top:11px; justify-content:center; }
/* "Change password", folded: a quiet row you press, not two fields you are
   handed. The default marker is dropped - the row is the control. */
.rb .acctpop .pwfold{ margin-top:11px; padding-top:11px; border-top:1px solid var(--line); }
.rb .acctpop .pwfold > summary{ list-style:none; cursor:pointer; font-size:12.5px;
  font-weight:700; color:var(--ink-soft); text-align:center; }
.rb .acctpop .pwfold > summary::-webkit-details-marker{ display:none; }
.rb .acctpop .pwfold > summary:hover{ color:var(--azure); }
.rb .acctpop .pwfold[open] > summary{ color:var(--ink); margin-bottom:2px; }
.rb .acctpop hr{ margin:13px 0; border:0; border-top:1px solid var(--line); }
.rb .acctpop .linkish{ display:block; width:100%; margin-top:9px; padding:0;
  background:none; border:0; font:inherit; font-size:12px; font-weight:700;
  color:var(--ink-ghost); text-align:center; cursor:pointer; }
.rb .acctpop .linkish:hover{ color:var(--red); text-decoration:underline; }

/* "Show me again" for the walkthrough: a ? beside the flag. Quiet on purpose -
   it is a door for the one supplier in ten who wants it back, not an action
   competing with Send - but the same 44px box as the day picker and the flag
   it stands next to (JRE, 2026-08-05: "make the ? as big as the other buttons
   like language selector").

   `.rb .btn.tourback`, not `.rb .tourback`: at two classes this rule lost to
   `.rb .btn.sm` and `.rb .btn.ghost` on every property they share, so the
   button drew 44x31 at 12px in ink, and never the 17px soft-ink circle this
   block has described since it was written. Three classes and a later line
   than both, so what is written here is what renders. Height rather than
   min-height: the box is square, and .btn's own centring puts the glyph in
   the middle of it. */
.rb .btn.tourback{ width:44px; min-width:44px; height:44px; padding:0;
  border-radius:var(--r-ctrl); font-size:17px; font-weight:800;
  color:var(--ink-soft); }
.rb .btn.tourback:hover{ background:#fff; color:var(--azure);
  border-color:var(--azure); }

/* for a control whose label is a screen-reader's only clue - the day picker's
   face is aria-hidden, so the <label> has to carry the name */
.visually-hidden{ position:absolute; width:1px; height:1px; margin:-1px;
  padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }

/* TODAY OR HISTORY (JRE, 2026-08-07: "just option 'today' or 'history'").
   Replaces the day <select> on the supplier header and the day strip on the
   operator's /today: two doors in one segmented control, the page you are on
   drawn solid. Shared by both shells, which is why it lives here and not in
   a page's own <style>. */
.rb .daynav{ display:inline-flex; align-items:center; gap:3px; padding:4px;
  border:1px solid var(--line); border-radius:var(--r-ctrl); background:#fff; }
.rb .daynav .daytab{ display:inline-flex; align-items:center; min-height:36px;
  padding:0 14px; border-radius:calc(var(--r-ctrl) - 4px);
  font-size:13.5px; font-weight:700; color:var(--ink-soft);
  text-decoration:none; white-space:nowrap; transition:background .12s, color .12s; }
.rb .daynav .daytab:hover{ background:var(--sunken); color:var(--ink); }
.rb .daynav .daytab.on{ background:var(--ink); color:#fff; }
.rb .daynav .daytab:focus-visible{ outline:2px solid var(--azure); outline-offset:2px; }

/* ONE chip on the supplier header (JRE, 2026-08-12): the pair became a
   toggle - the chip names where you are, tapping it opens the other page.
   The wrapper's own box goes, or a single tab sits in a double frame. */
.rb .daynav.one{ border:0; padding:0; background:none; }
.rb .daynav.one .daytab.on:hover{ background:var(--azure); }

/* The day a sheet is FOR, named beside the doors. It was the face of the day
   <select> until 2026-08-07; the nowrap survives it - until Manrope arrives
   the fallback font is wider, and "maandag 3 augustus" broke over two lines
   for as long as the webfont took to load. */
.rb .daysel-face{ font-size:14.5px; font-weight:700; color:var(--ink);
  white-space:nowrap; flex:none; }

/* Paper needs to say WHO it is for and WHEN, because the screen's chrome -
   the picker, the flag - is not printed (JRE, 2026-07-30). Screen-side this
   block is not there at all; the print rules below turn it on. */
.rb .doc-head .printmeta{ display:none; }
@media (max-width:560px){ .rb .doc-head .suplogo{ height:44px; } }
.rb .masthead{
  background:
    linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,0) 55%),
    var(--harbor);
  color:#fff; border-radius:var(--r-card); padding:15px 20px 14px;
  box-shadow:var(--shadow-lg); margin-bottom:14px; display:block; border:none;
}
/* Trimmed hard (JRE, 2026-07-26): the supplier's whole job is the sheet under
   this, and a 34px date on a 24px-padded block was pushing the first product
   row off a laptop screen. Same three facts, a third of the height. */
.rb .masthead .brand{ font-size:11px; font-weight:700; letter-spacing:.04em;
  color:rgba(255,255,255,.55); text-transform:uppercase; margin-bottom:3px; }
/* The counterparty's own mark (branding.py). It sits on a white tile because
   these arrive as whatever the supplier has: dark wordmarks on transparent,
   400px JPEGs with a white box baked in. One light tile makes both land the
   same way on a dark masthead, and object-fit keeps a wide wordmark and a
   square roundel the same visual weight without touching the file. Floated
   right so it costs no vertical space - the trim above was deliberate. */
.rb .masthead .suplogo{
  float:right; margin:-2px 0 6px 16px;
  height:34px; width:auto; max-width:150px; object-fit:contain;
  background:#fff; border-radius:var(--r-sm); padding:5px 8px;
  box-shadow:0 1px 3px rgba(0,0,0,.18);
}
@media (max-width:520px){ .rb .masthead .suplogo{ height:26px; max-width:104px; } }
.rb .masthead h1{ font-size:21px; font-weight:800; letter-spacing:-.02em; line-height:1.15; }
.rb .masthead .chips{ display:flex; flex-wrap:wrap; gap:7px; margin-top:9px; }
.rb .masthead .chip{ font-size:12px; font-weight:700; padding:5px 12px;
  border-radius:var(--r-pill); background:rgba(255,255,255,.12); color:rgba(255,255,255,.92); }

/* ---------- customer mobile shell ---------- */
/* `dvh` after `vh`, not instead of it: on a phone `100vh` is the LARGE
   viewport - the height the page would have with the browser's toolbars
   retracted - so a short day was always a few lines taller than the screen and
   scrolled a little for no reason. `100dvh` is the height that is actually
   visible right now; a browser too old to know it keeps the line above. */
.rb .cust{ max-width:460px; margin:0 auto; min-height:100vh; min-height:100dvh;
  background:var(--ground); padding-bottom:132px; }
.rb .cust-head{
  background:
    linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,0) 55%),
    var(--harbor);
  color:#fff; padding:22px 18px 24px; border-radius:0 0 var(--r-lg) var(--r-lg);
}
.rb .cust-head .greet{ font-size:12.5px; color:rgba(255,255,255,.62); font-weight:600; }
.rb .cust-head h1{ font-size:26px; font-weight:800; letter-spacing:-.03em; margin:3px 0; }
.rb .cust-head .when{ font-size:13px; color:rgba(255,255,255,.78); font-weight:600; }
.rb .cust-body{ padding:16px 14px 0; }
.rb .item{ background:#fff; border:1px solid var(--line); border-radius:var(--r-lg);
  padding:15px 16px; margin-bottom:10px; box-shadow:var(--shadow); }
.rb .item .name{ font-size:14.5px; font-weight:700; letter-spacing:-.01em; }
.rb .item .price{ font-size:23px; font-weight:800; letter-spacing:-.03em; margin-top:5px;
  font-variant-numeric:tabular-nums; }
.rb .item .price span{ font-size:13px; font-weight:600; color:var(--ink-ghost); letter-spacing:0; }
.rb .item.gone{ opacity:.62; }
.rb .stepper{ display:flex; align-items:center; gap:9px; margin-top:11px; }
.rb .stepper button{ width:36px; height:36px; border-radius:var(--r-md); border:1px solid var(--line);
  background:#fff; font:inherit; font-size:17px; font-weight:700; color:var(--ink);
  cursor:pointer; line-height:1; }
.rb .stepper button:hover:not([disabled]){ background:var(--sunken); }
.rb .stepper button[disabled]{ opacity:.35; cursor:not-allowed; }
.rb .stepper .qty{ min-width:34px; text-align:center; font-size:17px; font-weight:800;
  font-variant-numeric:tabular-nums; }
.rb .cust-bar{
  position:fixed; bottom:62px; left:50%; transform:translateX(-50%);
  width:min(460px,100%); background:#fff; border-top:1px solid var(--line);
  padding:12px 16px; display:flex; align-items:center; justify-content:space-between;
  gap:12px; box-shadow:0 -8px 24px -14px rgba(20,42,59,.35); z-index:20;
}
.rb .cust-bar .total{ font-size:20px; font-weight:800; letter-spacing:-.03em;
  font-variant-numeric:tabular-nums; }
/* `.cust-tabs` - the customer's fixed bottom tab strip - stood here until
   2026-08-07. Its two page tabs became the header's Oggi / Storico, the same
   control the supplier sheet and the back office wear, and the strip's last
   passenger (Chat) is a link at the foot of the orders list. Nothing renders
   it any more; the desktop rule below went with it. */

/* ---------- customer shell on a desktop ----------
   The shop is mobile-first and stays that way: most customers order from a
   phone. But some of them are wholesalers ordering forty lines from an office
   PC, and a 460px column stranded in the middle of a 1900px screen made them
   scroll a dozen times through a day that would have fitted on one screen
   (JRE, 2026-07-26). Same markup, same components - only the shell widens and
   the products flow into columns. Nothing below is reachable on a phone. */
@media (min-width:900px){
  .rb .cust{ max-width:1180px; padding-bottom:96px; }
  .rb .cust-head{ padding:26px 30px 28px; }
  .rb .cust-body{ padding:20px 24px 0; }
  /* the day, in columns. `.grp` keeps its species heading full width. */
  .rb .cust .grp .item{ margin-bottom:0; }
  .rb .cust .grp > .item, .rb .cust .grp{ break-inside:avoid; }
  .rb .cust .grp{ display:grid; gap:12px;
    grid-template-columns:repeat(auto-fill, minmax(300px, 1fr)); }
  .rb .cust .grp h2{ grid-column:1 / -1; }
  /* the search sticks under nothing on desktop, so give it the full width */
  .rb .cust .findrow{ top:0; }
  /* the basket stops being a phone bottom sheet and becomes a normal bar the
     width of the shell */
  .rb .cust-bar{ width:min(1180px,100%); border-radius:var(--r-lg) var(--r-lg) 0 0;
    padding:14px 24px; }
}

/* ---------- a list you pick one thing out of ----------
   A native <datalist> renders as the BROWSER's list, not ours: an unstyled
   OS-drawn column in the wrong typeface that on the orders page opened as a
   full-height wall of ~250 customer names, every one of them, no search worth
   the name and no way to tell "Acquachiara B.V." from the two entries either
   side of it (JRE, 2026-08-03).

   So the datalist stays as the data and the no-JavaScript fallback, and
   static/combo.js draws the list instead. Shape is borrowed from `.acctpop`,
   which is the portal's other floating panel - same white, same hairline, same
   lift - so a dropdown reads as a surface of this portal and not of macOS.

   THE PANEL IS NOT INSIDE THE PAGE. combo.js moves it to <body> and places it
   with position:fixed against the field's own rectangle, because an absolutely
   positioned panel is clipped by any ancestor that hides its overflow - and
   `.card` does, for its rounded corners. The Add-an-invoice row sits at the
   bottom of one, and eight matches rendered as one visible row with the rest
   cut off at the card's edge. Nothing about a dropdown should depend on how
   much room is left in the box it was declared in.

   Which is why there are no width rules here: the panel is `max-content`
   between the field's own width and a cap, so a 118px field still gets a list
   wide enough to read a company name in, with no page needing to say so.

   Unscoped, unlike most of this file: the class names belong to this component
   alone, the panel is a child of <body> and not of any shell, and a picker
   should work on a page that never opted into `.rb`. */
.combo{ position:relative; display:block; }
.combo > .field{ padding-right:34px; }         /* room for the caret */
.combo-caret{ position:absolute; top:0; right:0; width:32px; height:100%;
  display:flex; align-items:center; justify-content:center;
  background:none; border:0; padding:0; cursor:pointer; color:var(--ink-ghost); }
.combo-caret:hover{ color:var(--azure); }
.combo-caret svg{ width:15px; height:15px; transition:transform .14s; }
.combo.open .combo-caret svg{ transform:rotate(180deg); }

/* left/top/min-width/max-width are set by combo.js against the live field */
.combo-pop{ position:fixed; z-index:200; width:max-content;
  max-height:288px; overflow-y:auto; padding:5px;
  background:#fff; border:1px solid var(--line); border-radius:var(--r-md);
  box-shadow:0 14px 34px rgba(20,42,59,.16); }
.combo-pop[hidden]{ display:none; }
.combo-opt{ display:block; width:100%; text-align:left; font:inherit;
  font-size:13.5px; color:var(--ink); background:none; border:0;
  padding:8px 10px; border-radius:var(--r-sm); cursor:pointer;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* ONE highlight, driven by the keyboard. A separate :hover colour means the
   mouse resting anywhere over the panel lights a second row, and then Enter
   takes the one the mouse is NOT on - so hover moves the selection instead of
   drawing its own. */
.combo-opt.on{ background:var(--azure-wash); color:var(--azure-deep); }
.combo-opt b{ font-weight:800; color:var(--azure); }
.combo-opt.on b{ color:var(--azure-deep); }
.combo-note{ padding:8px 10px; font-size:12px; color:var(--ink-ghost); }
/* the option's data-note, a word beside the name ("· live") */
.combo-side{ font-size:12px; color:var(--ink-ghost); font-weight:500; }
.combo-opt.on .combo-side{ color:var(--azure-deep); }
.combo-note b{ color:var(--ink-soft); font-weight:700; }

/* ---------- a table too wide for the screen ----------
   The shared scroller, hoisted out of supplier_pricelist.html (2026-08-02) so
   the back office can use the same one. A `.tbl` is a real table with aligned
   columns; the phone answer is to swipe it sideways, NOT to let `.card`'s
   overflow:hidden cut the last three columns off with nothing to say so - the
   silent clip is exactly the complaint the supplier sheet already fixed.

   The shadows are the part that matters: `background-attachment:local` pins
   the white pads to the content and `scroll` pins the shadows to the frame,
   so a shadow shows on a side ONLY while there is more table that way. A
   scroll nobody announces is a scroll nobody makes. */
.rb .tblwrap{
  overflow-x:auto; -webkit-overflow-scrolling:touch;
  background:
    linear-gradient(to right, var(--sheet) 30%, rgba(255,255,255,0)) left center,
    linear-gradient(to left,  var(--sheet) 30%, rgba(255,255,255,0)) right center,
    radial-gradient(farthest-side at 0 50%, rgba(20,42,59,.13), rgba(20,42,59,0)) left center,
    radial-gradient(farthest-side at 100% 50%, rgba(20,42,59,.13), rgba(20,42,59,0)) right center;
  background-repeat:no-repeat;
  background-size:34px 100%, 34px 100%, 13px 100%, 13px 100%;
  background-attachment:local, local, scroll, scroll;
}

/* ---------- responsive ---------- */
@media (max-width:1080px){ .rb .g-side{ grid-template-columns:1fr; } }
@media (max-width:900px){
  .rb .g2{ grid-template-columns:1fr; }
}
@media (max-width:620px){
  .rb .top,.rb .content,.rb .rail,.rb .sendbar{ padding-left:16px; padding-right:16px; }
  /* the big date takes a line of its own under the controls, from the left:
     alone at the right edge of a wrapped row it reads as a stray */
  .rb .raildate{ flex-basis:100%; margin:6px 0 0; white-space:normal; }
  .rb .raildate b{ font-size:21px; line-height:26px; }
  .rb .card-head,.rb .card-body,.rb .card-foot,
  .rb .tbl thead th,.rb .tbl tbody td,.rb .need{ padding-left:16px; padding-right:16px; }
  .rb .tbl tfoot td{ padding-left:16px; padding-right:16px; }
}

/* ==================================================================
   "ARE YOU SURE?" IN OUR OWN VOICE (2026-08-05)
   ------------------------------------------------------------------
   window.confirm() draws the BROWSER's box: the host name in bold across
   the top ("…up.railway.app says"), OS type, OS buttons. A supplier taking
   a fish off their own price list was reading a Railway URL while they did
   it (JRE). Same question, same page, our own paper - see static/confirm.js
   for the data-confirm wiring. No tenant literals: every word comes from
   the page.
   ================================================================== */
dialog.pconfirm{
  border:1px solid var(--line); border-radius:var(--r-lg); padding:0;
  /* Centre it explicitly. A <dialog> is positioned, and `width:100%` with a
     max-width is NOT enough on its own - the box sat against the left edge
     until inset+margin said otherwise. */
  inset:0; margin:auto;
  width:min(420px, calc(100vw - 32px));
  background:var(--sheet); color:var(--ink);
  box-shadow:0 18px 48px rgba(20,42,59,.22);
}
dialog.pconfirm::backdrop{ background:rgba(20,42,59,.42); }
dialog.pconfirm .pconfirm-body{ padding:22px 24px 4px; }
dialog.pconfirm p{
  margin:0 0 12px; font-size:14.5px; line-height:1.5; color:var(--ink);
}
/* what it costs reads quieter than what it does */
dialog.pconfirm p + p{ font-size:13px; color:var(--ink-soft); }
dialog.pconfirm .pconfirm-acts{
  display:flex; justify-content:flex-end; gap:9px; padding:10px 24px 20px;
}
dialog.pconfirm .btn{ min-height:40px; }
dialog.pconfirm .btn.danger{
  border-color:var(--red); background:var(--red); color:#fff;
}
dialog.pconfirm .btn.danger:hover{ background:#93331F; border-color:#93331F; }
/* A question with a DETAIL block under it (data-confirm-detail): a little
   wider, and the block reads as a card inside the box, not as more prose. */
dialog.pconfirm.has-detail{ width:min(560px, calc(100vw - 32px)); }
dialog.pconfirm .pconfirm-detail{
  margin:-2px 0 8px; padding:12px 14px; border:1px solid var(--line);
  border-radius:var(--r-md, 10px); background:var(--paper);
  display:grid; gap:10px; font-size:13px; line-height:1.45;
}

/* ---------- the way back from a partner page (2026-08-05) ----------
   "when i view as supplier the top bar dissapears so i cant easily switch
   back to backoffice" (JRE). The operator bar is not on a partner page and
   must never be - `_shell_topnav.html` is the back office's own chrome, and
   a supplier reading their own sheet may not be handed it. So the way back
   is this strip and nothing else: one link, no menu, no partner list, no
   token. It renders only for a signed-in operator (`viewas`), which is why
   a supplier never sees a door to a place they cannot open.

   Sticky, because the pages it sits on are long and the way out should not
   depend on scrolling back up. Ink, not paper: it is the one thing on the
   page that is NOT the partner's portal, and it says so by not matching. */
.rb .vaback{
  position:sticky; top:0; z-index:60;
  background:var(--ink); color:#DCE7EE;
  box-shadow:0 1px 0 rgba(0,0,0,.18);
}
.rb .vab-in{
  max-width:940px; margin:0 auto; padding:7px 24px;
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  font-size:12.5px; line-height:1.4;
}
.rb .vab-tag{
  font-size:10.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  padding:3px 8px; border-radius:var(--r-pill);
  background:rgba(255,255,255,.14); color:#fff; white-space:nowrap;
}
.rb .vab-txt{ color:#A9BECD; }
/* pushed to the far end: it is the action, and the rest is the label */
.rb .vab-btn{
  margin-left:auto; display:inline-flex; align-items:center; gap:6px;
  font-size:12.5px; font-weight:600; color:#fff; text-decoration:none;
  padding:5px 11px; border-radius:var(--r-ctrl);
  border:1px solid rgba(255,255,255,.28); background:rgba(255,255,255,.08);
}
.rb .vab-btn:hover{ background:rgba(255,255,255,.18); }
.rb .vab-btn svg{ width:14px; height:14px; flex:none; }
/* the customer shop is its own full-bleed column, so the strip matches it */
.rb .cust .vab-in{ max-width:100%; padding-left:16px; padding-right:16px; }
@media print{ .rb .vaback{ display:none !important; } }

/* The preview supplier's row on the prices board: present and workable, but
   never mistaken for a partner who is actually late this morning. */
.rb tr.prevrow > td{ background:var(--sunken); }
/* NAME AND TAG ON ONE LINE (JRE, 2026-08-06: "line these (titel + card) out").
   `.rb .tbl .who` is display:block and every other row wants it that way - a
   name sits ABOVE its own `.meta` line - but here the tag is a SIBLING that
   follows the name, so block pushed it to a second line and its 8px offset
   read as an indent under the title. Inline for this row only; the tag is
   still an inline-block pill, so the two share a line and wrap together on a
   narrow screen. */
.rb .tbl tr.prevrow .who{ display:inline; }
.rb .prevtag{ margin-left:8px; vertical-align:middle; }

/* ==================================================================
   A FOLD INSIDE A CARD (.cfold)
   ------------------------------------------------------------------
   A hairline, a summary you press, a count on the right, and the card's
   own rows continuing underneath. Written for the customer page when it
   became a card in Settings (2026-08-05); shared from here since the money
   and the book moved on to the Dashboard and the price agreements stayed
   behind (JRE, 2026-08-06), which is two pages drawing the same thing.

   What it must NOT look like is a floating card of its own: that was `.grp`
   on the old page, and it is the reason a section inside a card reads as a
   second card stacked on the first.
   ================================================================== */
.rb .cfold{ border-top:1px solid var(--line); }
.rb .cfold > summary{ list-style:none; cursor:pointer; display:flex; gap:12px;
  align-items:center; padding:13px 22px; }
.rb .cfold > summary::-webkit-details-marker{ display:none; }
.rb .cfold > summary:hover{ background:var(--sunken); }
.rb .cfold .caret{ color:var(--ink-ghost); font-size:11px; flex:none;
  transition:transform .15s var(--ease-out); }
.rb .cfold[open] .caret{ transform:rotate(90deg); }
.rb .cfold .gt{ display:block; font-size:14px; font-weight:800; letter-spacing:-.01em; }
.rb .cfold .gs{ display:block; font-size:12.5px; color:var(--ink-soft); }
.rb .cfold .gn{ margin-left:auto; flex:none; font-size:12px; font-weight:800;
  font-variant-numeric:tabular-nums; color:var(--ink-soft);
  background:var(--sunken); border-radius:var(--r-pill); padding:3px 11px; }
.rb .cfold .gn.warn{ background:var(--red-wash); color:var(--red); }
.rb .cfold.amber .gn{ color:#8F5415; background:var(--amber-wash); }
.rb .cfold.teal .gn{ color:#0B7268; background:var(--teal-wash); }
.rb .cfold-body, .rb .owed-body{ border-top:1px solid var(--line);
  background:var(--paper); }
/* a long section scrolls inside itself rather than pushing every other fold
   off the screen - sixty customers in one card is the whole risk here */
.rb .cfold-body.tall{ max-height:min(52vh, 460px); overflow-y:auto; }
.rb .cfold-body .tbl thead th{ position:sticky; top:0; z-index:1;
  background:var(--sunken); }

/* ==================================================================
   THE BACK OFFICE ON A PHONE (2026-08-02)
   ------------------------------------------------------------------
   The operator pages were laid out for the desk they are worked at, and
   every one of them held at least one row that was simply built wider than
   a phone: a card head, a fold summary, an action group. A flex row that
   cannot wrap has a min-content width equal to the SUM of its children, so
   one 503px summary quietly dragged the whole card - and with it the page -
   past the viewport, and the page scrolled sideways under the thumb.

   None of this reshapes a page. It lets rows that were always meant to be
   rows become two rows when there is no room for one, which is what every
   one of them would have done had it been allowed to wrap.
   ================================================================== */
@media (max-width:640px){
  /* The four flex rows the card is built from. `.acts` is the one that
     matters most - it holds the buttons, and a button pushed off the right
     edge is a button that does not exist. */
  .rb .card-head{ flex-wrap:wrap; }
  .rb .card-foot,
  .rb .acts{ flex-wrap:wrap; }
  /* `flex:none` in the base rule is why wrapping alone was not enough: a
     group that cannot shrink is as wide as its contents, so its own children
     never had a reason to wrap and 415px of search box, segmented control
     and language buttons sat there whatever the screen did. Let it shrink,
     and the card head drops it onto its own line first. */
  .rb .card-head > .acts{ flex:0 1 auto; min-width:0; }
  /* ...and the heading TAKES the first line, rather than hoping to be given
     it (2026-08-10). The two rules above only wrap when `.acts` is wider than
     the row, and `.ch` is `flex:1 1 0%; min-width:0` - a column whose
     hypothetical size is ZERO. So the day `.acts` happens to fit beside it -
     a wider phone, a card whose pills are absent because nobody owes
     anything, a shorter word in another language - nothing wraps, `.ch` is
     handed the leftovers, and the heading renders 19px wide with one word per
     line under it while the search box sits alongside. That was the Customers
     card on the Dashboard, on a 430pt phone with the books settled. A basis
     of 100% makes the stack the RULE instead of a side effect of overflow. */
  .rb .card-head > .ch{ flex-basis:100%; }
  /* a fold's own heading row: two spans that together are wider than the
     screen (the waiting-bids fold on /orders was 503px of them) */
  .rb details > summary{ flex-wrap:wrap; }

  /* the sticky page header: title on the left, search or day controls pushed
     right by margin-left:auto. On a phone they go under the title instead. */
  .rb .main > .top{ flex-wrap:wrap; gap:8px 16px; }
  .rb .top .right{ margin-left:0; flex-wrap:wrap; width:100%; }
  .rb .top .right > form{ flex:1 1 auto; }
  .rb .top .right .field{ flex:1 1 auto; min-width:0; }

  /* A counterparty row: name over its mark, controls on their own line under
     it. Both pages that draw one get this, which is the point of the shared
     block - the tab answered the phone and Settings did not. */
  .rb .sup-row{ flex-wrap:wrap; padding:12px 16px; }
  /* wrap INSIDE the group too, or the last control simply leaves the screen:
     Settings' row carries five (kind, rate, its chip, Copy link and the ⋯),
     which come to ~360px against the 343px a 375px phone has left */
  .rb .sup-right{ width:100%; margin-left:0; flex-wrap:wrap; }

  /* Minimums written for a 1280px column. They are floors, not sizes, so
     dropping them costs the desk nothing and is the difference between a
     row that wraps and a row that bleeds. */
  .rb .wrow-what{ min-width:0; flex-basis:100%; }
  /* two figures per line instead of one 160px column and a lot of paper */
  .rb .sumstrip .f{ flex-basis:44%; }

  /* Popups anchored with right:0 assume their button is at the right edge.
     On a wrapped header it is not: on the supplier sheet the account panel
     opened 182px off the LEFT of the screen. A phone has one honest place
     to put a panel this size, and anchoring stops mattering there. */
  .rb .acctpop{
    position:fixed; top:auto; right:10px; bottom:10px; left:auto;
    width:min(300px, calc(100vw - 20px));
    box-shadow:0 -4px 40px rgba(20,42,59,.22);
  }
  /* ...but NOT in the customer shop. The rule above is for a wrapped BACK
     OFFICE header, where the button that opened the panel has moved and there
     is no honest place left to anchor it. The shop's header does not wrap: the
     profile button is in the top right corner of the screen, and pinning its
     panel to the bottom of the phone left a customer tapping at the top and
     watching a panel appear at the bottom (2026-08-05). */
  .rb .cust .acctpop{
    position:absolute; top:calc(100% + 6px); right:0; bottom:auto;
    width:min(260px, calc(100vw - 28px));
    box-shadow:0 14px 34px rgba(20,42,59,.18);
  }
  .rb .langsel-list{ max-width:calc(100vw - 24px); }

  /* A table squeezed into 341px does not just scroll - it wraps every cell to
     its narrowest word first, and the Settings row came to 216px tall with a
     120px "Portal link" column broken over five lines. Sized to its content
     instead, each row is one line again and the sideways swipe does the work
     it is there for. `min-width:100%` keeps the promise both ways: a table
     that already fits stays full width and never scrolls at all.

     SCOPED TO `.main`, which is the back office. Both supplier pages already
     answer this question their own way - their rows become cards below
     620/560px - and a `width:max-content` reaching in there wins on
     specificity over their own `width:auto` and puts a finished phone layout
     inside a 728px scroller. */
  .rb .main .tblwrap > .tbl{ width:max-content; min-width:100%; }

  /* ...unless the row is a FORM. `.tblcards` is the other answer, and it is
     the one Settings needs: a cut-off gets fixed at 07:40 from a doorway,
     and 1196px of sideways swipe to reach the Language column is not that.
     Each row becomes a small card, each cell a labelled line. Same markup
     and the same handlers - only the header row stops being drawn, and its
     headings are stamped onto the cells as labels instead (the page script
     does it, so a new column cannot forget one). */
  .rb .tblcards{ overflow-x:visible; background:none; }
  /* `.main` here only to match the rule above selector-for-selector - it is
     one class more specific, and without it the swipe layout would win over
     the cards on the very page that asked for cards */
  .rb .main .tblcards > .tbl{ width:100%; min-width:0; display:block; }
  .rb .tblcards thead{ display:none; }
  .rb .tblcards tbody, .rb .tblcards tr, .rb .tblcards td{ display:block; width:auto; }
  .rb .tblcards tbody tr{ padding:10px 16px 12px; border-bottom:1px solid var(--line); }
  .rb .tblcards tbody tr:last-child{ border-bottom:none; }
  .rb .tblcards tbody td{ padding:5px 0; border:none;
    display:flex; align-items:center; gap:8px 12px; flex-wrap:wrap; }
  .rb .tblcards tbody td[data-k]:not([data-k=""]):not(:first-child)::before{
    content:attr(data-k); flex:0 0 84px;
    font-size:10.5px; font-weight:700; letter-spacing:.08em;
    text-transform:uppercase; color:var(--ink-ghost); }
  /* the first cell is the card's name, not a field */
  .rb .tblcards tbody td:first-child{ display:block; padding:0 0 6px; }
  .rb .tblcards tbody td:first-child::before{ content:none; }

  /* The way back off a preview: the tag and the button, and no sentence.
     The customer shop is read on a phone by design, and a strip that wraps
     to two lines pushes the day's first product below the fold to explain
     something the two controls already say. */
  .rb .vab-txt{ display:none; }
  .rb .vab-in{ padding-left:16px; padding-right:16px; }

  /* The confirm box: full-width buttons, go-ahead on top. A 40px button in
     the bottom-right corner is the hardest place on a phone to reach, and
     these questions get answered one-handed on a quay. */
  dialog.pconfirm .pconfirm-acts{ flex-direction:column-reverse; gap:8px; }
  dialog.pconfirm .pconfirm-acts .btn{ width:100%; min-height:44px; }
}

/* ---------- a save that FAILED has to say so (2026-08-11) ----------
   Two autosave surfaces (the supplier's weights, the Settings rows) showed a
   green flash on success and NOTHING on refusal or a dead connection - the
   typed value stayed in the field looking saved. The invoice reconciles
   against one of those fields. Red border on what did not land, red dot
   where the green one would have been, and a line of text that says what to
   do. Cleared by the next save that goes through. */
.savefail input, .savefail select{
  border-color:var(--red); box-shadow:0 0 0 1px var(--red) inset;
}
.savefail .savedot{ background:var(--red); }
.savefail-note{
  margin:6px 0 0; font-size:12.5px; font-weight:600; color:var(--red);
}

/* A pressed submit while its request runs (confirm.js's one-press guard). */
form .btn.busy, form button.busy{ opacity:.55; cursor:progress; }

/* ==================================================================
   THE FEEL LAYER (2026-08-19)
   ------------------------------------------------------------------
   The fluid-interface audit - Apple's WWDC design principles read
   against this portal, findings and the reasoning in docs/FEEL.md.
   Three rules carried into CSS, none of them a redesign:

     1. RESPOND ON POINTER-DOWN. Every pressable thing acknowledges
        the press the instant it lands, never only on release.
     2. MATERIALIZE, don't blink. A floating surface (popover, menu,
        dialog, toast) arrives from its own trigger - scale + fade
        from the anchor, ~160ms, decelerating, no overshoot - and
        anything that also EXITS leaves along the same path.
     3. CHROME IS A MATERIAL. The sticky bars are translucent with a
        backdrop blur, so the page visibly scrolls underneath them
        instead of being eaten by an opaque strip.

   Everything here is covered by the global prefers-reduced-motion
   kill switch above; the transparency has its own opt-outs below.
   ================================================================== */

/* ---------- 1. the press ----------
   No transition on purpose: these are one-tap controls, and both edges of
   the press are instant. The .btn forms ease back because a 40px button
   snapping is visible where a 28px chip is not. */
.rb .stepper button:active:not([disabled]),
.rb .station:active,
.rb .daystep:active:not(.off),
.rb .dayhist:active,
.rb .daynav .daytab:active,
.rb .rowmenu > summary:active,
.rb .acctmenu summary:active,
.rb .langsel-face:active,
.rb .btn.tourback:active,
.langs button:active,
.datechips a:active{ transform:scale(.96); }
/* menu rows answer with their surface, like every OS menu does */
.rb .rowpop button:active, .rb .rowpop label:active,
.rb .langsel-list li:active,
.combo-opt:active{ background:var(--azure-wash); }

/* ---------- 2. arrival ---------- */
@keyframes feel-pop{ from{ opacity:0; transform:scale(.96) translateY(-3px); } }
@keyframes feel-fade{ from{ opacity:0; } }
/* every floating panel grows out of the corner its trigger sits at, so the
   spatial tie between button and surface is visible for a frame or two */
.rb .rowpop, .rb .acctpop{ transform-origin:top right;
  animation:feel-pop var(--t-pop) var(--ease-out); }
.rb .langsel-list, .combo-pop{ transform-origin:top left;
  animation:feel-pop var(--t-pop) var(--ease-out); }
.rb .nav-menu.open{ transform-origin:top center;
  animation:feel-pop var(--t-pop) var(--ease-out); }
/* the confirm box is modal, so it arrives from the centre with its scrim
   fading in on the same clock - one event, one motion */
dialog.pconfirm[open]{ animation:feel-pop .18s var(--ease-out); }
dialog.pconfirm[open]::backdrop{ animation:feel-fade .18s ease-out; }
/* the phone anchors the account panel to the bottom edge, so it rises from
   there instead of dropping from a corner it no longer touches - except in
   the customer shop, whose panel stays under its own button */
@media (max-width:640px){
  .rb .acctpop{ transform-origin:bottom right; }
  .rb .cust .acctpop{ transform-origin:top right; }
}

/* ---------- 3. chrome as material ----------
   color-mix over the tokens, never an rgba() respelling of them: a tenant
   theme that swaps --harbor keeps its own glass (rule 11). The solid
   backgrounds these override stay declared on the components themselves,
   which is the fallback for any browser that skips this block. */
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .rb .topnav{ background:color-mix(in srgb, var(--harbor) 88%, transparent);
    -webkit-backdrop-filter:blur(16px) saturate(1.6);
    backdrop-filter:blur(16px) saturate(1.6); }
  .rb .main > .top{ background:color-mix(in srgb, #fff 82%, transparent);
    -webkit-backdrop-filter:blur(16px) saturate(1.6);
    backdrop-filter:blur(16px) saturate(1.6); }
  .rb .sendbar{ background:color-mix(in srgb, var(--harbor) 90%, transparent);
    -webkit-backdrop-filter:blur(14px) saturate(1.5);
    backdrop-filter:blur(14px) saturate(1.5); }
  .rb .cust-bar{ background:color-mix(in srgb, #fff 84%, transparent);
    -webkit-backdrop-filter:blur(16px) saturate(1.6);
    backdrop-filter:blur(16px) saturate(1.6); }
  /* the supplier sheet's fixed send bar already blurred at 8px; same
     material as the rest now */
  .sendbar{ background:rgba(255,255,255,.82);
    -webkit-backdrop-filter:blur(16px) saturate(1.6);
    backdrop-filter:blur(16px) saturate(1.6); }
  /* the phone's slide-down menu paints on top of page content, not over its
     own bar - it stays solid or the text underneath shows through it */
  @media (max-width:900px){
    .rb .nav-menu{ background:var(--harbor);
      -webkit-backdrop-filter:none; backdrop-filter:none; }
  }
}
/* Reduced transparency or more contrast asked for = frosted glass goes
   solid again. AFTER the @supports block, so equal specificity resolves
   in this block's favour. */
@media (prefers-reduced-transparency:reduce), (prefers-contrast:more){
  .rb .topnav, .rb .sendbar{ background:var(--harbor);
    -webkit-backdrop-filter:none; backdrop-filter:none; }
  .rb .main > .top, .rb .cust-bar, .sendbar{ background:#fff;
    -webkit-backdrop-filter:none; backdrop-filter:none; }
}

/* every open price request of one supplier in one mail (2026-09-17) */
.askall{ display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;
  padding:10px 22px; background:var(--paper); border-bottom:1px solid var(--line-soft); font-size:13px; color:var(--ink-soft); }
.askall b{ color:var(--ink); }

/* ---------- PIP, the assistant, bottom right of the back office (2026-11-02) ----------
   assistant.py / _assistant.html / assistant.js. Pip's look is decant-
   trading's (src/index.css, PipTile, AssistPanel): the navy tile with the
   blue glow, the moods on `.pip[data-mood]`, a floating window over the
   page. His avatar here is a fish. Above the toast and the page, below the
   tour's dim (z 80) so the walkthrough can point at the button. */
.pip{ display:inline-block; flex-shrink:0; line-height:0; }
.pip > svg{ display:block; width:100%; height:auto; overflow:visible; aspect-ratio:100/80; }
.pip .pip-eyes, .pip .pip-scan{ display:none; }
.pip[data-mood="idle"] .pip-e-open, .pip[data-mood="reading"] .pip-e-open,
.pip[data-mood="scanning"] .pip-e-open, .pip[data-mood="thinking"] .pip-e-look,
.pip[data-mood="happy"] .pip-e-happy, .pip[data-mood="unsure"] .pip-e-unsure,
.pip[data-mood="failed"] .pip-e-worried, .pip[data-mood="scanning"] .pip-scan{ display:inline; }
.pip .pip-cap{ fill:#ffffff; transition:fill .35s ease; }
.pip[data-mood="thinking"] .pip-cap, .pip[data-mood="scanning"] .pip-cap{ fill:#9cc3f5; }
.pip[data-mood="happy"] .pip-cap{ fill:#3db87e; }
.pip[data-mood="unsure"] .pip-cap{ fill:#f0a74b; }
.pip[data-mood="failed"] .pip-cap{ fill:#e05555; }
.pip .pip-body{ transform-box:fill-box; transform-origin:50% 100%;
  transition:transform .45s cubic-bezier(.3,1.4,.5,1); }
.pip .pip-e-open, .pip .pip-e-look{ transform-box:fill-box; transform-origin:50% 50%;
  animation:pip-blink 5s infinite; }
.pip[data-mood="scanning"] .pip-cap, .pip[data-mood="thinking"] .pip-cap,
.pip[data-mood="unsure"] .pip-cap, .pip[data-mood="failed"] .pip-cap{
  animation:pip-pulse 1.6s ease-in-out infinite; }
.pip[data-mood="unsure"] .pip-body{ transform:rotate(-6deg); }
.pip[data-mood="happy"] .pip-body{ animation:pip-hop .7s cubic-bezier(.3,.8,.4,1) 1; }
.pip[data-mood="failed"] .pip-body{ animation:pip-shiver 2.4s ease-in-out infinite; }
.pip .pip-scan{ animation:pip-scanline .55s ease-in-out infinite alternate; }
@keyframes pip-blink{ 0%,93%,100%{ transform:scaleY(1); } 96%{ transform:scaleY(.12); } }
@keyframes pip-pulse{ 0%,100%{ opacity:.55; } 50%{ opacity:1; } }
@keyframes pip-shiver{ 0%,88%,100%{ transform:translateX(0); } 91%{ transform:translateX(-1px); }
  94%{ transform:translateX(1px); } 97%{ transform:translateX(-1px); } }
@keyframes pip-scanline{ from{ transform:translateY(0); } to{ transform:translateY(30px); } }
@keyframes pip-hop{ 0%{ transform:translateY(0) scale(1,1); } 18%{ transform:translateY(0) scale(1.06,.92); }
  45%{ transform:translateY(-14%) scale(.97,1.04); } 75%{ transform:translateY(0) scale(1.04,.96); }
  100%{ transform:translateY(0) scale(1,1); } }
@keyframes pip-dot{ 0%,100%{ opacity:.25; transform:translateY(0); } 50%{ opacity:1; transform:translateY(-2px); } }
@keyframes pip-in{ from{ opacity:0; transform:translateY(8px) scale(.97); } to{ opacity:1; transform:none; } }

/* the navy tile with the radial blue glow, as on decant.trading */
.pip-tile{ position:relative; display:inline-grid; place-items:center; flex-shrink:0;
  overflow:hidden; background:#091b37;
  background-image:radial-gradient(circle at 50% 58%, rgba(63,142,246,.35), transparent 62%); }
.pip-tile.md{ width:56px; height:56px; border-radius:50%; }
.pip-tile.md .pip{ width:34px; }
.pip-tile.sm{ width:36px; height:36px; border-radius:12px; }
.pip-tile.sm .pip{ width:24px; }

.pip-launch{ position:fixed; right:24px; bottom:24px; z-index:70; width:56px; height:56px;
  padding:0; border:0; border-radius:50%; background:#091b37; color:#fff; cursor:pointer;
  display:grid; place-items:center;
  box-shadow:0 12px 30px -8px rgba(9,27,55,.55), 0 2px 6px rgba(9,27,55,.22);
  transition:transform .15s ease; }
.pip-launch:hover{ transform:scale(1.05); }
.pip-launch:focus-visible{ outline:2px solid var(--azure); outline-offset:3px; }
.pip-launch .pip-x{ display:none; width:24px; height:24px; }
.pip-launch.open .pip-tile{ display:none; }
.pip-launch.open .pip-x{ display:block; }

.pip-panel{ position:fixed; right:24px; bottom:96px; z-index:70; width:420px;
  height:min(640px, calc(100dvh - 8rem)); display:flex; flex-direction:column; overflow:hidden;
  background:var(--sheet); color:var(--ink); border:1px solid var(--line); border-radius:16px;
  box-shadow:0 24px 64px -16px rgba(9,27,55,.38), 0 4px 14px rgba(9,27,55,.08);
  transform-origin:bottom right; animation:pip-in .15s ease-out; }
.pip-panel[hidden]{ display:none; }
.pip-head{ height:56px; flex:none; display:flex; align-items:center; gap:12px; padding:0 12px 0 16px;
  border-bottom:1px solid var(--line); }
.pip-head h2{ flex:1; margin:0; font-size:17px; font-weight:800; letter-spacing:-.015em; outline:none; }
.pip-icon{ width:32px; height:32px; display:grid; place-items:center; border:0; border-radius:6px;
  background:none; color:var(--ink-soft); cursor:pointer; }
.pip-icon:hover{ background:var(--paper); color:var(--ink); }
.pip-icon svg{ width:16px; height:16px; }
.pip-sr{ position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }
.pip-log{ flex:1; min-height:0; overflow-y:auto; padding:16px; display:flex; flex-direction:column;
  gap:18px; overscroll-behavior:contain; }
.pip-empty p{ margin:0 0 12px; font-size:13px; line-height:1.55; color:var(--ink-soft); }
.pip-starters{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
.pip-starters button{ width:100%; text-align:left; font:inherit; font-size:13px; color:var(--ink);
  background:none; border:1px solid var(--line); border-radius:6px; padding:8px 12px; cursor:pointer; }
.pip-starters button:hover{ background:var(--paper); }
.pip-starters button:focus-visible{ outline:2px solid var(--azure); outline-offset:1px; }
.pip-new{ display:flex; justify-content:flex-end; margin-bottom:-6px; }
.pip-new .btn{ display:inline-flex; align-items:center; gap:6px; }
.pip-new svg{ width:14px; height:14px; }
.pip-q{ margin-left:40px; text-align:right; white-space:pre-wrap; overflow-wrap:anywhere;
  font-size:13px; line-height:1.55; color:var(--ink-soft); }
.pip-a{ font-size:14px; line-height:1.55; color:var(--ink); overflow-wrap:anywhere; }
.pip-text p{ margin:0 0 8px; }
.pip-text p:last-child{ margin-bottom:0; }
.pip-text .pip-h{ font-weight:800; }
.pip-text ul, .pip-text ol{ margin:2px 0 8px; padding-left:20px; }
.pip-text li{ margin:2px 0; }
.pip-text a{ color:var(--azure); font-weight:700; text-decoration:underline; text-underline-offset:2px; }
.pip-text code{ font-size:12.5px; background:var(--paper); padding:0 4px; border-radius:4px; }
.pip-tbl{ overflow-x:auto; margin:4px 0 8px; }
.pip-tbl table{ border-collapse:collapse; font-size:12.5px; min-width:100%; }
.pip-tbl th, .pip-tbl td{ text-align:left; padding:4px 10px 4px 0; border-bottom:1px solid var(--line);
  white-space:nowrap; font-variant-numeric:tabular-nums; }
.pip-tbl th{ font-weight:700; color:var(--ink-soft); }
.pip-wait{ display:flex; align-items:center; gap:8px; font-size:12px; color:var(--ink-soft); }
.pip-dots{ display:inline-flex; align-items:center; }
.pip-dots i{ display:inline-block; width:3px; height:3px; margin:0 1px; border-radius:50%;
  background:currentColor; animation:pip-dot 1s ease-in-out infinite; }
.pip-dots i:nth-child(2){ animation-delay:.15s; }
.pip-dots i:nth-child(3){ animation-delay:.3s; }
.pip-note{ margin:8px 0 0; font-size:12px; color:var(--ink-soft); }
.pip-err{ display:flex; flex-wrap:wrap; align-items:center; gap:8px; font-size:13px; color:var(--ink-soft); }
.pip-err.red{ color:var(--red); }
.pip-fb{ margin-top:8px; }
.pip-fb-row{ display:flex; align-items:center; gap:4px; }
.pip-fb-btn{ height:28px; min-width:28px; display:grid; place-items:center; padding:0 6px; border:0;
  border-radius:6px; background:none; color:var(--ink-ghost); cursor:pointer; font:inherit; font-size:12px; }
.pip-fb-btn:hover, .pip-fb-btn[aria-pressed="true"]{ background:var(--paper); color:var(--ink); }
.pip-fb-btn svg{ width:14px; height:14px; }
.pip-fb-thanks{ margin-left:4px; font-size:12px; color:var(--ink-soft); }
.pip-fb-form{ margin-top:8px; padding:12px; border:1px solid var(--line); border-radius:8px;
  display:flex; flex-direction:column; gap:8px; }
.pip-fb-form p{ margin:0; font-size:12px; font-weight:700; }
.pip-fb-chips{ display:flex; flex-wrap:wrap; gap:6px; }
.pip-chip{ font:inherit; font-size:12px; font-weight:600; color:var(--ink-soft); background:none;
  border:1px solid var(--line); border-radius:999px; padding:4px 10px; cursor:pointer; }
.pip-chip[aria-pressed="true"]{ color:var(--azure); border-color:var(--azure); background:rgba(22,104,184,.08); }
.pip-fb-form textarea{ font:inherit; font-size:13px; resize:none; border:1px solid var(--line-strong);
  border-radius:6px; padding:6px 10px; }
.pip-fb-form .btn{ align-self:flex-end; }
.pip-compose{ flex:none; padding:10px 12px 12px; border-top:1px solid var(--line); }
.pip-field{ display:flex; align-items:flex-end; gap:8px; padding:6px 6px 6px 12px; background:#fff;
  border:1px solid var(--line-strong); border-radius:8px; }
.pip-field:focus-within{ border-color:var(--azure); box-shadow:0 0 0 3px rgba(22,104,184,.15); }
.pip-field textarea, .pip-field textarea:focus{ flex:1; margin:0; padding:6px 0; min-height:32px;
  max-height:104px; resize:none; border:0; border-radius:0; outline:none; box-shadow:none;
  background:transparent; font:inherit; font-size:14px; line-height:20px; color:var(--ink); }
.pip-send, .pip-stop{ width:32px; height:32px; flex:none; display:grid; place-items:center; border:0;
  border-radius:6px; cursor:pointer; }
.pip-send{ background:#091b37; color:#fff; }
.pip-send:hover{ background:#163463; }
.pip-stop{ background:none; color:var(--ink); border:1px solid var(--line-strong); }
.pip-send svg, .pip-stop svg{ width:16px; height:16px; }
.pip-send[hidden], .pip-stop[hidden]{ display:none; }
@media (max-width:640px){
  .pip-launch{ right:16px; bottom:16px; }
  .pip-panel{ left:8px; right:8px; top:64px; bottom:88px; width:auto; height:auto; }
}
@media (prefers-reduced-motion:reduce){
  .pip *, .pip-dots i, .pip-panel{ animation:none !important; transition:none !important; }
}
@media print{ .pip-launch, .pip-panel{ display:none !important; } }
