:root {
  --brand: #2B5CE5;
  --brand-dark: #1948CD;
  /* THE SAME COLOUR, AS CHANNELS. Fifteen places across five screens wanted a
     faded brand — a picked row, a status pill, a chosen delivery type — and
     every one of them had the hex typed out again in full, as an rgba() literal.
     A tint written that way is invisible to a change of --brand: the token
     moves and fifteen patches of the old colour stay exactly where they were.
     Written as `rgb(var(--brand-rgb) / .14)` they follow it. */
  --brand-rgb: 43 92 229;
  /* ⚠️ TEXT ON THE BRAND IS WHITE, NOT --brand-ink. The cyan was light enough
     to carry dark text (6.6:1) and the blue is not (3.2:1 — under AA, and it was
     used on 11px pills and a gate button). White on the blue is 5.6:1. Six
     places paired the ink with the brand and all six flipped with the value.
     --brand-ink is still the app's text colour on --bg and --panel; it is just
     not what goes ON the brand. */
  --brand-ink: #101820;
  --bg: #f4f7f9;
  --panel: #ffffff;
  --panel2: #eef2f5;
  --text: #101820;
  /* ⚠️ SLATE-600, NOT SLATE-500, AND THE 0.27 IT GAINED IS THE WHOLE REASON.
     --muted on --panel2 measured 4.23:1 — a quarter of a point under AA, on
     /monitoring's column headers, /driver's "Updated 7:23 pm" and the inherited
     values on a customer record. Muted is not the same as unreadable: this is
     text a person is expected to read, on 30 rules. 6.73:1 now, and against a
     white panel 7.58:1. See INT-023. */
  --muted: #475569;
  --line: #d7dfe6;
  /* ⚠️ THE THREE STATUS COLOURS ARE DARKENED ONE STEP, AND IT WAS A MEASUREMENT
     RATHER THAN A TASTE. Every pill, tag, badge, strip and error line in TopGas
     pairs one of these with its own tint, and none of the three pairings reached
     AA: --ok 3.00:1, --warn 2.86:1, --bad 3.95:1, across ~130 rules on 20
     screens. The amber was the worst and carried the most text — /orders and
     /orders-review's warning strips are the same component INT-017 fixed on
     /planning and left here.

     ⚠️ AND THE HALF NOTHING COULD SEE DECIDED IT. 59 more rules colour text with
     one of these tokens and state no background at all; on white, --ok was
     3.30:1 and --warn 3.19:1. colourPairing.test.ts cannot see those — it needs
     both halves, or a button — so a fix that only repaired the pairs it reports
     would have left the larger half unmeasured and unfixed. Darkening the token
     itself is the only move that reaches both.

     Each is checked on all three surfaces it lands on: its own tint, white, and
     white on top of it. See docs/DECISIONS.md → The status palette. */
  --ok: #15803d;
  --ok-bg: #dcfce7;
  --bad: #b91c1c;
  --bad-bg: #fee2e2;
  --warn: #b45309;
  --warn-bg: #fef3c7;
  /* RUN COLOURS — several runs on one map have to stay apart at a glance.
     Here rather than in the screen because they are brand-level choices, and
     because JavaScript reads them back with getComputedStyle: a palette
     hardcoded in a script is a second set of colours nobody would find. Chosen
     to differ in lightness as well as hue, so they survive a colour-blind
     reader and a printed page. */
  /* ⚠️ --run-1 IS THE OLD BRAND CYAN AND DELIBERATELY DOES NOT FOLLOW --brand.
     It coincided with the brand rather than deriving from it, and these six are
     IDENTITY colours: their job is to stay apart from each other, not to match
     the header. Pointing this one at the brand would move it every time the
     brand moves, for no reason a planner looking at a map would recognise. */
  --run-1: #17ABCB;
  --run-2: #B45309;
  --run-3: #7C3AED;
  --run-4: #15803D;
  --run-5: #BE123C;
  --run-6: #0F172A;
  --radius: 12px;
  --shadow: 0 1px 3px rgba(16,24,32,.08), 0 1px 2px rgba(16,24,32,.04);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  background: var(--bg); color: var(--text); line-height: 1.5;
  padding: 0 16px 40px; max-width: 900px; margin: 0 auto;
}
.brandbar {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 0; margin-bottom: 8px; border-bottom: 2px solid var(--brand);
}
.brandbar img { height: 40px; width: auto; display: block; }
.wordmark { display: block; line-height: 0; }
/* ⚠️ THE MARK, NOT TWO SPANS OF TEXT. It was `Top` in --brand and `Gas` in
   --brand-ink at 30px, which was the brand as CSS could draw it before there
   was a logo. `logo-lockup.svg` is the designer's dark art with the tagline
   left out: four pixels tall in a 34px bar is mush, and mush with a meaning.
   34px keeps the bar the height it was, so no screen below it moves. */
.wordmark img { height: 34px; width: auto; display: block; }

/* ---- THE WORDMARK IS THE WAY BACK -----------------------------------------
   It was a <span> on every screen, so ten screens had no way to the landing
   page at all and the four that did put an "All screens" button somewhere
   different on each. On a phone that left the browser's back button or
   retyping the address.

   The brand is where everybody already clicks, it costs no width in a 375px
   header, and being in the shared stylesheet means a new screen inherits it
   rather than remembering it. `screenShell.test.ts` fails the suite if a
   screen ships without it.

   It renders EXACTLY as the span did — colour and underline are taken off, and
   the two halves keep their own colours — so this changes navigation and not
   the look of a single screen. */
a.wordmark { text-decoration: none; color: inherit; }
/* The hover used to darken the "Top" to --brand-dark. There is no text to
   darken now, and repainting a designer's mark on hover is not ours to do —
   so the whole thing lifts slightly instead. */
a.wordmark:hover img, a.wordmark:focus-visible img { opacity: .82; }
a.wordmark:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 4px; }

/* Where a screen was reached FROM another screen, and said so in the URL —
   see /assets/back-link.js. It sits after the brand and before the screen
   name; on a phone it is the one thing in the bar allowed to shrink. */
.brandbar .back-link {
  font-size: 14px; font-weight: 600; color: var(--muted);
  text-decoration: none; white-space: nowrap;
}
.brandbar .back-link:hover { color: var(--brand-dark); text-decoration: underline; }

.brandbar .screen-name { margin-left: auto; font-size: 14px; font-weight: 600; color: var(--muted); }

/* WHO IS SIGNED IN — added to the brand bar by /assets/session.js on every
   screen. `margin-left:auto` puts it at the right unless a .screen-name has
   already claimed that, in which case it simply follows it. */
.session-bar { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.session-bar .session-who { font-size: 14px; font-weight: 600; color: var(--brand-ink); }
/* A PIN session says so. "Why can I not see customers?" has exactly one answer
   on a truck phone, and it should not be a mystery. */
.session-bar .session-pin {
  font-size: 11px; font-weight: 800; letter-spacing: 0.5px;
  background: var(--brand); color: #fff;
  border-radius: 999px; padding: 2px 8px;
}
h1 { font-size: 22px; margin: 16px 0 4px; color: var(--brand-ink); }
.sub { color: var(--muted); font-size: 14px; margin-bottom: 18px; }
.panel {
  background: var(--panel); border-radius: var(--radius); padding: 20px;
  margin-bottom: 16px; border: 1px solid var(--line); box-shadow: var(--shadow);
}
label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; }
/* Every text-ish input type gets the same treatment, so a form built from
   emails, phones and times looks like one form rather than three.

   ⚠️ `password` WAS MISSING UNTIL LOGIN EXISTED, and the list being explicit is
   why nobody noticed: there had never been a password field in TopGas, so the
   sign-in screen rendered its password box completely unstyled at desktop —
   19px tall, 13px text, no border — while looking fine on a phone, where the
   screen's own mobile block covered it. Found by MEASURING in a real browser,
   not by reading the CSS. If a new input type is ever added, add it here rather
   than styling it inside one screen. */
select, textarea,
input[type=text], input[type=email], input[type=tel], input[type=password],
input[type=time], input[type=date], input[type=number], input[type=search] {
  width: 100%; padding: 14px; font-size: 18px; border-radius: 8px;
  border: 1px solid var(--line); background: #fff; color: var(--text);
  font-family: inherit;
}
select:focus, textarea:focus,
input[type=text]:focus, input[type=email]:focus, input[type=tel]:focus, input[type=password]:focus,
input[type=time]:focus, input[type=date]:focus, input[type=number]:focus, input[type=search]:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgb(var(--brand-rgb) / .15);
}
textarea { resize: vertical; min-height: 80px; line-height: 1.5; }
.row { display: flex; gap: 10px; align-items: flex-end; }
.row > div { flex: 1; }
button {
  padding: 14px 20px; font-size: 16px; font-weight: 600; border: none;
  border-radius: 8px; cursor: pointer; color: #fff; background: var(--brand);
  transition: background .15s, opacity .15s; white-space: nowrap;
}
button:hover { background: var(--brand-dark); }
button:disabled { opacity: .4; cursor: not-allowed; }
button.green { background: var(--ok); }
button.red { background: var(--bad); }
button.ghost { background: #fff; color: var(--brand-ink); border: 1px solid var(--line); }
button.ghost:hover { background: var(--panel2); }
.btn-full { width: 100%; margin-top: 12px; }
.hidden { display: none !important; }
/* ==========================================================================
   PHONES — shared responsive rules
   ==========================================================================
   The office works on a desktop, but drivers, owners and staff away from the
   desk open these screens on a phone. Everything below applies ONLY at narrow
   widths: the desktop layouts above are untouched, and there is no separate
   mobile page to keep in step.
   
   Rules that any screen would want live here so future screens inherit them.
   A screen with its own layout (an order row, a filter bar) keeps its own
   overrides in its own file — but it must still clear the three bars below.

   THE THREE THINGS THAT MATTER ON A PHONE
   ---------------------------------------
   1. 16px MINIMUM on anything you can type into. iOS Safari zooms the page in
      when you focus a smaller control and does not zoom back out, which leaves
      the user scrolled sideways into a form they now have to pan around.
   2. 44x44px MINIMUM on anything you tap. A checkbox drawn at 18px is a real
      target of 18px; missing it repeatedly on a list of orders is how a wrong
      order gets confirmed.
   3. NOTHING WIDER THAN THE SCREEN. A page that scrolls sideways hides the
      right-hand end of every row, and the office cannot tell what it is
      missing.
   ========================================================================== */

/* Helpers any screen can use, defined at every width so they are predictable. */
.mobile-only { display: none; }

/* ---- A RECORD'S OWN NUMBER --------------------------------------------------
   RUN-0012, and the same treatment /orders gives SO-0001: monospaced and heavy,
   so a reference somebody is about to read down a phone can be picked out of a
   line of prose without being read word by word. Monospace also stops the
   digits changing width as they change, which is what makes a column of them
   scannable.

   Shared rather than copied because three run screens print the same element
   and a reference that looks different on each is a reference the office has to
   look twice at. It carries no colour of its own — it inherits whatever the
   line around it is, so it works in a heading and in a muted meta line alike. */
.recno { font-family: ui-monospace, monospace; font-weight: 800; letter-spacing: -.01em; }

@media (max-width: 640px) {
  /* The sign-out button is a real tap target like everything else, and the
     name is trimmed rather than allowed to push the bar into a second line.

     ⚠️ THE TRIMMING ONLY WORKS IF NOTHING ELSE IN THE BAR CAN SHRINK. A flex
     item's default is to shrink below its content, so on a 375px phone the
     brand bar squeezed EVERY item instead of just the name: the wordmark broke
     across two lines as "TopG / as" and the screen name as "Driv / er". The
     name has a max-width and an ellipsis precisely so it is the one thing that
     gives; the rest must hold their size for that to mean anything. */
  .brandbar .wordmark,
  .brandbar .screen-name,
  .session-bar .session-pin,
  .session-bar .session-out { flex: 0 0 auto; white-space: nowrap; }
  .session-bar .session-out { min-height: 44px; font-size: 16px; }
  /* 40vw is 150px of a 375px screen, which does not leave room for the brand,
     the screen name, the PIN badge and a 44px-high Sign out. */
  .session-bar .session-who { max-width: 26vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .brandbar { gap: 8px; }
  .session-bar { gap: 6px; min-width: 0; }

  /* Long values — an email, an address, an order reference — break rather than
     push the page sideways. */
  body { padding: 0 12px 40px; overflow-wrap: anywhere; }

  /* The header is the same on every screen and is pure chrome: on a 667px-tall
     phone it must not cost a fifth of the viewport before any content. */
  .brandbar { padding: 9px 0; margin-bottom: 4px; }
  /* 30px on a phone, from `.brandbar img` below — the same height the bar has
     always given an image. The 22px font-size that used to be here went with
     the text wordmark. */
  /* 44px of TAP without 44px of HEADER. The wordmark is 30px tall here, and
     the comment above is the reason it stays that way: padding grows the hit
     area to 44px and an equal negative margin gives the layout back, so the
     way home is a full-size target and the bar is exactly as tall as it was.
     Measured at 375px, not read. */
  a.wordmark { display: inline-block; padding: 11px 6px; margin: -11px -6px; }
  .brandbar img { height: 30px; }
  h1 { font-size: 20px; margin: 10px 0 2px; }
  .sub { font-size: 15px; margin-bottom: 10px; }
  .panel { padding: 14px; }

  /* 16px, everywhere something can be typed into. A screen with its own
     smaller override must raise it in its own mobile block — this cannot
     reach past a later stylesheet. */
  select, textarea,
  input[type=text], input[type=email], input[type=tel],
  input[type=time], input[type=date], input[type=number], input[type=search] {
    font-size: 16px;
  }

  /* Tap targets. min-height rather than height, so a button with two lines of
     label still grows. */
  button, .btn, a.btn > button { min-height: 44px; }

  /* A checkbox or radio is drawn small and tapped large: the box grows a
     little, and the label beside it becomes part of the target. */
  input[type=checkbox], input[type=radio] { width: 22px; height: 22px; }
  .tap-target { min-height: 44px; min-width: 44px; display: inline-flex;
    align-items: center; justify-content: center; }

  /* Side-by-side form fields stack. */
  .row { flex-direction: column; align-items: stretch; gap: 12px; }

  .mobile-only { display: block; }
  .desktop-only { display: none !important; }
  /* Words that only make sense inline on a desktop — "wanted 17 Aug" reads
     well in a row, and reads twice under a WANTED label. */
  .desktop-only.inline { display: none !important; }

  /* ---- one record per card, label above value -------------------------
     A list built as a wide row of columns turns into an unreadable ragged
     wrap on a phone. Screens mark each field with data-label; here that label
     is printed above the value, so the row reads as a card rather than a pile
     of unexplained fragments. Costs nothing on desktop, where the attribute
     renders nothing at all. */
  [data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 12px; font-weight: 700; letter-spacing: .03em;
    text-transform: uppercase; color: var(--muted);
    margin-bottom: 1px;
  }

  /* ---- the action bar for a selection ---------------------------------
     Sticking it to the TOP is a desktop idea: there, the list and the buttons
     are on screen together. On a phone the buttons scroll off while you are
     still ticking rows, and you have to scroll back up to use them. So while
     something is selected the bar sits at the bottom of the viewport, over the
     list, within thumb reach; with nothing selected it says nothing worth a
     permanent strip of a small screen, so it goes away entirely. */
  .actionbar { display: none; }
  .actionbar.has-selection {
    display: flex;
    position: fixed; top: auto; left: 0; right: 0; bottom: 0; z-index: 40;
    margin: 0; border-radius: 0;
    border: 0; border-top: 1px solid var(--line);
    box-shadow: 0 -2px 12px rgba(16,24,32,.14);
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  }
  /* Compact: the count and the note wrap to their own lines, the buttons share
     one row, and the spacer that pushes them apart on a desktop is dropped.
     A bar three rows tall would cover a third of the list it belongs to. */
  .actionbar.has-selection { flex-wrap: wrap; gap: 8px; }
  .actionbar.has-selection > span:not(.count):not(.why) { display: none; }
  .actionbar.has-selection .count { flex: 1 1 100%; font-size: 14px; }
  .actionbar.has-selection .why { flex: 1 1 100%; font-size: 12px; margin: 0; }
  .actionbar.has-selection button { flex: 1 1 0; min-height: 46px; }

  /* Room to scroll the last row clear of the bar. */
  body.selection-open { padding-bottom: 150px; }
}

/* ===========================================================================
   THE CAMERA BAND — where a scan preview opens.

   Applied by public/assets/scanner.js while a camera is running, and by nothing
   else. It is here rather than in the four screens for the reason everything
   shared is here: four copies is four answers, and the one nobody is looking at
   is the one that goes stale.

   ⚠️ WHY IT IS PINNED AT ALL. The preview used to open wherever the screen's
   markup put it, which on a real page is nowhere near the operator: measured at
   390x844 with the page scrolled to the button they had just pressed, /refill's
   preview opened 4,792px above the fold and /bottles' 4,876px — nought per cent
   of either on screen, camera running, page saying nothing.

   ⚠️ AND AT EVERY WIDTH, not just on a phone. The bench runs this in a
   half-width browser window on a desktop, and a long page folds there too. The
   same lesson as the row-shaped button in §4.0 of CLAUDE.md.

   ⚠️ THE ROOM UNDERNEATH IS RESERVED, and that is not decoration: the box the
   scanned value lands in is on the page the band is now covering. `--scanband-h`
   is measured and re-measured by the module (the preview's height is the
   camera's aspect ratio, not ours to know); the 320px fallback is what a page
   gets where there is no ResizeObserver, and errs towards too much room.
   =========================================================================== */
.scanband {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  margin: 0; max-width: none;
  background: var(--panel);
  border: 0; border-top: 1px solid var(--line);
  box-shadow: 0 -2px 12px rgba(16,24,32,.14);
  padding: 8px 10px calc(8px + env(safe-area-inset-bottom));
}
/* Above the bulk-action bar (40) — one camera is a deliberate act and the bar
   is a standing offer — and below the driver's outbox bar, which is that
   screen's own layer and never shares a page with a band. */
body.scan-open { padding-bottom: calc(var(--scanband-h, 320px) + 16px); }

/* ===========================================================================
   CORRECTING AN ORDER — the editor shared by /orders and /orders/review.

   The markup is built once, in /assets/orders-shared.js, because the same
   correction is made from both screens: from the order record, and from the
   review queue where the fixable problems actually surface. Its look lives
   here for the same reason — two copies of these rules would drift, and the
   screen that got the fix would be whichever somebody was looking at.

   Self-contained on purpose: neither order screen styles a textarea, a date
   field or a number field today, so the editor cannot rely on inheriting one.
   =========================================================================== */
.order-editor {
  background: var(--panel2); border: 1px solid var(--line);
  border-radius: 10px; padding: 13px 15px; margin-top: 10px;
}
.order-editor h4 {
  font-size: 12px; font-weight: 800; letter-spacing: .03em; color: var(--muted);
  text-transform: uppercase; margin-bottom: 9px;
}
.order-editor select, .order-editor input, .order-editor textarea {
  font-family: inherit; font-size: 15px; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); color: var(--text);
}
.order-editor .editline { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
.order-editor .editline select { flex: 1; min-width: 0; }
.order-editor .editline input[type="number"] { width: 92px; }
.order-editor .editfields {
  display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end; margin-top: 12px;
}
.order-editor .editfield label {
  display: block; font-size: 12px; font-weight: 700; color: var(--muted); margin-bottom: 4px;
}
.order-editor .editfield.grow { flex: 1 1 260px; }
.order-editor textarea { width: 100%; min-height: 62px; resize: vertical; }
/* The whole strip toggles the box, so the label is part of the target. */
.order-editor .checkline {
  display: flex; align-items: center; gap: 8px; font-size: 14px; padding-bottom: 9px;
}
.order-editor .checkline input { padding: 0; width: 18px; height: 18px; }
.order-editor .editactions {
  display: flex; gap: 9px; flex-wrap: wrap; align-items: center; margin-top: 13px;
}
.order-editor .editnote { font-size: 12px; color: var(--muted); margin-top: 9px; }
.order-editor .editerr {
  background: var(--bad-bg); color: var(--bad); border-radius: 8px;
  padding: 9px 11px; font-size: 13px; margin-top: 10px;
}

@media (max-width: 640px) {
  /* 16px, or iOS zooms in on focus and never zooms back out. The rule above is
     more specific than the shared one in the block further up this file, so it
     has to be raised here rather than left to inherit. */
  .order-editor select, .order-editor input, .order-editor textarea { font-size: 16px; }
  /* And a real tap target. The shared 44px rule covers buttons only, so a
     product picker came out at 42 — close enough to look right and not close
     enough to hit while holding a phone in one hand. */
  .order-editor select, .order-editor input:not([type="checkbox"]) { min-height: 44px; }
  /* One control per line: a product name and a quantity side by side leave the
     name three characters wide. */
  .order-editor .editline { flex-wrap: wrap; }
  .order-editor .editline select { flex: 1 1 100%; }
  .order-editor .editline input[type="number"] { flex: 1 1 auto; width: auto; }
  .order-editor .editline button { flex: 1 1 auto; }
  .order-editor .editfield { flex: 1 1 100%; }
  .order-editor .checkline { min-height: 44px; padding-bottom: 0; }
  .order-editor .checkline input { width: 22px; height: 22px; }
  .order-editor .editactions button { flex: 1 1 auto; }
}

/* ===========================================================================
   TABS — one long record broken into tabs.

   Built here rather than on the screen that needed it first, because the
   customer record is not the only long scroll in this system: the site record,
   the order record and the fleet screens all get there eventually, and four
   copies of a tab strip would drift the way two copies of a bulk action would.
   The mechanism is /assets/tabs.js; this is what it looks like.

   THE PANELS INSIDE A TAB ARE NOT TOUCHED. A tab hides a WRAPPER around
   whatever panels belong to it, so a panel keeps its own show/hide rules (the
   contacts panel is hidden until a new customer has been saved, and that is a
   different question from which tab it is on) and every input stays in the
   page — which is what lets one Save button still save a field edited on a tab
   you are no longer looking at.
   =========================================================================== */
.tabs {
  display: flex; gap: 2px; align-items: stretch;
  border-bottom: 1px solid var(--line);
  margin: 0 0 16px;
}
/* Buttons, so they are reachable by keyboard for nothing. The theme's button
   is a solid cyan block, which is right for an action and wrong for a tab, so
   every inherited property is stated again rather than left to chance. */
.tabs .tab {
  background: transparent; color: var(--muted);
  font-size: 15px; font-weight: 700; padding: 11px 16px;
  border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px;
  border-radius: 8px 8px 0 0; cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
}
.tabs .tab:hover { background: var(--panel2); color: var(--text); }
.tabs .tab.on {
  background: var(--panel); color: var(--brand-ink);
  border-bottom-color: var(--brand);
}
.tabs .tab:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

/* THE INDICATOR IS THE POINT OF THE BADGE, not the decoration.
   A tab hides what is on it, so an overdue follow-up behind a tab nobody
   clicked is a follow-up nobody does. Grey is a count ("4 contacts"), amber
   and red are "something here needs you". */
.tabs .tab .badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px;
  font-size: 12px; font-weight: 800; line-height: 1;
  background: var(--panel2); color: var(--muted);
}
.tabs .tab .badge.warn { background: var(--warn-bg); color: var(--warn); }
.tabs .tab .badge.bad  { background: var(--bad-bg);  color: var(--bad); }
.tabs .tab.on .badge { background: var(--panel2); }
.tabs .tab.on .badge.warn { background: var(--warn-bg); }
.tabs .tab.on .badge.bad  { background: var(--bad-bg); }

.tabpanel[hidden] { display: none; }

@media (max-width: 640px) {
  /* THE STRIP SCROLLS SIDEWAYS, THE PAGE DOES NOT. Four tabs wrapping into a
     ragged second row is how a tab strip stops reading as a row of choices;
     letting the page itself go wider hides the right-hand end of everything
     else on it. So the overflow is contained here and nowhere else.

     The negative margin bleeds the strip to the screen edges, so the last tab
     scrolls fully out rather than being clipped by the body's padding, and the
     sticky background covers the full width. */
  .tabs {
    position: sticky; top: 0; z-index: 25;
    background: var(--bg);
    overflow-x: auto; flex-wrap: nowrap;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    margin-left: -12px; margin-right: -12px; padding: 0 12px;
    /* The strip carries its background a few pixels up past its own top edge.
       A pinned layer and the content scrolling under it do not land on the
       same device pixel, and the one-pixel hairline that leaves shows the
       panel behind — a white line flickering above the tabs at every scroll
       position, which reads as a rendering fault rather than a design. */
    box-shadow: 0 -6px 0 var(--bg);
  }
  .tabs::-webkit-scrollbar { display: none; }
  /* Stuck to the top on a phone, because the tab you want is otherwise a long
     scroll back up a site list. It costs 44px of a small screen permanently,
     which is the trade: the save bar is already pinned to the bottom for the
     same reason. */
  .tabs .tab {
    flex: 0 0 auto; min-height: 44px; font-size: 15px; padding: 10px 14px;
    white-space: nowrap;
  }
}

/* ===========================================================================
   A TICK AND ITS LABEL — .checkline

   Shared because the same bug has now been found twice, on two screens, by
   the first real browser check either of them had: a screen sets
   `input, select { min-height: 44px }` for the phone rules, that is a FLOOR
   rather than a height, and it beats the 22px the tick box was given. The box
   renders 22 wide by 44 tall — a stretched rectangle — everywhere.

   THE 44px TARGET IS THE LABEL, NOT THE BOX. A tick drawn 44px square reads as
   a button; wrapping it in a label makes the whole line tappable, which is the
   same answer the order rows use. So the label carries the height and the box
   keeps its own size — including a min-height of its own, or the floor above
   wins again.

   A screen adds its own spacing on top; it should not restate these.
   =========================================================================== */
.checkline { display: flex; align-items: center; gap: 8px; }
/* ⚠️ `flex: none` MATTERS AND WAS ADDED AFTER A MEASUREMENT. A flex item may
   shrink by default, so a long label squeezed the tick box from 22px down to
   15px — found on the role editor, where a permission's description is a whole
   sentence. It is invisible on a short label, which is why it survived this
   long, and it makes the smallest target on the screen smaller still. */
.checkline input[type='checkbox'] { width: 18px; height: 18px; padding: 0; flex: none; }

@media (max-width: 640px) {
  .checkline { min-height: 44px; }
  /* The attribute selector out-specifies a screen's bare `input` rule, so this
     holds wherever the label is used, whatever order the files load in. */
  .checkline input[type='checkbox'] { width: 22px; height: 22px; min-height: 22px; }
  /* THE 44px TARGET IS THE LABEL, and it only is one if the label FILLS the
     row. Left as text it was 20px tall inside a 44px row, so the bottom half of
     every tick line did nothing — the row looked like a target and was not. */
  .checkline label { flex: 1; display: flex; align-items: center; min-height: 44px; margin-bottom: 0; }
}

/* ===========================================================================
   ADDRESS SEARCH (LINZ) — public/assets/address-picker.js

   Shared for the same reason the tab strip is: a site and a depot are the same
   question, and a second copy would eventually disagree about what a near miss
   looks like. The markup comes from addressFieldHtml(), so the classes are the
   component's and belong beside it rather than on whichever screen was written
   first.

   The list hangs UNDER the address box rather than beside it: on a phone there
   is nowhere beside it, and one layout that works at both widths beats two
   that each work at one.
   =========================================================================== */
.addr-field { position: relative; }
.addr-results {
  position: absolute; z-index: 30; left: 0; right: 0; top: 100%;
  margin-top: 4px; max-height: 292px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 6px 20px rgba(16,24,32,.16);
}
.addr-opt {
  display: block; width: 100%; text-align: left; background: transparent;
  color: var(--text); font-size: 15px; font-weight: 400; white-space: normal;
  border: 0; border-radius: 0; padding: 11px 14px; cursor: pointer;
}
.addr-opt + .addr-opt { border-top: 1px solid var(--line); }
.addr-opt:hover, .addr-opt:focus { background: var(--panel2); }
.addr-opt .a1 { display: block; font-weight: 600; }
.addr-opt .a2 { display: block; font-size: 13px; color: var(--muted); margin-top: 1px; }
.addr-note { padding: 11px 14px; font-size: 14px; color: var(--muted); }
/* Near misses, not answers — see the note in the picker's search(). */
.addr-warn {
  background: var(--warn-bg); color: var(--warn); font-weight: 600;
  border-bottom: 1px solid var(--warn);
}
.addr-warn b { color: var(--warn); }
/* What the record will say once saved, stated before it is saved. */
.addr-state { font-size: 13px; margin-top: 6px; }
.addr-state.matched   { color: var(--ok); font-weight: 600; }
.addr-state.unmatched { color: var(--warn); font-weight: 600; }
.addr-state .why { color: var(--muted); font-weight: 400; }

@media (max-width: 640px) {
  /* A real target per address, and 16px so picking one does not zoom the form
     in on the way past. */
  .addr-opt { font-size: 16px; min-height: 44px; padding: 12px 14px; }
  .addr-opt .a2, .addr-state, .addr-note { font-size: 14px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   REPORTED MESSAGES — the shared look for `report-at.js`.

   ⚠️ THE MODULE CANNOT RELY ON A CLASS EACH SCREEN HAPPENS TO DEFINE. Ten
   screens define `.msg`, `.msg.bad` and `.msg.ok` separately, in near-identical
   near-copies — two of them with hex literals where the rest use tokens. A
   shared module writing `<div class="msg bad">` onto a screen that never
   defined one would print an unstyled sentence: readable, but not obviously a
   refusal, which is halfway back to the fault this is all about.

   ⚠️ THE NINE SCREEN-LOCAL COPIES ARE LEFT ALONE FOR NOW, deliberately. A
   screen's own <style> is later in the cascade than this file, so nothing moves
   a pixel today. Deleting them belongs with the PR that adopts the module on
   the other eight screens — one change at a time, and this one is about
   /settings.
   ═══════════════════════════════════════════════════════════════════════════ */
.msg { padding: 10px 12px; border-radius: 8px; margin: 10px 0; font-size: 14px; }
.msg.bad  { background: var(--bad-bg);  color: var(--bad); }
.msg.ok   { background: var(--ok-bg);   color: var(--ok); }
.msg.warn { background: var(--warn-bg); color: var(--warn); }

/* The module's own wrapper. `:empty` never happens — it removes the node
   instead — but a stray empty one must not leave a coloured gap under a
   button. */
[data-tg-report]:empty { display: none; }

/* ⚠️ INSIDE A PINNED BAR, NOT AFTER IT. A bar held against the bottom of the
   viewport (`/customers`' `.savebar`, `/orders`' `.actionbar`) has nothing
   after it that anybody can see, so report-at.js puts the message IN the bar —
   and in a flex row it has to take a line of its own above the buttons rather
   than squeezing in beside them. The same shape `.savebar #saveHint` already
   uses on a phone. */
[data-tg-report-pinned] { flex: 1 1 100%; order: -1; }
[data-tg-report-pinned] .msg { margin: 0 0 8px; }

@media (max-width: 640px) {
  /* 16px, for the same reason every control on a phone is 16px: below that iOS
     Safari zooms in and does not come back, and a refusal is exactly when
     somebody is about to touch the form again. */
  .msg { font-size: 16px; }
}
