/* Amarine Driver — V6 shell.
   Dark ground, pastel document cards. The colour carries the meaning here: a driver recognises their
   licence card by its shade before they read a word of it, which matters when half the fleet is
   reading a second language. */

/* HIDDEN MEANS HIDDEN.
   The browser's own rule for [hidden] is `display: none` at the very bottom of the cascade, so ANY
   class that sets a display beats it and the element stays on screen with its `hidden` attribute
   sitting there doing nothing. This file has been patched for that one component at a time four
   times over — .offer-form, .check-row, .drawer, .drawer-scrim — and it happened again the first
   time the chat's report card was looked at: the red "if anybody is hurt, call 999" block was
   shouting at a driver who had ticked nothing. The emergency block earns its volume by appearing
   only when it must, so this is fixed once, here, for everything. */
[hidden] { display: none !important; }

  /* Native controls — the date picker above all — paint themselves from this, not from our tokens.
     Without it a date field renders as a white pill on a black page, which is exactly where the
     licence dates were unreadable. */
:root {
  color-scheme: dark;

  --bg: #0b0b0d;
  --bg-raised: #141417;
  --bg-sunken: #08080a;
  --line: #26262b;
  --text: #f4f4f5;
  /* Was #9a9aa3 (7.05:1). It passed the standard and was still hard to read, because the standard
     does not know this text is also 0.78rem on a phone held at arm length in a cab. 9.56:1, and still
     plainly quieter than --text at 17.89:1 — secondary, not faint. */
  --text-muted: #b4b4bd;
  /* Brighter than --text-muted and dimmer than --text: what a form asks you for. */
  --text-label: #d6d6dd;
  --accent: #f4f4f5;
  --danger: #ff6b6b;
  --warn: #f5c451;
  --ok: #6fcf97;
  /* The colour a faint overlay is mixed FROM. White on a dark ground; on a light one it has to be
     the ink instead, or every hairline border and every soft panel disappears. Introduced with the
     light theme, and the dark values are the literals that were already there — so dark comes out
     exactly as it was. */
  --tint: #fff;
  --info: #4ea3ff;

  --radius-lg: 26px;
  --radius-md: 16px;
  --radius-sm: 10px;
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  /* The top inset was missing, and only the installed app ever felt it. In a browser tab Safari's own
     chrome sits above the page, so nothing overlapped; added to the Home Screen the page owns the
     whole display and `viewport-fit=cover` puts it UNDER the clock, the signal bars and the battery.
     The company name was printed across the time of day.
     Sides too: nothing needs them in portrait, and a phone held sideways has a notch on one edge. */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);

  /* Card themes: a soft gradient plus an ink colour dark enough to read on it. */
  --rose-a: #f7d9df; --rose-b: #fceff1; --rose-ink: #8b5566;
  --lilac-a: #ded9ef; --lilac-b: #f1edfa; --lilac-ink: #5f5588;
  --sky-a: #cfe0ee; --sky-b: #e9f2f8; --sky-ink: #45677f;
  --sand-a: #e8dcc4; --sand-b: #f6efde; --sand-ink: #78683f;
  /* THE MONEY PAGES' ACCENT. The rest of this theme is deliberately monochrome — --accent is
     #f4f4f5 — so expenses and invoices are the only place a colour appears, which is what makes
     'photograph a receipt' and 'raise an invoice' read as the thing to press.
     The stops are a shade deeper than they look: white on #6366f1/#8b5cf6 measures 4.47 and 4.23,
     both under the 4.5 a button label needs. These measure 6.29 and 5.70. */
  --grad-a: #4f46e5;
  --grad-b: #7c3aed;
  --grad-ink: #ffffff;
  --tile-bg: color-mix(in srgb, var(--grad-a) 16%, transparent);
  --tile-ink: #a5b4fc;
  --mint-a: #cfe8dc; --mint-b: #e9f6ef; --mint-ink: #3f6f5a;
  --peach-a: #f6ded0; --peach-b: #fcf0e8; --peach-ink: #8a6046;
}

/* ---- the light theme ----------------------------------------------------------------------------
   Dark is what this app was designed in and what it stays by default; light is a choice a driver
   makes, and a lot of them work in a cab with the sun on the screen.
   The green is the company's own — #00b090 and the darker #008361 from amarinetrans.co.uk — used as
   the accent rather than as a wash: a page that is green all over stops the green meaning anything,
   and on this screen it has to mean "this is the thing to press".
   The ground is not pure white. A very faint green bias sits under it, so the page reads as the same
   product as the dark one rather than as a different app that happens to share a logo.
   The pastel document cards are NOT redefined. They were built to be pale on a dark ground and they
   are just as legible on a light one — a driver recognises their licence by its shade, and that
   should not change because they flipped a switch. */
:root[data-theme="light"] {
  color-scheme: light;

  --bg: #f4f8f6;
  --bg-raised: #ffffff;
  --bg-sunken: #e9efec;
  --line: #d3ddd9;
  --text: #0e1f1a;
  /* The same move in the light theme: 5.54:1 -> 6.82:1. */
  --text-muted: #4a5a52;
  --text-label: #2c3a33;
  --accent: #008361;
  --danger: #b3261e;
  --warn: #9a5b00;
  --ok: #008361;
  --info: #0b6bd6;
  /* Overlays now mix from the ink, not from white, or every hairline and soft panel vanishes. */
  --tint: #0e1f1a;
  /* Pill inks. Small bold text on a tint of its own colour needs to be darker than the same colour
     used as a border or an icon: --warn measured 3.93:1 that way, under the 4.5 that 12px needs. */
  --status-ok: #00614a;
  --status-warn: #7a4700;
  --status-danger: #8f1d17;
  /* Same gradient in both themes — it is a brand mark on a button, not a surface, so it does not
     need to invert. The tile behind an icon does: a 16% wash of indigo reads on either ground,
     but the icon on it has to be dark here and light there. */
  --tile-ink: #4338ca;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}

body { min-height: 100dvh; }

h1, h2, h3 { margin: 0; line-height: 1.15; letter-spacing: -0.02em; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }

.boot {
  min-height: 100dvh;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 14px;
  color: var(--text-muted);
}
/* THE MARK.
   Inlined into the markup rather than loaded through <img>, because an SVG behind <img> is its own
   document: it inherits no colour from the page, and the currentColor version rendered black on
   black — measured, on the first attempt, looking exactly like a missing icon.

   It is drawn with no plate behind it. The old brand was two letters in a green circle, which is
   what you do when the mark is text; this one is a shape, and a shape wants the ground it is on. */
.mark { display: block; width: 100%; height: 100%; color: inherit; }
.brand-mark { width: 30px; height: 30px; flex: 0 0 30px; display: block; color: var(--text); }
/* The product's own name, as opposed to the company a driver happens to work for. Tracked out to
   match the wordmark it sits beside. */
.brand-product .brand-name { letter-spacing: 0.16em; text-transform: uppercase; font-size: 0.82rem; }

/* Two lines in a topbar that is 375px wide and already holds a menu button, a bell and a language
   switch. The product name is the smaller of the two on purpose: a driver knows which app they
   opened, and what they need at a glance is whose work they are looking at. Both ellipsis rather
   than wrap — a topbar that grows a second row shifts every screen underneath it. */
.brand-lines { display: flex; flex-direction: column; min-width: 0; line-height: 1.15; }
/* The language switch moved out of the topbar and into the drawer, which is what bought the brand
   the room to be legible. Laid out the same as a drawer group so it does not read as an afterthought
   bolted to the bottom. */
.drawer-lang .lang-toggle { margin-top: .35rem; }
.brand-product-name {
  font-size: 0.58rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.boot-mark {
  width: 68px; height: 68px;
  display: grid; place-content: center;
  color: var(--text);
}

/* ---- shell ---- */

.shell {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  max-width: 720px;
  margin: 0 auto;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* The inset is part of the bar's own padding rather than a spacer above it, so the blurred
     background reaches up behind the status bar instead of leaving a bare strip. */
  padding: calc(14px + var(--safe-top)) calc(18px + var(--safe-right)) 14px calc(18px + var(--safe-left));
  /* The glass is on a child, not on this element. See "WHY THE BLUR IS NEVER ON THE BAR ITSELF". */
  background: transparent;
}
.topbar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
}
.topbar > * { position: relative; z-index: 1; }

.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.brand-name { font-weight: 600; font-size: 0.95rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.avatar {
  width: 34px; height: 34px;
  flex: 0 0 34px;
  border-radius: 50%;
  display: grid; place-content: center;
  background: #c9f2c7;
  color: #14321a;
  font-size: 0.78rem;
  font-weight: 700;
}

.lang-toggle {
  display: inline-flex;
  padding: 3px;
  border-radius: 999px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
}
.lang-toggle button {
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 5px 11px;
  border-radius: 999px;
  cursor: pointer;
}
.lang-toggle button.active { background: var(--text); color: var(--bg); }

.content {
  flex: 1;
  padding: 6px calc(18px + var(--safe-right)) 132px calc(18px + var(--safe-left));
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.auth-content { justify-content: center; gap: 14px; padding-bottom: 40px; }

.page-head { display: flex; flex-direction: column; gap: 8px; }
.page-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.page-head h1 { font-size: 2rem; font-weight: 600; }
.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.66rem;
  font-weight: 600;
  color: var(--text-muted);
}
.muted { color: var(--text-muted); font-size: 0.88rem; line-height: 1.5; }
.small { font-size: 0.82rem; }

.score { text-align: right; flex: 0 0 auto; }
.score strong { display: block; font-size: 1.6rem; font-weight: 600; }
.score small { color: var(--text-muted); font-size: 0.66rem; }

/* ---- panels and rows ---- */

.panel { display: flex; flex-direction: column; gap: 12px; }
.panel h2 { font-size: 0.95rem; font-weight: 600; color: var(--text-muted); }

.rows { display: flex; flex-direction: column; gap: 8px; }
.row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 14px;
  border-radius: var(--radius-md);
  background: var(--bg-raised);
  border: 1px solid var(--line);
}
.row-quiet { background: transparent; }
.row-warn { border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }
.row-danger { border-color: color-mix(in srgb, var(--danger) 50%, var(--line)); }
.row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.row-body strong { font-size: 0.92rem; font-weight: 600; }
.row-body small { color: var(--text-muted); font-size: 0.78rem; }
.row-chevron { color: var(--text-muted); font-size: 1.2rem; }
.row-badge {
  width: 38px; height: 38px;
  flex: 0 0 38px;
  border-radius: 12px;
  display: grid; place-content: center;
  font-size: 0.68rem;
  font-weight: 700;
}

/* ---- document cards ---- */

.card-stack { display: flex; flex-direction: column; gap: 16px; }

.doc-card {
  position: relative;
  border-radius: var(--radius-lg);
  padding: 22px 22px 18px;
  min-height: 250px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  cursor: pointer;
  color: var(--ink);
  background: linear-gradient(150deg, var(--a) 0%, var(--b) 100%);
  transition: transform 120ms ease;
}
.doc-card:active { transform: scale(0.985); }
.doc-card-staged { outline: 2px solid var(--ok); outline-offset: 3px; }

.theme-rose { --a: var(--rose-a); --b: var(--rose-b); --ink: var(--rose-ink); }
.theme-lilac { --a: var(--lilac-a); --b: var(--lilac-b); --ink: var(--lilac-ink); }
.theme-sky { --a: var(--sky-a); --b: var(--sky-b); --ink: var(--sky-ink); }
.theme-sand { --a: var(--sand-a); --b: var(--sand-b); --ink: var(--sand-ink); }
.theme-mint { --a: var(--mint-a); --b: var(--mint-b); --ink: var(--mint-ink); }
.theme-peach { --a: var(--peach-a); --b: var(--peach-b); --ink: var(--peach-ink); }

.row-badge.theme-rose, .row-badge.theme-lilac, .row-badge.theme-sky,
.row-badge.theme-sand, .row-badge.theme-mint, .row-badge.theme-peach {
  background: var(--a);
  color: var(--ink);
}

.card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.card-head h3 { font-size: 1.28rem; font-weight: 700; }
.card-head p { font-size: 0.875rem; color: #34443d; margin-top: 4px; }
.card-badge {
  flex: 0 0 auto;
  padding: 5px 10px;
  border-radius: 8px;
  background: color-mix(in srgb, #fff 62%, transparent);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.card-drop, .card-current {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  text-align: center;
}
.card-drop strong { font-size: 0.92rem; font-weight: 600; }
.card-drop small, .card-current small { font-size: 0.875rem; color: #34443d; opacity: 1; }
.card-current strong { font-size: 1.05rem; }
.card-drop-icon {
  width: 46px; height: 46px;
  border-radius: 14px;
  display: grid; place-content: center;
  background: color-mix(in srgb, #fff 68%, transparent);
  font-size: 1.15rem;
  margin-bottom: 6px;
}

.card-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.chip {
  padding: 7px 13px;
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 600;
  background: color-mix(in srgb, #fff 62%, transparent);
}
.chip-action { background: transparent; }
.chip-expired, .chip-rejected { background: color-mix(in srgb, var(--danger) 26%, #fff); }
.chip-expiring { background: color-mix(in srgb, var(--warn) 34%, #fff); }
.chip-approved { background: color-mix(in srgb, var(--ok) 30%, #fff); }

/* ---- WHY THE BLUR IS NEVER ON THE BAR ITSELF ------------------------------------------------------
   Three bars are glass: the topbar (sticky), the send bar and the tab bar (both fixed). All three
   came unstuck on an iPhone after the driver opened the file picker to upload a document: on the way
   back they scrolled away with the page, and the tab bar ended up floating in the middle of the
   wallet with cards passing behind it.
   `backdrop-filter` ON a fixed or sticky element is the fault. WebKit gives it its own compositing
   layer, and after the system picker rebuilds the page's layers that layer stops being re-composited
   against the viewport — so it paints where the document was, not where the screen is.
   The blur therefore lives on a `::before` that fills the bar, and the bar itself carries no filter.
   The look is identical; what changes is that the element holding the position no longer holds a
   filter. `validate-glass-bars.mjs` keeps it that way.
   Not reproducible in the tools here — Chromium composites this correctly. Measured for appearance,
   argued for behaviour, and tested on the phone that showed it. */

/* ---- send bar ---- */

.send-bar {
  position: fixed;
  left: 0; right: 0;
  bottom: calc(66px + var(--safe-bottom));
  z-index: 15;
  max-width: 720px;
  margin: 0 auto;
  /* A GRID, not a wrapping flex row. Flex wraps BEFORE it shrinks, so the moment the note under the
     count got longer than a few words the button dropped onto a line of its own and the bar went from
     73px to 147px with nothing uploading. Two columns hold the count and the button on one row
     whatever the note says; the progress spans both. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 6px 14px;
  padding: 12px 18px;
  background: transparent;
  border-top: 1px solid var(--line);
}
/* Its own full-width line under the count and the button, rather than a third thing squeezed beside
   them. This is where the reading is reported now - it used to be written into the upload sheet,
   which has already closed by the time anybody presses the button.
   The two above it must NOT wrap with it: `flex-wrap` alone let the note's own text set its width and
   push the button onto a line of its own, which took the bar from 73px to 126px with nothing
   uploading at all. The note shrinks; the button never does. */
.send-bar-progress { grid-column: 1 / -1; margin-top: 2px; }
.send-bar .progress-sub { margin: 0; color: var(--text-muted); font-size: 0.76rem; line-height: 1.35; }
.send-bar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: color-mix(in srgb, var(--bg-sunken) 94%, transparent);
  backdrop-filter: blur(14px);
}
.send-bar > * { position: relative; z-index: 1; }
.send-bar strong { display: block; font-size: 0.86rem; }
.send-bar small { color: var(--text-muted); font-size: 0.74rem; }

/* ---- bottom nav ---- */

/* Invoices and expenses.
   Both are lists of money, and both are read on a phone by somebody checking a figure they already
   half remember. Amounts are right-aligned in tabular figures so the column can be scanned rather
   than read. */
.invoice-card-head, .expense-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: .75rem;
}
.invoice-card-head small, .expense-head small {
  display: block;
  color: var(--text-muted);
  font-size: .8rem;
  margin-top: .15rem;
}
.invoice-card-amount, .expense-amount {
  text-align: right;
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}
.expense-amount small { display: block; color: var(--text-muted); font-size: .75rem; }
.invoice-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .9rem;
  margin-top: .5rem;
  color: var(--text-muted);
  font-size: .8rem;
}
/* Faded, not struck through: the figures on a cancelled invoice were real when it was raised. */
.invoice-card-cancelled { opacity: .65; }
.invoice-card-note { color: var(--text-muted); font-size: .85rem; margin: .5rem 0 0; }
.invoice-card-balance { font-weight: 600; margin: .5rem 0 0; }
.invoice-credits { margin-top: .6rem; border-top: 1px solid var(--line); padding-top: .5rem; }
/* Sending an invoice by email: a small form that opens on the invoice's own card, under a rule so it
   reads as part of that invoice and not as the next one. */
.invoice-send { display: flex; flex-direction: column; gap: 12px; border-top: 1px solid var(--line); padding-top: 12px; }
.invoice-send-title { margin: 0; }
.invoice-send small { font-weight: 400; }
.invoice-credit {
  display: flex;
  justify-content: space-between;
  gap: .75rem;
  font-size: .85rem;
  padding: .25rem 0;
}
.invoice-credit small { display: block; color: var(--text-muted); font-size: .75rem; }
.invoice-credit-amount { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Kept visually apart from the total beside it, because it is deliberately NOT in that total: a
   draft is a figure nobody has checked. */
.expense-drafts-pending strong { color: var(--warn); }

/* A draft is marked as one wherever it appears. The whole safety argument for reading receipts with
   a model is that nothing counts until a person has looked at it, and that has to be visible. */
.expense-draft { border-left: 3px solid var(--warn); }
.expense-draft-note { color: var(--warn); font-size: .82rem; margin: .5rem 0 0; }
.expense-actions { margin-top: .7rem; }
.expense-capture { margin-bottom: 1rem; }

/* What is stopping an invoice being issued, said in the panel that shows what it will say rather
   than as a toast that disappears while somebody is still reading the figures. */
/* What stays yours after the engagement ends. The "ended" mark rides on the row rather than
   splitting the list in two: somebody driving for three operators wants their contracts in one
   place, with the one that finished marked rather than filed away somewhere else. */
.record-past {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--bg-raised);
  border: 1px solid var(--line);
}

/* The money on an offer row, against the description on the left. Tabular figures so a column of
   them lines up on the decimal point. */
.row-amount {
  margin-left: auto;
  padding-left: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Said once, at the top, rather than only as a line in the blocker list: an invoice nobody can pay
   is not a small omission and it is the state a driver is in until they act on it. */
.bank-missing { border: 1px solid var(--warn); }
.bank-missing p { margin: 0; color: var(--warn); }

.newinv-blockers {
  margin: .75rem 0 .5rem;
  padding-left: 1.2rem;
  color: var(--warn);
  font-size: .85rem;
  display: flex;
  flex-direction: column;
  gap: .3rem;
}

/* What is due on the vehicle about to be taken out.
   Three states, and they are not decorative: an expired annual test means the vehicle does not go
   out, a brake test a fortnight late does not. Painting both amber would teach a driver to ignore
   amber, which is the only reason amber is worth having. */
/* A card of its own, like the rows around it. It had a border and nothing inside it: the words touched the line on
   both sides and the "I have seen the dates" box ran out of it (the owner's own screenshot, 2026-09-19). */
.vehicle-due, .vehicle-unknown {
  padding: 16px 16px 14px;
  border-radius: var(--radius-md);
  background: var(--bg-raised);
  border: 1px solid var(--line);
}
.vehicle-due h2 { font-size: 1.05rem; font-weight: 700; color: var(--text); }
.vehicle-due-blocked { border-color: var(--danger); border-width: 2px; }
.vehicle-stop {
  color: var(--danger);
  font-weight: 700;
  margin: .2rem 0 .6rem;
}
.vehicle-items {
  list-style: none;
  margin: .4rem 0 .8rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .1rem;
}
.vehicle-item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: .75rem;
  padding: .5rem 0;
  border-bottom: 1px solid var(--line);
}
.vehicle-item:last-child { border-bottom: 0; }
.vehicle-item-name { flex: 1 1 auto; }
.vehicle-item-when {
  flex: 0 0 auto;
  text-align: right;
  /* So the dates line up down the column and a late one is visible without reading each row. */
  font-variant-numeric: tabular-nums;
}
.vehicle-item-when small { display: block; font-size: .75rem; color: var(--text-muted); }
.vehicle-overdue .vehicle-item-when,
.vehicle-overdue .vehicle-item-name { color: var(--danger); font-weight: 600; }
.vehicle-overdue .vehicle-item-when small { color: var(--danger); }
.vehicle-urgent .vehicle-item-when { color: var(--warn); font-weight: 600; }
.vehicle-due_soon .vehicle-item-when small { color: var(--warn); }
.vehicle-confirm { margin-top: .5rem; }
.vehicle-due .vehicle-items { margin: 0 0 .4rem; }
/* A plate nobody recognises, or a vehicle with nothing set up. Quiet, because neither is an alarm —
   but present, because showing nothing at all reads as "all clear". */
.vehicle-unknown { border-style: dashed; }

/* Asking the app something.
   Deliberately not styled as a messaging app: no avatars, no typing dots, no send-swoosh. It answers
   from the company's own writing and from the driver's own rows, and dressing that as a conversation
   with an intelligence would oversell what it is and invite the questions it has to refuse. */
.ask-thread {
  display: flex;
  flex-direction: column;
  gap: .7rem;
  margin-bottom: 1rem;
  /* CLAIMS THE FREE SPACE, so the composer sits at the bottom of the screen instead of riding up
     under the last message.

     `.content` is already a flex column filling the viewport, and `.ask-form` is `position: sticky`
     — but sticky only pins something that would otherwise scroll away, and a short conversation has
     nothing to scroll. So with two messages on screen the composer sat directly under them with
     roughly four hundred pixels of dead black between the footnote and the tab bar. Measured, and
     visible in a screenshot the owner sent.

     Growing the thread instead of the gap puts the composer where a composer belongs, and changes
     nothing about the long case: the thread keeps its natural height and the document scrolls, which
     is what every scroll measurement in ask.js depends on. */
  flex: 1 1 auto;
}
.ask-bubble {
  border-radius: var(--radius-md);
  padding: .75rem .9rem;
  max-width: 88%;
  line-height: 1.45;
}
.ask-bubble p { margin: 0; }
.ask-you {
  align-self: flex-end;
  background: var(--accent);
  color: var(--bg);
  font-weight: 600;
}
.ask-app {
  align-self: flex-start;
  background: var(--bg-raised);
  border: 1px solid var(--line);
}
.ask-thinking { color: var(--text-muted); font-style: italic; }

/* Whose words the answer came from. "Your company's procedure says" and "the general advice is" are
   different claims, and a driver who acts on the second thinking it was the first has been misled by
   the interface rather than by the model. */
.ask-sources {
  margin: .6rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
}
.ask-source {
  font-size: .72rem;
  padding: .15rem .5rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--text-muted);
}
.ask-source-company { border-color: var(--accent); color: var(--accent); }
/* A task of the user guide (0354): a link to that page, and it looks like one. */
.ask-source-guide { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.ask-guide-link { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; }

.ask-empty { text-align: left; }
.ask-suggestions { display: flex; flex-direction: column; gap: .5rem; margin-top: .75rem; }
.ask-suggestion {
  text-align: left;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: var(--radius-md);
  padding: .65rem .8rem;
  font: inherit;
  cursor: pointer;
}
.ask-suggestion:hover { border-color: var(--accent); }

/* A global `form { display: flex; flex-direction: column }` sits above this and wins on nothing but
   being about forms in general. Measured: the send button landed on its own row at full width. */
/* Pinned to the bottom of the screen rather than sitting at the end of a long thread, so the box
   somebody is typing into does not travel up the page as the conversation grows. */
.ask-form {
  display: flex;
  flex-direction: row;
  gap: .5rem;
  align-items: stretch;
  position: sticky;
  bottom: 0;
  padding: .5rem 0;
  background: var(--bg);
}

/* While the keyboard is up. The tab bar goes: nothing on it can be reached with the keyboard over it,
   and it was sitting between the composer and the keys. The content's bottom padding — 132px of
   clearance for a bar that is no longer there — goes with it, which is most of what made the page
   feel like it had jumped. */
body.keyboard-up .bottom-nav { display: none; }
body.keyboard-up .ask-footnote { display: none; }
/* Room under the thread for the composer AND the keys beneath it. The end of the DOCUMENT is not the
   end of what can be SEEN: the composer is sticky and lifted by the keyboard's height, so without
   this padding the last few hundred pixels sit behind it. Measured — the answer that had just
   arrived rendered at 648-743 with the composer's top edge at 447, i.e. entirely hidden, on a page
   the app believed it had scrolled to the bottom of.
   This used to read `padding-bottom: 12px`, which removed the tab bar's clearance and then some. */
body.keyboard-up .content { padding-bottom: calc(var(--keyboard-inset, 0px) + 12px); }
/* The visual viewport shrinks under the keyboard but the layout viewport does not, so the sticky
   composer would otherwise pin itself to a bottom edge that is behind the keys. */
body.keyboard-up .ask-form { bottom: var(--keyboard-inset, 0px); }
.ask-form input {
  flex: 1 1 auto;
  min-width: 0;
  /* 16px or iOS zooms the whole page in on focus and the driver loses the thread. */
  font-size: 16px;
}
.ask-form button { flex: 0 0 auto; }
.ask-footnote { margin-top: .6rem; }

/* The camera. Square, the same height as the box beside it, and quiet — it is a second way to ask a
   question, not the main one. */
.ask-camera {
  flex: 0 0 auto;
  width: 2.9rem;
  border: 1px solid var(--line);
  background: var(--bg-sunken);
  border-radius: var(--radius-md);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
}
.ask-camera:disabled { opacity: .5; cursor: default; }

/* A photograph chosen but not yet sent. Above the composer rather than in the thread, because a
   picture sitting among the messages looks exactly like one that has already gone. */
.ask-attached {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem .6rem;
  margin-bottom: .4rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  position: sticky;
  bottom: 3.4rem;
}
body.keyboard-up .ask-attached { bottom: calc(var(--keyboard-inset, 0px) + 3.4rem); }
.ask-attached img { width: 44px; height: 44px; object-fit: cover; border-radius: var(--radius-sm); }
.ask-attached .muted { flex: 1 1 auto; }
.ask-drop {
  flex: 0 0 auto;
  width: 1.9rem; height: 1.9rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--text-muted);
  font-size: 1.1rem; line-height: 1;
  cursor: pointer;
}

/* The photograph inside a sent question. Capped rather than full width: it is context for the
   sentence under it, and a phone photograph at full bleed pushes the words off the screen. */
.ask-shot {
  display: block;
  width: 100%;
  max-height: 220px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  margin-bottom: .55rem;
}

/* What it offers to do.
   Held apart from the answer bubbles on purpose. An answer is words; this is a button that files a
   report or raises an invoice, and it should not look like more conversation. */
.ask-card {
  align-self: stretch;
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  background: var(--bg-raised);
  padding: .85rem .9rem;
  display: flex;
  flex-direction: column;
  gap: .6rem;
}
.ask-card h2 { margin: 0; font-size: 1.05rem; }
.ask-card .rows { margin: 0; }
.ask-card label { margin: 0; }
.ask-card-buttons { margin-top: .2rem; }
/* Once it has been done. No border to press against and nothing left to decide. */
.ask-card-done { border-color: var(--line); }
.ask-card-emergency { font-weight: 700; color: var(--danger); margin: 0; }
.ask-card-hurt { display: flex; flex-direction: column; gap: .5rem; }

/* What to do when it has gone wrong.
   The emergency block is the one thing on this app allowed to shout. It earns that by appearing
   only when a driver has said somebody is hurt, something is on fire, or fuel is running towards a
   drain — and by never appearing otherwise, which is what keeps it believed. */
.emergency-panel {
  border: 2px solid var(--danger);
  background: var(--bg-raised);
  /* Its own stacking so the border is never clipped by the panel above it. */
  position: relative;
}
/* .panel h2 is (0,1,1) and sets a muted grey at 0.95rem. A bare .emergency-headline is (0,1,0) and
   loses to it whatever the order, so the headline came out grey — the one word on this screen that
   has to read as an alarm. Qualified to match, the same way .alert-panel h2 already does. */
.emergency-panel h2.emergency-headline {
  color: var(--danger);
  margin: 0 0 .4rem;
  font-size: 1.25rem;
  line-height: 1.25;
  font-weight: 700;
}
/* Filled, unlike every other destructive control in this app, which are outlined. The rule there is
   that a solid red block is a button people press to make it go away — which is exactly what is
   wanted here and nowhere else. */
.button-emergency {
  background: var(--danger);
  color: #fff;
  border-color: var(--danger);
  font-size: 1.1rem;
  padding-block: .95rem;
}
.button-emergency strong { font-weight: 800; letter-spacing: .02em; }
.call-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  text-decoration: none;
}
.emergency-more { margin-top: .75rem; }
.emergency-more summary { cursor: pointer; color: var(--text-muted); font-size: .9rem; }
.emergency-more p { font-size: .9rem; margin: .5rem 0 0; }

/* The procedure. Numbered because the order is the only formatting that helps somebody who is not
   calm, and spaced because it is read on a phone in the rain. */
.procedure-steps {
  margin: .5rem 0 .9rem;
  padding-left: 1.35rem;
  display: flex;
  flex-direction: column;
  gap: .55rem;
}
.procedure-steps li { line-height: 1.45; }
.procedure-steps li::marker { font-weight: 700; color: var(--accent); }

/* Panels the app scrolls to on the driver's behalf. The topbar is sticky, so an element aligned to
   the top of the viewport lands underneath it without this. */
.scroll-target { scroll-margin-top: 88px; }

.bottom-nav {
  position: fixed;
  left: 0; right: 0;
  bottom: 0;
  z-index: 16;
  max-width: 720px;
  margin: 0 auto;
  display: grid;
  /* However many there are, spread evenly. This was `repeat(5, 1fr)`, which is fine while the bar
     always holds five and leaves a bare column the moment it does not — which is what a driver
     between contracts saw, the icons bunched left with a gap where Training used to be. A bar should
     not have to be told how many things are on it. */
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  background: transparent;
  border-top: 1px solid var(--line);
  padding: 0 var(--safe-right) var(--safe-bottom) var(--safe-left);
}
.bottom-nav::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: color-mix(in srgb, var(--bg) 96%, transparent);
  backdrop-filter: blur(14px);
}
.bottom-nav > a { position: relative; z-index: 1; }
.bottom-nav a {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 13px 4px 11px;
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 500;
}
/* The active tab used to differ only in text colour, which on a dark bar is a small difference seen
   at arm's length in a cab. It now sits on its own lit panel, with a bar above it — three signals
   instead of one, and none of them relying on noticing a shade. */
.bottom-nav a.active {
  color: var(--text);
  font-weight: 600;
}
/* The lit panel is a pseudo-element at z-index 0 with the icon and label lifted above it, rather than
   a negative z-index — the bar is a stacking context (fixed, with a z-index of its own), so anything
   sent behind would disappear under the bar's own glass instead of sitting under the icon. */
.bottom-nav a > * { position: relative; z-index: 1; }
.bottom-nav a.active::before {
  content: "";
  position: absolute;
  inset: 6px 8px 6px;
  border-radius: 16px;
  background: color-mix(in srgb, var(--text) 11%, transparent);
  z-index: 0;
}
.bottom-nav a.active::after {
  content: "";
  position: absolute;
  top: 0;
  width: 30px;
  height: 3px;
  border-radius: 0 0 4px 4px;
  background: var(--text);
}
.bottom-nav span { font-size: 1.05rem; }
/* <small> shrinks itself by its own default, so the label ended up at 10px however large the link's
   font-size was set. Stated outright instead. */
.bottom-nav small { font-size: 0.72rem; line-height: 1.15; }

/* ---- buttons and forms ---- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 13px 20px;
  border-radius: 999px;
  border: 1px solid transparent;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
}
.button-primary { background: var(--text); color: var(--bg); }
.button-secondary { background: var(--bg-raised); color: var(--text); border-color: var(--line); }
.button-ghost { background: transparent; color: var(--text-muted); }
/* The one button on this app that destroys something. Outlined rather than filled: a solid red block
   in the middle of a screen is a button people press to make it go away, and this one spends an
   invoice number whether or not they meant it. */
.button-danger { background: transparent; color: var(--danger); border-color: var(--danger); }
.button-danger:active { background: color-mix(in srgb, var(--danger) 12%, transparent); }
.button:disabled { opacity: 0.4; cursor: not-allowed; }
.full-width { width: 100%; }

form { display: flex; flex-direction: column; gap: 14px; }
/* A FIELD LABEL IS NOT SECONDARY TEXT. It is the question being asked — "Legal company name" is what
   you have to answer, not a caption on something else. It was 0.82rem in --text-muted, the same
   treatment as an explanatory aside, on a form of thirteen required fields where the operator could not
   read which one he was on. Now --text-label: 13.60:1 against the dark ground and 0.88rem, still
   quieter than a heading and no longer competing with the hint text underneath it. */
label { display: flex; flex-direction: column; gap: 6px; font-size: 0.88rem; color: var(--text-label); font-weight: 500; }
/* Every control a driver types into.
   The list used to name each type, and the comment above it warned that a field left out would fall
   back to the browser default — then four fields were added with no `type` at all, which the list
   could not match either. A bare `input` selector cannot be forgotten. Buttons, checkboxes and file
   pickers are excluded by name because they are not things anybody types into.
   The 16px matters most: below it, iOS zooms the whole page the moment a field takes focus, which is
   what a driver saw when the share sheet opened. */
input:not([type="button"]):not([type="submit"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]),
textarea {
  padding: 13px 14px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--bg-raised);
  color: var(--text);
  font: inherit;
  font-size: 1rem; /* 16px keeps iOS from zooming the viewport on focus */
  width: 100%;
  box-sizing: border-box;
}
textarea { resize: vertical; min-height: 76px; }
/* Left alone, a date input centres its value and dims the calendar glyph into the background. */
input[type="date"] { text-align: left; }
input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1); opacity: 0.55; }
input:focus, textarea:focus { outline: none; border-color: var(--text); }
.form-error { color: var(--danger); font-size: 0.84rem; }
.password-field { display: flex; flex-direction: column; gap: 5px; }
.password-control { display: flex; gap: 8px; align-items: center; }
.password-control input { min-width: 0; flex: 1; }
.password-toggle { min-width: 70px; min-height: 44px; padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-raised); color: var(--text); font: inherit; font-size: .85rem; cursor: pointer; }
.auth-recovery { min-height: 44px; display: inline-flex; align-items: center; }
.later-reminders summary { min-height: 44px; display: list-item; padding: 12px 0; cursor: pointer; font-weight: 600; }
.later-reminders > p { margin: 4px 0 10px; }
.check-note-label { display: flex; flex-direction: column; gap: 6px; }
.result-btn { min-height: 44px; }

/* ---- states ---- */

.loading { display: grid; place-items: center; gap: 12px; padding: 48px 0; color: var(--text-muted); }
.spinner {
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 2px solid var(--line);
  border-top-color: var(--text);
  animation: spin 700ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.empty {
  padding: 24px 18px;
  border-radius: var(--radius-md);
  border: 1px dashed var(--line);
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.empty strong { font-size: 0.92rem; }
.empty p { color: var(--text-muted); font-size: 0.82rem; }
.empty-error { border-color: color-mix(in srgb, var(--danger) 50%, var(--line)); }

/* ---- sheet ---- */

.sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(0, 0, 0, 0.62);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.sheet {
  width: 100%;
  max-width: 720px;
  background: var(--bg-raised);
  border-radius: 24px 24px 0 0;
  border-top: 1px solid var(--line);
  padding: 20px 18px calc(24px + var(--safe-bottom));
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-height: 86dvh;
  overflow-y: auto;
}
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.sheet-head h3 { font-size: 1.05rem; font-weight: 600; }

.slot-list { display: flex; flex-direction: column; gap: 10px; }
.slot {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  border-radius: var(--radius-md);
  border: 1px dashed var(--line);
  cursor: pointer;
  color: var(--text);
}
.slot-chosen { border-style: solid; border-color: color-mix(in srgb, var(--ok) 55%, var(--line)); }
.slot-icon {
  width: 36px; height: 36px;
  flex: 0 0 36px;
  border-radius: 11px;
  display: grid; place-content: center;
  background: var(--bg-sunken);
}
.slot-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.slot-body strong { font-size: 0.9rem; font-weight: 600; }
.slot-body small { color: var(--text-muted); font-size: 0.76rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.progress { display: flex; flex-direction: column; gap: 8px; }
/* `display: flex` beats the `hidden` attribute, and an empty box still eats its gap. Without this the
   send bar carries a blank strip whenever nothing is being uploaded. */
.progress[hidden] { display: none; }
.progress-copy { display: flex; justify-content: space-between; font-size: 0.8rem; }
.progress-track { height: 5px; border-radius: 999px; background: var(--bg-sunken); overflow: hidden; }
.progress-track span { display: block; height: 100%; background: var(--ok); transition: width 200ms ease; }

/* Reading the document takes a minute or two on a two-minute schedule, so there is no honest
   percentage to show — a bar that pretends to know how far along it is would be a made-up number. */
.progress-track-indeterminate { position: relative; }
.progress-track-indeterminate span {
  position: absolute;
  width: 38%;
  transition: none;
  animation: progress-sweep 1.5s ease-in-out infinite;
}
@keyframes progress-sweep {
  0% { left: -38%; }
  100% { left: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .progress-track-indeterminate span { animation: none; left: 0; width: 100%; opacity: 0.5; }
}
.progress-sub { margin: 0; color: var(--text-muted); font-size: 0.78rem; line-height: 1.5; }

/* The wallet card for a document waiting to be checked. It has to read as a thing to press: the
   driver has no other way of knowing the document stopped here rather than reaching the office. */
.card-drop-needs-you .card-drop-icon { background: #1a5f36; color: #fff; }
.card-drop-needs-you .card-drop-icon svg { width: 22px; height: 22px; }
.card-drop-cta {
  margin-top: 8px; padding: 7px 16px; border-radius: 999px;
  background: #1a5f36; color: #fff; font-size: 0.82rem; font-weight: 700;
}

/* ---- something is waiting on the DRIVER ----
   Above the greeting, coloured like an action rather than a warning: nothing is wrong, the driver
   simply has to look at it before anyone else can. */
.needs-you {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 14px 14px 16px;
  margin-bottom: 4px;
  border-radius: var(--radius-lg, 18px);
  background: linear-gradient(135deg, #1a5f36, #237045);
  color: #fff;
  text-decoration: none;
  box-shadow: 0 6px 18px color-mix(in srgb, #16351f 45%, transparent);
}
.needs-you-glyph { display: grid; place-content: center; width: 38px; height: 38px; flex: none;
  border-radius: 12px; background: color-mix(in srgb, #fff 20%, transparent); }
.needs-you-glyph svg { width: 22px; height: 22px; }
.needs-you-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.needs-you-body strong { font-size: 1rem; line-height: 1.25; }
.needs-you-body small { font-size: 0.78rem; line-height: 1.45; color: #eaf4ee; }
.needs-you-cta {
  flex: none; align-self: center;
  padding: 8px 14px; border-radius: 999px;
  background: #fff; color: #14512c;
  font-size: 0.82rem; font-weight: 700;
}

/* Steps a driver has to follow with the phone in their hand. Numbered because the order matters and
   because the first one is a button they have to find. */
.how-to { margin: 10px 0 2px; padding-left: 20px; display: flex; flex-direction: column; gap: 7px; }
.how-to li { font-size: 0.88rem; line-height: 1.45; }
/* The raw flags, for reading out loud down a phone line. Not decoration — deliberately plain. */
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.7rem; letter-spacing: -0.01em; }

/* A filed check, read back. Every line is an answer that cannot be changed, so nothing here looks
   like a control. */
.history-items { display: flex; flex-direction: column; gap: 2px; }
.history-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: 10px;
}
.history-item + .history-item { border-top: 1px solid color-mix(in srgb, var(--tint) 9%, transparent); border-radius: 0; }
.history-item-body { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.history-item-body strong { font-weight: 500; font-size: 0.9rem; }
.history-item-body small { color: var(--text-muted); font-size: 0.8rem; }
/* (0401) What the office did about a fault he reported. Closed is quiet; still open is not, because a
   fault still on the lorry is the one he needs to see before he drives it. */
.history-answer { font-style: italic; }
.history-answer-closed { color: var(--text-muted); }
.history-answer-open { color: var(--warn, #b7791f); font-style: normal; font-weight: 600; }
/* A defect is the one line somebody scrolling this is looking for. */
.history-item-defect { background: color-mix(in srgb, var(--danger) 14%, transparent); }
.history-item-defect .history-item-body strong { font-weight: 700; }
/* The note on a defect is the sentence somebody is actually reading, and muted ink on the red tint
   came out at 4.38 on the light ground. It gets full ink on both themes. */
.history-item-defect .history-item-body small { color: var(--text); }

.fact-list { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 0; }
.fact-list dt { color: var(--text-muted); font-size: 0.74rem; text-transform: uppercase; letter-spacing: .04em; }
.fact-list dd { margin: 2px 0 0; font-size: 0.95rem; font-weight: 600; }

/* The fuel row on the home screen: full width like the walkaround, deliberately shorter. It is the
   second most common thing a driver does, and it should read that way — bigger than the pair below
   it, smaller than the check that must happen before the wheels turn. */
.quick-secondary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 15px 16px;
  border-radius: var(--radius-lg, 18px);
  background: var(--bg-raised);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tint) 12%, transparent);
  color: inherit;
  text-decoration: none;
}
.quick-secondary strong { flex: 1; font-size: 1rem; }
.quick-secondary-glyph {
  display: grid; place-content: center; width: 40px; height: 40px; flex: none;
  border-radius: 12px;
  background: color-mix(in srgb, var(--warn) 22%, transparent);
  color: var(--warn);
}
.quick-secondary-glyph svg { width: 23px; height: 23px; }

/* A note under a field, saying where the number comes from. Not a placeholder: a placeholder
   disappears the moment somebody starts typing, which is when they need it. */
.field-hint { display: block; margin-top: 4px; color: var(--text-muted); font-size: 0.78rem; line-height: 1.4; }

/* Two numbers that belong side by side: diesel and AdBlue go in at the same stop. */
.field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.field-pair label { min-width: 0; }

/* A row that is a record rather than a link — no chevron, nothing to press. */
.row-static { cursor: default; }
.row-when { color: var(--text-muted); font-size: 0.76rem; white-space: nowrap; align-self: center; }

/* A question the driver answers with a tick rather than a tap-one-of-three: the trailer option, and
   the declaration. Laid out so the whole row is the target, not just the box. */
.switch-row { display: flex; align-items: flex-start; gap: 11px; padding: 6px 0; cursor: pointer; }
.switch-row input { margin-top: 3px; width: 20px; height: 20px; flex: none; }
.switch-row span { display: flex; flex-direction: column; gap: 3px; }
.switch-row strong { font-size: 0.95rem; }
.switch-row small { color: var(--text-muted); font-size: 0.82rem; line-height: 1.5; }

/* The declaration is the point of the exercise, so it does not look like a footnote. */
.declaration-panel { border: 1px solid color-mix(in srgb, var(--ok, #4caf7d) 45%, transparent); }
.declaration-panel .switch-row small { color: var(--text); }

/* Which unit a section is about, when a check covers both. */
.scope-tag {
  margin-left: 7px; padding: 2px 8px; border-radius: 999px;
  background: color-mix(in srgb, var(--tint) 16%, transparent);
  font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  vertical-align: middle;
}

/* Choosing the theme. Two buttons showing what they do rather than a switch labelled "dark mode",
   because the swatch is readable by somebody who is not reading. */
.theme-choice { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px; }
.theme-choice button {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; min-height: 52px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--bg-raised);
  color: var(--text);
  font: inherit; font-size: 0.95rem; font-weight: 600;
  cursor: pointer;
}
.theme-choice button.active { border-color: var(--ok); box-shadow: inset 0 0 0 1px var(--ok); }
.theme-swatch { width: 26px; height: 26px; border-radius: 8px; flex: none; border: 1px solid var(--line); }
.theme-swatch-dark { background: linear-gradient(135deg, #0b0b0d 50%, #141417 50%); }
.theme-swatch-light { background: linear-gradient(135deg, #f4f8f6 50%, #00b090 50%); }

/* ---- what the company has told the driver ---- */

.link-button {
  position: relative;
  border: 0; background: none; padding: 10px 2px;
  color: var(--info); font: inherit; font-size: 0.82rem; font-weight: 650;
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.link-button:disabled { opacity: 0.5; cursor: progress; }
/* The text stays the size it should be; the reachable area is grown past it. */
.link-button::after { content: ""; position: absolute; inset: -8px -6px; }

.notif-list { display: flex; flex-direction: column; gap: 8px; }
.notif-row {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 13px 12px 13px 10px;
  border-radius: var(--radius-md, 14px);
  background: var(--bg-raised);
  color: inherit; text-decoration: none;
}
.notif-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.notif-body strong { font-size: 0.95rem; line-height: 1.3; }
.notif-body small { color: var(--text-muted); font-size: 0.82rem; line-height: 1.4; }
.notif-when { color: var(--text-muted); font-size: 0.72rem; margin-top: 2px; }

/* The dot is the only thing separating read from unread, so it carries a colour that means something:
   how close the thing it is about actually is. */
.notif-dot { width: 9px; height: 9px; margin-top: 7px; border-radius: 999px; flex: none; background: transparent; }
.notif-unread .notif-dot { background: var(--text-muted); }
.notif-unread.notif-urgent .notif-dot { background: var(--danger); }
.notif-unread.notif-warn .notif-dot { background: var(--warn); }
.notif-unread.notif-info .notif-dot { background: var(--info); }
.notif-unread.notif-good .notif-dot { background: var(--ok); }
.notif-unread .notif-body strong { font-weight: 700; }
.notif-row:not(.notif-unread) { background: var(--bg-sunken); }
.notif-row:not(.notif-unread) .notif-body strong { font-weight: 500; }

/* Answered is a third state, and it is not "read". Something happened here — a signature, an answer
   to an offer — so the row reads as settled rather than as one more thing scrolled past. The dot keeps
   a colour for that reason; a blank dot would say it was merely seen. --status-ok is the darkened
   light-theme text colour, because --ok alone measured under 4.5:1 on the sunken background. */
.notif-done .notif-dot { background: var(--status-ok, var(--ok)); }
.notif-done .notif-when { color: var(--status-ok, var(--ok)); }

/* The bell, and the count on it. */
.topbar-actions { display: flex; align-items: center; gap: 6px; }
.topbar-bell { position: relative; display: grid; place-content: center; width: 44px; height: 44px;
  border-radius: 12px; color: inherit; text-decoration: none; }
.topbar-bell .icon { width: 22px; height: 22px; }
.topbar-bell .nav-badge { top: 2px; left: auto; right: 2px; }

/* The same fact, carried on the tab bar so it is visible from any screen. */
.bottom-nav a.nav-needs-you { color: var(--text); }
/* Specific enough to beat `.bottom-nav a > *` and `.bottom-nav span`, which otherwise put this back
   in the flow underneath the label and sized it like body text. */
.bottom-nav a > span.nav-badge {
  position: absolute;
  top: 7px;
  left: calc(50% + 6px);
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: #b3261e;
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  box-shadow: 0 0 0 2px var(--bg, #101216);
}

/* ---- no signal ----

   One line above everything, including the sticky top bar, because it is about the whole app and not
   about the screen under it. It PUSHES the page rather than covering it: a bar over the top bar would
   sit exactly where a thumb reaches for the menu.

   --warn rather than --danger on purpose. No signal in a yard is the ordinary state of a lorry driver's
   day, not a fault; red is kept for something that went wrong. */
.offline-bar {
  position: sticky;
  top: 0;
  z-index: 30;
  padding: calc(8px + var(--safe-top)) 16px 8px;
  background: color-mix(in srgb, var(--warn) 22%, var(--bg));
  border-bottom: 1px solid color-mix(in srgb, var(--warn) 45%, var(--line));
  color: var(--ink);
  font-size: 13px;
  line-height: 1.35;
  text-align: center;
}
.offline-bar[hidden] { display: none; }
/* The top bar is sticky too, and both sticking to 0 would overlap the moment the page scrolls. */
.offline-bar:not([hidden]) ~ * .topbar { top: 0; }

/* ---- toast ---- */

.toast-root { position: fixed; left: 0; right: 0; bottom: calc(140px + var(--safe-bottom)); z-index: 60; display: flex; justify-content: center; pointer-events: none; }
.toast {
  max-width: 420px;
  margin: 0 18px;
  padding: 12px 16px;
  border-radius: var(--radius-md);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  font-size: 0.85rem;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
}
.toast-error { border-color: color-mix(in srgb, var(--danger) 55%, var(--line)); }
.toast-success { border-color: color-mix(in srgb, var(--ok) 55%, var(--line)); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ---- training ---- */

.button-small { padding: 8px 14px; font-size: 0.8rem; }
.row-plain { }

.chip-assigned { background: var(--bg-sunken); color: var(--text-muted); }
.chip-progress { background: color-mix(in srgb, var(--warn) 26%, var(--bg-sunken)); color: var(--text); }
.chip-passed { background: color-mix(in srgb, var(--ok) 26%, var(--bg-sunken)); color: var(--text); }
.chip-failed, .chip-retraining { background: color-mix(in srgb, var(--danger) 26%, var(--bg-sunken)); color: var(--text); }
.chip-waived { background: var(--bg-sunken); color: var(--text-muted); }

/* Inside a row the chips sit on the dark ground, not on a pastel card, so they need their own
   contrast rather than the translucent white the document cards use. */
.rows .chip { flex: 0 0 auto; }

.quiz-question { border: 1px solid var(--line); border-radius: var(--radius-md); padding: 16px; }
.quiz-question legend { padding: 0 6px; font-size: 0.92rem; line-height: 1.4; }

.option {
  flex-direction: row;
  align-items: center;
  gap: 10px;
  padding: 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  color: var(--text);
  font-size: 0.9rem;
  cursor: pointer;
}
.option:has(input:checked) { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent); }
.option input { accent-color: var(--ok); width: 18px; height: 18px; flex: 0 0 18px; }

/* ---- defect form ---- */

select, textarea {
  padding: 13px 14px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--bg-raised);
  color: var(--text);
  font: inherit;
  font-size: 1rem;
  resize: vertical;
}
.panel form, .panel > label { gap: 14px; }
.check-list { display: flex; flex-direction: column; gap: 8px; }
.check-list .option input { accent-color: var(--warn); }
.row-disabled { opacity: 0.45; }

/* ---- evidence photo picker ---- */

.mobile-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.thumb-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.thumb {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
}
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb-remove {
  position: absolute; top: 5px; right: 5px;
  width: 26px; height: 26px;
  border: 0; border-radius: 50%;
  background: rgba(0,0,0,0.62); color: #fff;
  font: inherit; font-size: 0.72rem; line-height: 1; cursor: pointer;
}
.thumb-size {
  position: absolute; left: 5px; bottom: 5px;
  padding: 2px 6px; border-radius: 6px;
  background: rgba(0,0,0,0.55); color: #fff; font-size: 0.62rem;
}
.thumb-add {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; cursor: pointer; border-style: dashed; color: var(--text-muted);
}
.thumb-add span { font-size: 1.4rem; }
.thumb-add small { font-size: 0.7rem; }

/* ---- walkaround checklist ---- */

.check-item {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 13px 14px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--bg-raised);
}
.check-item-defect { border-color: color-mix(in srgb, var(--danger) 55%, var(--line)); }
.check-item-label { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
.check-item-label strong { font-size: 0.92rem; font-weight: 600; }
.check-item-label small { flex: 1 0 100%; color: var(--text-muted); font-size: 0.76rem; }
.tag-critical {
  padding: 2px 7px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--danger) 24%, transparent);
  color: var(--danger);
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Three equal targets: one tap per item, and nothing pre-selected. */
.check-item-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.result-btn {
  padding: 11px 6px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}
.result-ok.active { background: color-mix(in srgb, var(--ok) 26%, transparent); border-color: var(--ok); color: var(--text); }
.result-defect.active { background: color-mix(in srgb, var(--danger) 26%, transparent); border-color: var(--danger); color: var(--text); }
.result-not_applicable.active { background: var(--bg-sunken); border-color: var(--text-muted); color: var(--text); }

.check-item-note {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid color-mix(in srgb, var(--danger) 40%, var(--line));
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
  font-size: 1rem;
}

/* ---- wallet thumbnails ---- */

/* The slot is always rendered; the image is set on it after a signed URL arrives. Empty it is simply
   a soft panel, so a thumbnail that never loads degrades to the plain card rather than a hole. */
/* The photograph of the document.
   It used to be a single `cover` background, which cropped a landscape licence to a band across its
   middle — the driver saw half a card and never the whole thing. Now the picture is CONTAINED, so
   whatever shape the document is it arrives entire, and a blurred copy of it fills the space behind
   so the card still reads as full rather than as a photo floating on a gap. */
/* An upload that has not gone anywhere yet. It must not look like the calm "in review" state — it is
   waiting on the driver, and the whole fault was that it looked like it was waiting on the office. */
.card-drop-unfinished .card-drop-icon {
  background: color-mix(in srgb, #fff 82%, transparent);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, #b3261e 55%, transparent);
}
.card-drop-unfinished strong { color: #7a1d16; }

.card-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  margin-bottom: 10px;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: color-mix(in srgb, #fff 34%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #000 8%, transparent);
}
.card-thumb-back,
.card-thumb-face {
  position: absolute;
  inset: 0;
  background-image: var(--thumb, none);
  background-repeat: no-repeat;
  background-position: center;
}
.card-thumb-back {
  background-size: cover;
  filter: blur(14px) saturate(1.15);
  /* Scaled up so the blur does not pull the edge of the picture inside the frame. */
  transform: scale(1.25);
  opacity: 0.55;
}
.card-thumb-face { background-size: contain; }

/* Turning the document over. Small, on the picture, out of the way of the document itself. */
.card-thumb-flip {
  position: absolute;
  right: 8px;
  bottom: 8px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 7px 12px;
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-size: 0.76rem;
  font-weight: 650;
  letter-spacing: 0.01em;
  color: #14161c;
  background: color-mix(in srgb, #fff 88%, transparent);
  box-shadow: 0 1px 6px color-mix(in srgb, #000 28%, transparent);
  cursor: pointer;
}
/* The pill stays small so it does not sit on top of the document, but a thumb needs something
   larger than a 26px target. The reachable area is grown past the pill instead — it stops exactly at
   the edge of the picture, which is where the button already sits. */
.card-thumb-flip::after { content: ""; position: absolute; inset: -8px; border-radius: 999px; }
.card-thumb-flip:disabled { opacity: 0.6; cursor: progress; }
.card-thumb-flip:focus-visible { outline: 2px solid #14161c; outline-offset: 2px; }

/* Until the signed URL arrives there is no picture, so the blurred layer would smear the empty
   placeholder colour across the frame. Both layers stay hidden until there is something to show. */
.card-thumb:not(.card-thumb-loaded) .card-thumb-back,
.card-thumb:not(.card-thumb-loaded) .card-thumb-face { display: none; }
.card-current { justify-content: flex-start; width: 100%; }
.card-current strong { font-size: 1rem; }

/* ---- required fields and non-image attachments ---- */

.field-invalid { border-color: var(--danger); }
.field-error { color: var(--danger); font-size: 0.76rem; }
.thumb-doc {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 100%; height: 100%; gap: 6px; color: var(--text-muted); text-align: center; padding: 8px;
}
.thumb-doc span { font-size: 1.6rem; }
.thumb-doc small { font-size: 0.62rem; word-break: break-all; }

/* ---- document confirmation ---- */

.chip-confirm { background: color-mix(in srgb, var(--warn) 40%, #fff); }

/* The photograph and the transcription have to be readable together. On a phone they stack, which is
   fine — what matters is that the picture is above the fields, not buried under them. */
.confirm-photos { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
.confirm-photo {
  flex: 0 0 auto;
  width: 72%;
  max-width: 320px;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--bg-sunken);
}
.confirm-photo img { width: 100%; display: block; }

.alert-panel { border: 1px solid color-mix(in srgb, var(--danger) 55%, var(--line)); border-radius: var(--radius-md); padding: 14px; }
.alert-panel h2 { color: var(--danger); }
.alert-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; font-size: 0.85rem; }
.alert-list.muted { color: var(--text-muted); }

.confirm-field { gap: 6px; }
.confirm-field > span { color: var(--text-muted); font-size: 0.8rem; }

/* A flagged field is one a warning named. The mark has to be visible without making the value harder
   to read than an unflagged one — a tinted box that swallows its own text is worse than no mark at
   all, which is how the two swapped dates ended up as the least legible things on the screen. */
.confirm-field-flagged > span { color: var(--danger); }
.confirm-field-flagged input {
  border-color: var(--danger);
  border-width: 2px;
  background: var(--bg-raised);
  color: var(--text);
}
.field-hint { color: var(--warn); font-size: 0.74rem; }

/* ---- drawn icons ---- */

/* The bottom bar previously used typographic glyphs, which every phone renders at its own size and
   weight. A drawn icon is the same on all of them, and it inherits the text colour so the active tab
   reads as active without a second rule. */
.icon { width: 22px; height: 22px; display: block; }
.bottom-nav .icon { width: 26px; height: 26px; margin: 0 auto; }
.bottom-nav a.active .icon { stroke-width: 2.1; }

/* ---- dashboard quick actions ---- */

/* Anchored low rather than sitting under the header. A phone is held one-handed and the top third of
   the screen is the part a thumb cannot reach — these are the things a driver taps every shift, so
   they belong where the hand already is. `margin-top: auto` inside the column means they stay there
   whether or not anything is being flagged above them. */
.quick-actions {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* The walkaround is not one of three equals. It is the one that is a legal obligation before the
   wheels turn and the one that happens every single day, so it gets the width and the only colour on
   the screen — on a black page a pastel panel is not decoration, it is the thing your eye lands on
   first. */
.quick-primary {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px 18px;
  border-radius: var(--radius-md);
  background: linear-gradient(140deg, var(--mint-a) 0%, var(--mint-b) 100%);
  /* A shade darker than the card ink used elsewhere: that one is measured against the paler end of
     the gradient, and here the label sits over the darker end at only 4.47:1 — under the threshold
     for text this size. */
  color: color-mix(in srgb, var(--mint-ink) 78%, #000);
  text-decoration: none;
}
.quick-primary:active { transform: scale(0.99); }
.quick-primary strong { flex: 1; font-size: 1.02rem; font-weight: 650; }
.quick-primary-arrow { font-size: 1.2rem; opacity: 0.75; }
.quick-primary-glyph {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  border-radius: 14px;
  display: grid;
  place-content: center;
  background: color-mix(in srgb, #fff 58%, transparent);
}
.quick-primary .icon { width: 26px; height: 26px; }

.quick-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.quick-action {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
  padding: 16px 14px;
  border-radius: var(--radius-md);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  color: var(--text);
  text-decoration: none;
}
.quick-action:active { transform: scale(0.98); }
.quick-action .icon { width: 22px; height: 22px; }
.quick-action strong { font-size: 0.82rem; font-weight: 600; line-height: 1.2; }
.quick-action-glyph {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 12px;
  display: grid;
  place-content: center;
  background: color-mix(in srgb, var(--text) 10%, transparent);
}

/* ---- training lessons ----
   The reading, before the questions. Long-form text on a phone, so the measure is narrow, the line
   height is generous, and paragraphs are separated by space rather than by indentation. */
.lesson { display: flex; flex-direction: column; gap: 10px; }
.lesson-ref {
  font-size: 0.68rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-muted);
}
.lesson h2 { font-size: 1.06rem; font-weight: 650; line-height: 1.3; color: var(--text); }
.lesson-body { display: flex; flex-direction: column; gap: 10px; }
.lesson-body p { font-size: 0.92rem; line-height: 1.62; color: var(--text); }

/* ---- work offers and company details (0063 / 0064) --------------------------------------------- */

.offer-list { display: flex; flex-direction: column; gap: 14px; }

.offer {
  padding: 16px; border-radius: var(--radius-md);
  border: 1px solid var(--line); background: var(--bg-raised);
}
/* An offer waiting for an answer is the only one anybody needs to act on, so it is the only one that
   gets a colour. The rest are a record. */
.offer-open { border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }

.offer-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.offer-period { display: block; font-weight: 700; font-size: 1.02rem; }
.offer-ref { display: block; margin-top: 2px; font-size: 0.74rem; color: var(--text-muted); }

.offer-status {
  flex: none; padding: 4px 10px; border-radius: 999px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.02em; text-align: center;
}
.offer-status-ok { color: var(--status-ok, var(--ok)); background: color-mix(in srgb, var(--ok) 14%, transparent); }
.offer-status-warn { color: var(--status-warn, var(--warn)); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.offer-status-danger { color: var(--status-danger, var(--danger)); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.offer-status-muted { color: var(--text-muted); background: color-mix(in srgb, var(--tint) 7%, transparent); }

/* The price is what a driver looks for first. It gets its own line and its own size. */
.offer-money { display: flex; align-items: baseline; gap: 8px; margin: 12px 0 4px; }
.offer-money strong { font-size: 1.6rem; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.offer-money small { color: var(--text-muted); }

.offer-note { margin: 8px 0 0; line-height: 1.45; }
.offer-answer { margin: 10px 0 0; font-size: 0.84rem; color: var(--text-muted); }
.offer-answer-danger { color: var(--danger); }

.offer-facts { display: grid; gap: 10px; margin: 12px 0 0; grid-template-columns: 1fr 1fr; }
.offer-facts > div { min-width: 0; }
.offer-facts dt {
  margin: 0 0 2px; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-muted);
}
.offer-facts dd { margin: 0; font-size: 0.88rem; overflow-wrap: anywhere; }

.offer-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }
/* Turning work down sits beside sending somebody else, both the same size, neither smaller than
   accept. A decline button that looks like an afterthought is a decline nobody uses. */
.offer-actions-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
/* On an assignment where substitution is not practical, turning it down is the only option in
   the pair, and half a row of button reads like something failed to load. */
.offer-actions-pair > :only-child { grid-column: 1 / -1; }
/* `display: flex` beats the browser's own `[hidden] { display: none }`, so without this the answer
   form renders in full while still marked hidden — measured at 173px tall on a card that had not been
   touched. Every offer would have opened with its form already spilled out below it. */
.offer-form { display: flex; flex-direction: column; gap: 10px; }
.offer-form[hidden] { display: none; }

.check-row[hidden] { display: none; }
.check-row {
  display: flex; flex-direction: row; align-items: flex-start; gap: 10px;
  padding: 10px; border-radius: var(--radius-sm); background: var(--bg-sunken);
}
.check-row input { width: auto; flex: none; margin-top: 2px; }
.check-row span { font-size: 0.88rem; line-height: 1.4; }

/* A button is not secondary text. This one took its colour from --text-muted, so "Attach the page"
   and "Send to the company" rendered the same weight and colour as the sentence explaining them, and
   read as prose rather than as something you could press. */
.button-quiet { background: none; border: none; color: var(--text); font-weight: 600; }

.company-status { border-left: 4px solid var(--line); }
.company-status-ok { border-left-color: var(--ok); }
.company-status-warn { border-left-color: var(--warn); }
.company-status-danger { border-left-color: var(--danger); }
.company-status strong { display: block; }
.company-status p { margin: 6px 0 0; font-size: 0.86rem; color: var(--text-muted); }


/* ---- the drawer ---------------------------------------------------------------------------------- */
/* The tab bar holds five, which is as many as a thumb finds without looking, and it is full. This is
   where a sixth destination lives — and it lists everything, including what is already in the tab
   bar, because a drawer that only holds the leftovers teaches people not to open it. */

.topbar-menu {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; margin-left: -8px; flex: none;
  padding: 0; border: none; border-radius: var(--radius-sm);
  background: none; color: var(--text); cursor: pointer;
}
.topbar-menu:active { background: color-mix(in srgb, var(--tint) 10%, transparent); }
.topbar-menu:focus-visible { outline: 2px solid var(--text); outline-offset: -2px; }
.topbar-menu-bars { display: flex; flex-direction: column; gap: 4px; width: 19px; }
.topbar-menu-bars span { display: block; height: 2px; border-radius: 2px; background: currentColor; }

.drawer-scrim {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(0, 0, 0, 0.5);
}
.drawer-scrim[hidden] { display: none; }

.drawer {
  position: fixed; z-index: 41;
  top: 0; bottom: 0; left: 0;
  width: min(19rem, 82vw);
  display: flex; flex-direction: column; gap: 4px;
  padding: calc(var(--safe-top) + 12px) 12px calc(var(--safe-bottom) + 16px) calc(var(--safe-left) + 12px);
  overflow-y: auto;
  background: var(--bg-raised);
  border-right: 1px solid var(--line);
  box-shadow: 12px 0 40px rgba(0, 0, 0, 0.35);
}
.drawer[hidden] { display: none; }

.drawer-head { display: flex; align-items: center; justify-content: space-between; padding: 4px 8px 10px; }
.drawer-head strong { font-size: 1.05rem; }
.drawer-close {
  width: 36px; height: 36px; padding: 0; border: none; border-radius: var(--radius-sm);
  background: none; color: var(--text-muted); font-size: 1.5rem; line-height: 1; cursor: pointer;
}

.drawer-group { display: flex; flex-direction: column; gap: 2px; padding-bottom: 10px; }
.drawer-group-label {
  padding: 8px 8px 4px; font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted);
}

.drawer-link {
  display: flex; align-items: center; gap: 12px;
  /* 48px tall: a drawer is operated with a thumb, at arm's length, in a cab. */
  min-height: 48px; padding: 0 8px; border-radius: var(--radius-sm);
  color: var(--text); text-decoration: none; font-size: 0.95rem;
}
.drawer-link .icon { width: 20px; height: 20px; flex: none; color: var(--text-muted); }
.drawer-link:active { background: color-mix(in srgb, var(--tint) 8%, transparent); }
.drawer-link.active { background: color-mix(in srgb, var(--tint) 12%, transparent); font-weight: 600; }
.drawer-link.active .icon { color: var(--text); }

/* The screen behind must not scroll under an open drawer: on a phone that reads as the page having
   jumped when it closes again. */
body.drawer-open { overflow: hidden; }

/* ---- what the office is waiting on, on the home screen -------------------------------------------- */
/* Only ever rendered when it has something in it. The rule on this screen is that a panel which is
   usually empty teaches people to scroll past it, and then it is invisible on the day it matters. */
.panel-attention { border-left: 3px solid var(--warn); }
.attention-more {
  display: block; margin-top: 10px; padding: 8px;
  text-align: center; font-size: 0.85rem; color: var(--text-muted);
}

/* ---- the agreement, and signing it (0066) -------------------------------------------------------- */

.agreement-status {
  display: inline-block; margin-top: 10px; padding: 4px 10px; border-radius: 999px;
  font-size: 0.72rem; font-weight: 700;
}
.agreement-status-ok { color: var(--status-ok, var(--ok)); background: color-mix(in srgb, var(--ok) 14%, transparent); }
.agreement-status-warn { color: var(--status-warn, var(--warn)); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.agreement-status-muted { color: var(--text-muted); background: color-mix(in srgb, var(--tint) 7%, transparent); }

/* The document itself. A contract read on a phone is still a contract: it gets a readable measure, a
   line height that does not fight the eye, and headings that stay attached to what follows them. */
.agreement-body { font-size: 0.95rem; line-height: 1.62; }
.agreement-body h2 {
  margin: 22px 0 8px; font-size: 1rem; line-height: 1.3;
  break-after: avoid; page-break-after: avoid;
}
.agreement-body h2:first-child { margin-top: 0; }
.agreement-body p { margin: 0 0 9px; }
.agreement-body ul { margin: 0 0 12px; padding-left: 1.2rem; }
.agreement-body li { margin-bottom: 5px; }
.agreement-body strong { font-weight: 650; }
/* The hanging indent that makes "4.4" sit in the margin is dropped on a narrow screen: at this width
   it costs more in wrapped lines than it gives back in scannability. */
.agreement-body .doc-clause { padding-left: 0; text-indent: 0; }

.sign-panel { display: flex; flex-direction: column; gap: 12px; }
.sign-panel h2 { margin: 0; }

.signed-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.signed-row {
  display: flex; flex-direction: column; gap: 2px;
  padding: 12px; border-radius: var(--radius-sm); background: var(--bg-sunken);
}
.signed-row small { color: var(--text-muted); }
.signed-row .row-when { font-size: 0.78rem; color: var(--text-muted); }


/* ---- the person, and the companies asking for them (0072) ------------------------------------------

   The first screens in this app that are about the driver rather than about a company. They have to
   hold up for somebody who belongs to nobody, so nothing here assumes there is an organisation to
   name or a colour scheme borrowed from one. */

/* A company asking is not a notification — it arrives for a driver who may have no company at all,
   and the whole notification system is addressed per company. So it gets its own line above every
   screen, where it cannot be missed and cannot be confused with work. */
.invite-banner {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 16px; padding: 12px 14px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--line));
  color: inherit; text-decoration: none;
  font-size: 0.88rem; font-weight: 600;
}
.invite-banner span:first-child { flex: 1; min-width: 0; }

.invite-card {
  padding: 16px;
  border-radius: var(--radius-md);
  background: var(--bg-raised);
  border: 1px solid color-mix(in srgb, var(--warn) 40%, var(--line));
}
.invite-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.invite-head strong { font-size: 1.02rem; }
.invite-message {
  margin: 0; padding: 10px 12px;
  border-left: 2px solid var(--line);
  color: var(--text-muted); font-size: 0.88rem; line-height: 1.5;
}
/* Accepting and declining sit side by side at the same size. Making the refusal smaller or quieter
   would be the interface leaning on somebody over a decision that is theirs alone. */
.invite-actions { display: flex; gap: 10px; }
.invite-actions .button { flex: 1; }

.pill {
  flex: none; padding: 4px 10px; border-radius: 999px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.02em;
}
/* --status-* are the darkened light-theme text colours; the raw hue alone measured under 4.5:1 on a
   light ground. */
.pill-ok { color: var(--status-ok, var(--ok)); background: color-mix(in srgb, var(--ok) 14%, transparent); }
.pill-warn { color: var(--status-warn, var(--warn)); background: color-mix(in srgb, var(--warn) 16%, transparent); }

.company-row { width: 100%; text-align: left; cursor: pointer; font: inherit; color: inherit; }
.company-active { border-color: color-mix(in srgb, var(--ok) 45%, var(--line)); }

.section-label { font-size: 0.95rem; font-weight: 600; color: var(--text-muted); }
.stack { display: flex; flex-direction: column; gap: 12px; }

/* The company name in the top bar becomes a control only when there is more than one to choose from.
   A caret next to a name nobody can change is a promise the app does not keep. */
.brand-switch { text-decoration: none; color: inherit; }
.brand-caret { color: var(--text-muted); font-size: 0.75rem; flex: none; }

.auth-alt { display: flex; flex-wrap: wrap; gap: 6px; font-size: 0.88rem; }
.text-button { color: var(--text); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }


/* ---- the driver's half of the wallet (0073, folded back in) --------------------------------------

   Everything here is an ADDITION to the card language further up, never a redefinition of it. The
   first version of this was a screen of its own and defined its own `.doc-card`; sitting later in the
   file, it quietly won, and the gradient, radius and padding of every card in the real wallet were
   replaced by a flat grey box. Nothing below styles a class that already exists. */

/* A share code has no photograph, so the code takes the space the picture would have had — set in a
   face where 0 and O are not the same shape, spaced so somebody reading it down a phone does not lose
   their place. */
.card-code { align-items: center; justify-content: center; gap: 8px; }
.doc-code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 1.25rem; font-weight: 700; letter-spacing: 0.14em;
  padding: 10px 14px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--ink) 10%, transparent);
}

.card-renew {
  margin-top: 6px; padding: 8px 14px; min-height: 40px;
  display: inline-flex; align-items: center;
  border-radius: 999px; font-size: 0.8rem; font-weight: 700;
  background: color-mix(in srgb, var(--ink) 14%, transparent);
  color: inherit; text-decoration: none;
}

/* The field the browser is complaining about, marked so the message has something to point at. A form
   of thirteen required fields over three screens can otherwise say "please fill out this field" with
   nothing visible beside it. */
.field-invalid {
  outline: 2px solid var(--warn);
  outline-offset: 4px;
  border-radius: 10px;
}

/* The DVLA licence check. Four states that are genuinely different things to be told — approved,
   being looked at, send another, not needed — so they are told apart by a coloured edge rather than
   by wording alone, which is the difference somebody reads before they read anything. */
.dvla-standing { border-left: 4px solid var(--line); padding-left: 14px; }
.dvla-ok { border-left-color: var(--ok); }
.dvla-warn { border-left-color: var(--warn); }
.dvla-info { border-left-color: var(--accent, var(--line)); }
.dvla-standing h2 { color: var(--text); font-size: 1rem; }
/* What the office wrote to the driver, set apart from the app's own words: a person wrote this one. */
.dvla-message {
  margin: 4px 0 0; padding: 10px 12px; border-radius: 10px;
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  line-height: 1.5;
}
.dvla-kinds { display: flex; flex-direction: column; gap: 4px; margin-bottom: 4px; }

/* A code the driver has already typed, shown inside the editor the way it is shown on the card —
   because it IS the same code, and opening the card to an empty box with a ghost example in it told
   somebody they had not done the one thing they had done. */
.code-standing {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 18px 14px 14px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
}
.code-standing small { opacity: 0.75; }
.code-standing .button { margin-top: 8px; }
.code-fields { display: flex; flex-direction: column; gap: 12px; }
.is-hidden { display: none; }

/* 0236. A code card that also holds a page says so in one quiet line, and only when there is one.
   The code stays the whole card — it is what somebody reads out, and it is why this card exists. */
.card-clip {
  margin-top: 6px; padding: 3px 10px;
  display: inline-flex; align-items: center;
  border-radius: 999px; font-size: 0.72rem; font-weight: 600;
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  color: inherit; opacity: 0.85;
}

/* And inside the editor, where attaching one is offered. Set apart from the boxes above it by a rule
   rather than a panel, so it reads as "and also" instead of as a second form. */
.doc-attach {
  margin-top: 18px; padding-top: 14px;
  border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
}
.doc-attach h3 { margin: 0; font-size: 0.9rem; font-weight: 700; }
.doc-attach p { margin: 0; }
.doc-attach-row {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 8px 12px; border-radius: 10px;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  font-size: 0.85rem;
}
/* The company's copy is a different thing going to a different place, so it does not share the
   personal block's edge. */
.doc-attach-office { border-top-style: dashed; }

/* Sending everything, at the top of the wallet. Aligned right and kept small: it is the most
   consequential control on the screen, so it should be reachable without being the first thing a
   thumb lands on by accident. 44px tall, which is the smallest a target should be. */
.share-top {
  flex: none;
  min-height: 44px; padding: 0 16px;
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: 999px; border: 1px solid var(--line);
  background: var(--bg-raised); color: var(--text);
  font: inherit; font-size: 0.85rem; font-weight: 700; cursor: pointer;
}
.share-top:active { transform: scale(0.97); }

.doc-card-busy { opacity: 0.55; pointer-events: none; }

/* Adding a kind of document that is not in the wallet yet. A chip rather than a row: the list is a
   set of choices, and a stack of full-width rows would read as things that already exist. */
/* Putting a year on one sheet. Two dates and three shortcuts, because nobody wants to operate a date
   picker twice to ask for "last year". */
.export-panel { margin-top: 14px; }
/* `1fr` is `minmax(auto, 1fr)`, and `auto` will not shrink a track below its content's intrinsic
   width. A native date input has a wide intrinsic width and ignores the track, so these two fields
   pushed the whole document to 391px inside a 375px phone.

   It survived until the export panel got card padding, which took away the 32px it had been getting
   away with. The symptom was not a stray horizontal scrollbar — on a phone a document wider than the
   viewport makes VERTICAL swiping unreliable, so the two pages carrying this panel, and only those
   two, felt like they would not scroll. */
.export-period { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.export-period label { min-width: 0; }
.export-period input { width: 100%; min-width: 0; max-width: 100%; box-sizing: border-box; }
.export-quick { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 12px; }
.export-quick .chip { cursor: pointer; border: 1px solid var(--line); background: var(--bg-raised); color: var(--text); }

/* A card for a document that is not there yet.
   THE SAME SIZE as a real one — it inherits .doc-card and overrides only what has to differ. It was
   two thirds the height, and a wallet of ten then read as a list of headings with three cards mixed
   into it. A card is a card; what says this one is empty is the dashed edge, the + where the
   photograph goes, and the chip at the foot.
   The colour is the real one too: dimming was tried twice and measured both times, and mixing these
   pastels toward a near-black page turns all six into the same muddy mid-grey. */
.doc-ghost {
  border: 2px dashed color-mix(in srgb, var(--ink) 32%, transparent);
  /* .doc-card is a div; this is a button, which brings its own font and alignment. */
  font: inherit;
  text-align: left;
  width: 100%;
}
.doc-ghost:active { transform: scale(0.985); }

/* The middle of an empty card. The real card's own "no photograph yet" state uses .card-drop, so
   this is the same box with a + in it instead of an arrow — the two read as the same thing because
   they are the same thing. */
.doc-ghost .card-drop-icon {
  background: color-mix(in srgb, #fff 70%, transparent);
  color: var(--ink);
  font-size: 1.6rem;
  font-weight: 600;
}
.doc-ghost .card-drop strong { color: var(--ink); }
.doc-ghost .card-badge { background: color-mix(in srgb, #fff 62%, transparent); color: var(--ink); }
.doc-ghost .card-head p { color: var(--ink); opacity: 0.7; }

/* Quieter than every other chip on a card: this one is not a warning, it is the absence of one. */
.chip-ghost {
  background: color-mix(in srgb, #fff 55%, transparent);
  color: var(--ink);
}

/* While the photograph is going up and being read. The tap has to acknowledge itself or somebody
   presses it again and starts a second one. */
.doc-ghost-busy { opacity: 0.55; pointer-events: none; }

/* A card read off a photograph that nobody has checked yet.
   Dashed, because dashed is how this app draws something that is not finished — the wallet's empty
   state uses one — and this document genuinely is not finished: it is not in the pack a company gets
   and it does not drive an expiry reminder until a person has said the dates are right. */
.doc-card-unchecked { outline: 2px dashed var(--warn); outline-offset: 3px; }
/* The background is a pale mix in BOTH themes, so the text on it cannot come from --text-muted:
   that is #9a9aa3 on #fcebc4, which measures 2.37:1 and is the chip that says "waiting on you".
   A fixed warm ink instead — 11:1 on the same pale ground, and theme-proof because the ground is. */
.chip-checkme { background: color-mix(in srgb, var(--warn) 34%, #fff); color: #3d2f04; }

/* While the photograph is going up and being read. The tap has to acknowledge itself or somebody
   presses it again and starts a second one. */
.doc-ghost-busy { opacity: 0.55; pointer-events: none; }

/* Said once at the top of the confirmation screen rather than as an asterisk under the boxes: what is
   in them was read by a machine, and the expiry date decides when this app warns a driver their
   licence is running out. */
.doc-form-checking { border: 1px solid var(--warn); }
.doc-form-note { margin: 0 0 12px; color: var(--warn); font-size: 0.85rem; line-height: 1.4; }


.wallet-empty { display: flex; flex-direction: column; gap: 6px; padding: 22px;
  border: 1px dashed var(--line); border-radius: var(--radius-md); }
.wallet-empty small { color: var(--text-muted); font-size: 0.85rem; }

.doc-form { padding: 16px; border-radius: var(--radius-md); background: var(--bg-raised);
  border: 1px solid var(--line); }
.doc-form-actions { display: flex; gap: 10px; }
.doc-form-actions .button { flex: 1; }

/* What has already been sent, folded away: useful when somebody asks "did I send it?", and noise
   every other time. */
.share-history { margin-top: 4px; }
.share-history summary { cursor: pointer; color: var(--text-muted); font-size: 0.82rem; padding: 6px 0; }
.share-history .rows { margin-top: 8px; }

/* ---- the invoice a driver raises against accepted work (0084) -------------------------------------
   Laid out as the document it becomes rather than as a form, because that is what the driver is being
   asked to check. The money column is right-aligned against one edge: a column of amounts that does
   not line up is the first thing anybody distrusts, on paper and on a phone alike. */
.invoice-sheet { display: flex; flex-direction: column; gap: 16px; }

.invoice-parties { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.invoice-party { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.invoice-party-label {
  font-size: 0.66rem; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 3px;
}
.invoice-party strong { font-size: 0.95rem; line-height: 1.25; }
.invoice-party small { color: var(--text-muted); font-size: 0.78rem; line-height: 1.35; }

.invoice-lines { display: flex; flex-direction: column; }
.invoice-line {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
  padding-top: 14px; border-top: 1px solid var(--line);
}
.invoice-line small { display: block; margin-top: 3px; color: var(--text-muted); font-size: 0.78rem; }
.invoice-line-amount { display: flex; align-items: center; gap: 8px; flex: none; }
.invoice-line-amount strong { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* 32px and a hit area to match: removing a line you have just added is a common correction, not a
   rare one, and it is done with a thumb. */
.invoice-remove {
  width: 32px; height: 32px; flex: none; padding: 0;
  border: 1px solid var(--line); border-radius: 50%;
  background: none; color: var(--text-muted); font-size: 1.1rem; line-height: 1; cursor: pointer;
}
.invoice-remove:active { background: color-mix(in srgb, var(--tint) 10%, transparent); }

/* Quantity, rate and amount side by side: a driver fills in two of the three, and seeing all three
   at once is what makes it obvious which two. */
/* The number about to be spent, and the way to change it, on one line — because the moment somebody
   is about to spend one is the moment they will know whether it is the right one. */
.invoice-number-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.invoice-next-number { display: block; font-size: 1.25rem; font-variant-numeric: tabular-nums; }
.invoice-number-fields { display: grid; grid-template-columns: 1.4fr 1fr 0.8fr; gap: 10px; margin-top: 12px; }
@media (max-width: 380px) { .invoice-number-fields { grid-template-columns: 1fr 1fr; } }

.invoice-extra-fields { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
@media (max-width: 380px) {
  .invoice-extra-fields { grid-template-columns: 1fr 1fr; }
  .invoice-extra-fields > label:last-child { grid-column: 1 / -1; }
}

.invoice-totals { display: flex; flex-direction: column; gap: 8px; padding-top: 14px; border-top: 1px solid var(--line); }
.invoice-total-row { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; font-size: 0.9rem; }
.invoice-total-row strong { font-variant-numeric: tabular-nums; white-space: nowrap; }
.invoice-total-due { padding-top: 8px; border-top: 1px solid var(--line); font-size: 1.05rem; }
.invoice-total-due strong { font-size: 1.25rem; }

.invoice-terms { color: var(--text-muted); font-size: 0.82rem; line-height: 1.5; }
.invoice-terms span { font-weight: 700; color: var(--text); }

/* Every reason at once, so nobody discovers them one at a time and gives up on the way. */
.invoice-blockers { margin: 8px 0 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; font-size: 0.88rem; }

@media (max-width: 420px) {
  .invoice-parties { grid-template-columns: 1fr; }
}


/* ---- the money pages: expenses and invoices ---------------------------------------------------
   These two are the only screens in the app that are about figures rather than about compliance, and
   they were built as bare stacked text — `.panel` here carries no background, no border and no
   padding, so nothing on the page looked like a thing you could pick up. Everything below is scoped
   to these pages; none of it changes the wallet, the checks or the chat. */

/* An icon in a rounded tile. The tile is what makes a heading read as a card rather than as a
   paragraph that happens to be bold. */
.tile {
  flex: 0 0 auto;
  display: grid;
  place-content: center;
  width: 44px;
  height: 44px;
  border-radius: 13px;
  background: var(--tile-bg);
  color: var(--tile-ink);
}
.tile .icon { width: 22px; height: 22px; }

/* A card with a number in it. Used for the expense total and, in pairs, for the invoice figures. */
.stat-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--bg-raised);
}
.stat-body { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
.stat-body span { color: var(--text-muted); font-size: .76rem; }
.stat-body strong { font-size: 1.5rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.stat-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.stat-pair .stat-card { padding: 14px; gap: 10px; }
.stat-pair .tile { width: 36px; height: 36px; border-radius: 11px; }
.stat-pair .tile .icon { width: 18px; height: 18px; }
.stat-pair .stat-body strong { font-size: 1.15rem; }

/* Twelve months of spending, drawn from the expenses already on the page — real figures, not an
   ornament. It is deliberately unlabelled and unscaled: at this size it can only honestly say
   "roughly this shape", and putting an axis on it would claim more than 60 pixels can carry. */
.spark { flex: 0 0 auto; width: 88px; height: 38px; overflow: visible; }
.spark polyline { fill: none; stroke: var(--tile-ink); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.spark circle { fill: var(--tile-ink); }
.spark-fill { fill: var(--tile-bg); stroke: none; }

/* The one coloured control in the app. */
.button-gradient {
  border: 0;
  color: var(--grad-ink);
  background: linear-gradient(100deg, var(--grad-a), var(--grad-b));
}
.button-gradient:active { filter: brightness(0.94); }
.button-gradient:disabled { filter: grayscale(0.4); }
.button-gradient .icon { width: 18px; height: 18px; }

/* The export panel and each money row, given the same card treatment. */
.paper-card, .money-card {
  gap: 12px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--bg-raised);
}
.paper-head { display: flex; align-items: flex-start; gap: 12px; }
.paper-head h2 { margin: 0; color: var(--text); font-size: .95rem; }
.paper-head p { margin: .2rem 0 0; color: var(--text-muted); font-size: .8rem; }

/* Dates, references, "incl. VAT" — the facts about a row that are not its name or its amount. */
.meta-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text) 9%, transparent);
  color: var(--text-muted);
  font-size: .73rem;
  white-space: nowrap;
}
.money-meta { display: flex; flex-wrap: wrap; gap: 6px; }

/* A supplier's initials, standing in for the logo the mockup had. There is no logo for "Post Office
   Ltd" anywhere in this system and inventing one would mean fetching images from somewhere on the
   strength of a name a model read off a photograph. */
.money-avatar {
  flex: 0 0 auto;
  display: grid;
  place-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--text) 11%, transparent);
  color: var(--text-muted);
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .02em;
}
.money-head { display: flex; align-items: flex-start; gap: 12px; }
.money-head-body { flex: 1 1 auto; min-width: 0; }
.money-head-body strong { display: block; font-size: .96rem; }
.money-head-body small { display: block; color: var(--text-muted); font-size: .78rem; margin-top: .1rem; }
.money-amount { flex: 0 0 auto; text-align: right; font-variant-numeric: tabular-nums; }
.money-amount strong { display: block; font-size: 1.05rem; }
.money-amount small { display: block; color: var(--text-muted); font-size: .72rem; }

.expense-actions .button-ghost, .money-card .button-ghost { border-color: var(--line); }
.money-card .button-secondary { background: color-mix(in srgb, var(--text) 7%, transparent); border-color: var(--line); }
.money-card .button { flex: 1 1 auto; }
.money-card .button .icon { width: 17px; height: 17px; }

@media (max-width: 340px) { .stat-pair { grid-template-columns: 1fr; } }


/* ---- infringements -------------------------------------------------------------------------------
   Reuses the money-page components — a card, a tile, meta chips — because this screen is the same
   kind of thing: a record with facts on it and an action attached. */

/* The home panel. Red rather than the green `.needs-you` beside it: a document waiting to be checked
   is housekeeping, and this is the office telling a driver they broke the law. Same shape so it reads
   as the same kind of interruption, different colour so it is not mistaken for one. */
.needs-you-alarm {
  background: linear-gradient(135deg, #7a2320, #a3352f);
  box-shadow: 0 6px 18px color-mix(in srgb, #4a1512 50%, transparent);
}

.infringement-row { text-decoration: none; color: inherit; display: block; }
.tile-alarm { background: color-mix(in srgb, var(--danger) 18%, transparent); color: var(--danger); }

.infringement-avoid { margin: 0; padding-left: 16px; color: var(--text-muted); font-size: .85rem; line-height: 1.55; }
.infringement-avoid li { margin-bottom: 5px; }

/* The acknowledgement and the signature, set apart from the form above them. A driver should feel
   the line between writing something and signing it. */
.infringement-sign {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.infringement-sign p { color: var(--text-muted); line-height: 1.5; }

/* For a label that exists only for a screen reader, where a visible one would repeat the heading
   directly above it. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}


/* ---- the lesson inside an infringement ----------------------------------------------------------- */

.lesson-card { border-color: color-mix(in srgb, var(--grad-a) 30%, var(--line)); }

/* The three numbers that are the whole rule — 45h / 24h / 6×24h. A driver remembers a number in a box
   long after they have forgotten the paragraph under it. */
.lesson-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(72px, 1fr)); gap: 8px; margin: 4px 0 2px; }
.lesson-facts div { text-align: center; padding: 9px 4px; border-radius: 11px; background: var(--tile-bg); }
.lesson-facts b { display: block; font-size: 1.05rem; font-weight: 700; color: var(--tile-ink); }
.lesson-facts span { font-size: .62rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }

.lesson-example { border-left: 2.5px solid var(--tile-ink); padding: 8px 10px; border-radius: 0 10px 10px 0;
  background: color-mix(in srgb, var(--text) 5%, transparent); }
.lesson-example strong { display: block; font-size: .82rem; margin-bottom: 3px; }
.lesson-example p { margin: 0; font-size: .82rem; color: var(--text-muted); line-height: 1.5; }

.lesson-source { display: inline-block; font-size: .74rem; color: var(--tile-ink); text-decoration: underline; }

.lesson-quiz { display: flex; flex-direction: column; gap: 12px; margin-top: 6px;
  padding-top: 12px; border-top: 1px solid var(--line); }
.lesson-q p { margin: 0 0 6px; }
.lesson-options { display: flex; flex-direction: column; gap: 6px; }

/* The whole row is the target. A radio dot on a phone in a lay-by is not a thing anybody hits. */
.lesson-option { display: flex; align-items: center; gap: 9px; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 11px; font-size: .84rem; cursor: pointer; }
.lesson-option input { accent-color: var(--grad-a); flex: 0 0 auto; }

/* After marking, BOTH are shown: the one they picked and the one that was right. Being told only that
   you were wrong teaches nothing. */
.lesson-option-right { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 14%, transparent); }
.lesson-option-wrong { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent); }
.lesson-why { margin: 6px 0 0; font-size: .78rem; color: var(--text-muted); line-height: 1.45; }

.lesson-result { text-align: center; font-weight: 600; font-size: .88rem; padding: 8px;
  border-radius: 10px; background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }
.lesson-result-pass { background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok); }

/* ---- a legal document, read on a phone ------------------------------------------------------------
   Four shapes and no more. This is read by somebody who has just been asked to photograph their
   passport, so it is set for reading rather than for skimming: wider line height than the rest of the
   app, real space between sections, and headings that are quiet enough not to be shouted past. */
.legal-body { line-height: 1.68; }
.legal-body h2 {
  margin: 1.4rem 0 .5rem;
  font-size: .95rem;
  font-weight: 650;
  color: var(--text);
  letter-spacing: -0.005em;
}
.legal-body h2:first-child { margin-top: 0; }
/* FULL TEXT, NOT MUTED TEXT.

   This was --text-muted at .88rem, which measures 6.59:1 — above the 4.5:1 the standard asks for, and
   the standard is not the test here. The handbook is 106,000 characters and a driver reads it on a
   phone, in a cab, often in poor light. Muted grey is the colour of a caption; a document somebody has
   to tick to say they understood is not a caption.

   The operator read it end to end on his own phone and said it was hard to see, which is worth more
   than a ratio that passes. Full --text takes it to about 16:1 and the size up a notch. */
.legal-body p { margin: 0 0 .8rem; font-size: .95rem; color: var(--text); }

/* The other half of a bilingual declaration.

   The signed record keeps a snapshot of `declarations_json` and hashes it, so the document he signs
   contains BOTH wordings whatever the screen draws. Showing only his own language would show him less
   than he is signing — so the other one sits underneath, quiet but present, tied to the statement above
   it by a rule rather than by proximity alone. It is subordinate, not hidden: never below .8rem, and
   never a colour that stops being readable in either theme. */
.legal-body p.decl-other {
  margin: -.5rem 0 .9rem;
  padding-left: .7rem;
  border-left: 2px solid var(--line);
  font-size: .82rem;
  color: var(--text-muted);
}

/* Which document, and which version of it. Small, but it is the answer to "what did I sign". */
.decl-doc {
  margin: 0 0 1rem;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--text-muted);
}
.legal-body strong { color: var(--text); font-weight: 650; }
.legal-list { margin: 0 0 .85rem; padding-left: 18px; }
.legal-list li { margin-bottom: .45rem; font-size: .95rem; color: var(--text); line-height: 1.65; }
.legal-rule { border: 0; border-top: 1px solid var(--line); margin: 1.3rem 0 1rem; }

/* ---- onboarding: where you are of twelve -----------------------------------------------------
   Twelve rows on a phone, most of them not yet the driver's turn. The whole design problem is
   showing what is ahead WITHOUT it reading as twelve things to do right now — so only the current
   row carries a button, and the rest carry a state and nothing to press.

   The tone comes from the database's `waiting_on`, never from a colour guessed in the screen. */
.onb-count { display: flex; align-items: baseline; gap: 6px; }
.onb-count strong { font-size: 2.2rem; line-height: 1; }
.onb-count span { color: var(--text-muted); font-size: 1rem; }

.onb-bar {
  margin: 10px 0 12px;
  height: 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--tint) 10%, transparent);
  overflow: hidden;
}
.onb-bar > span { display: block; height: 100%; background: var(--ok); border-radius: 999px; }

.onb-ref { color: var(--text-muted); font-size: .82rem; margin: 0; }

.onb-list { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 8px; }

.onb-step {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--bg-raised);
}
/* A locked step is dimmed rather than hidden: knowing an IR35 questionnaire is coming is most of
   the value of this screen, and it is the step people need to find their accountant for. */
.onb-locked { opacity: .55; }
.onb-good .onb-mark { background: var(--ok); color: #06210f; border-color: transparent; }
.onb-office .onb-mark { border-color: var(--info); color: var(--info); }
.onb-bad .onb-mark { border-color: var(--danger); color: var(--danger); }
.onb-next { border-color: color-mix(in srgb, var(--ok) 45%, var(--line)); }

.onb-mark {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border-radius: 999px;
  border: 1px solid var(--line);
  font-size: .82rem;
  font-variant-numeric: tabular-nums;
}

.onb-body { flex: 1 1 auto; min-width: 0; display: grid; gap: 2px; }
.onb-body strong { font-weight: 600; font-size: .95rem; }
.onb-note { color: var(--text-muted); font-size: .78rem; }
.onb-bullet { margin: 2px 0 2px 6px; }
/* Inside a document body a bullet is a paragraph with a dot, and takes .legal-body p's colour and
   size. Only the indent is its own. */
.legal-body .onb-bullet { margin: 0 0 .5rem 8px; }

.onb-go { flex: 0 0 auto; padding: 8px 14px; font-size: .85rem; }

.onb-form { display: grid; gap: 12px; margin-top: 6px; }
.onb-form label { display: grid; gap: 4px; }
.onb-form .field-invalid {
  outline-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, transparent);
  scroll-margin-block: calc(100px + var(--safe-top)) calc(100px + var(--safe-bottom));
}
.onb-form [aria-invalid="true"] { border-color: var(--danger); }
.onb-form .onb-field-error {
  display: block;
  color: var(--danger);
  font-size: .85rem;
  line-height: 1.45;
  font-weight: 600;
}
.onb-form .checkbox-row.field-invalid { flex-wrap: wrap; }
.onb-form .checkbox-row .onb-field-error { flex-basis: 100%; }
/* A tick beside its label, not above it.

   The generic `label` rule above is flex-column, which is right for "caption over field" and wrong for
   a checkbox — and this rule, which fixes it, was scoped to `.onb-form`. Of the six places the app
   draws a `.checkbox-row`, only the ones inside a form got the fix: the onboarding acknowledgement,
   the sign-up privacy tick, the infringement dispute and the platform privacy gate all had the box
   centred on its own line above the words it belongs to.
   
   Found by adding a seventh on the Documents screen and looking at it on a phone. Unscoped now, which
   is what it should always have been — a class named for a row is a row wherever it is. */
label.checkbox-row { display: flex; flex-direction: row; align-items: flex-start; gap: 10px; }
label.checkbox-row input[type="checkbox"] { flex: 0 0 auto; margin-top: 2px; }
label.checkbox-row span { flex: 1 1 auto; }
/* A heading inside a long form.

   Thirty-four questions of employment-status law read as one undifferentiated wall, and the wall is
   what makes people answer carelessly to get to the end. The heading has to be visibly a JOIN — more
   space above it than below, so it belongs to what follows rather than floating between — and it must
   not compete with the form's own <h2>. The rule sits with the form because that is the only place a
   section heading appears; a first-child has no gap above it because the panel already provides one. */
.onb-form .onb-section {
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.onb-form .onb-section:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

.onb-form input[type="text"], .onb-form input[type="email"], .onb-form input[type="date"],
.onb-form input[type="number"], .onb-form select {
  width: 100%;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
}

/* The company's documents, readable after onboarding rather than only during it.

   Two different things get said on a row and they must not look the same. "You confirmed this
   version" is settled; "a newer version has been published since you confirmed" is not, and it is
   the state every driver of this company is actually in — the privacy notice was republished and
   nobody has been re-asked. So the second carries --warn: not an error, but not finished either. */
.docs-hint { margin: 0 0 .75rem; }
.docs-ok,
.docs-changed {
  display: block;
  margin-top: .25rem;
  font-size: .78rem;
  font-weight: 600;
}
.docs-ok { color: var(--ok); }
.docs-changed { color: var(--warn); }

/* What was actually in a pack that was sent. Under the recipient and the date, quieter than
   both, because the question it answers is "which code went there" and not "who". */
.share-contents { display: block; margin-top: .3rem; opacity: .8; line-height: 1.5; }

/* 0303 — the driver's own mark, shown as it will sit at the head of their invoices. On white, because
   that is the ground the picture was flattened onto when it became a JPEG, and what the paper is. */
.logo-preview {
  display: block;
  width: auto;
  height: auto;
  max-width: 180px;
  max-height: 72px;
  margin: 0 0 0.75rem;
  padding: 6px 8px;
  background: #ffffff;
  border-radius: 10px;
}

/* 0303 — the document, before it is issued. White and dark whatever the phone's theme is: this is the
   only thing on the screen that is not the app talking to the driver, it is the paper their customer
   will hold, and a dark-mode invoice would be a lie about what gets sent. */
.driver-paper {
  background: #ffffff;
  color: #16181d;
  border-radius: 14px;
  padding: 1.1rem 1rem 1.25rem;
  margin: 0 0 1rem;
  font-size: 0.92rem;
  line-height: 1.45;
  overflow-x: auto;
}
.driver-paper p { margin: 0.1rem 0; }
.driver-paper .paper-logo {
  display: block; width: auto; height: auto;
  max-width: 150px; max-height: 48px; margin: 0 0 0.75rem;
}
.invoice-sheet > .paper-logo { display: block; width: auto; height: auto; max-width: 150px; max-height: 48px; align-self: flex-start; }
.driver-paper .invoice-party-label,
.driver-paper .invoice-party small,
.driver-paper .invoice-line small,
.driver-paper .invoice-terms,
.driver-paper .invoice-terms span,
.driver-paper .muted { color: #596170; }
.driver-paper .invoice-total-due { color: #16181d; }
.driver-paper .invoice-line strong,
.driver-paper .invoice-party strong { overflow-wrap: anywhere; }
.paper-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.75rem; border-bottom: 1px solid #d9dce3; padding-bottom: 0.6rem;
}
.paper-word { font-size: 1.25rem; letter-spacing: 0.04em; }
.paper-number { font-weight: 700; }
.paper-number-waiting { font-weight: 400; color: #6b7280; font-size: 0.8rem; }
.paper-parties {
  display: grid; grid-template-columns: 1fr 1fr; gap: 0.9rem;
  padding: 0.8rem 0; border-bottom: 1px solid #d9dce3;
}
.paper-label { display: block; font-size: 0.7rem; letter-spacing: 0.06em; color: #6b7280; }
.paper-lines { padding: 0.7rem 0; border-bottom: 1px solid #d9dce3; }
.paper-line, .paper-totals div {
  display: flex; justify-content: space-between; gap: 1rem; padding: 0.15rem 0;
}
.paper-totals { padding: 0.7rem 0 0; }
.paper-total { border-top: 1px solid #d9dce3; margin-top: 0.35rem; padding-top: 0.45rem; font-size: 1.05rem; }
.paper-pay { margin-top: 0.9rem; padding-top: 0.7rem; border-top: 1px solid #d9dce3; }
@media (max-width: 420px) { .paper-parties { grid-template-columns: 1fr; } }

/* 0309 — the IR35 answers, already written. A document to read, not a form to fill: the questions
   sit quiet and the answers carry the weight. */
.stm-doc { margin: 0.25rem 0 1rem; }
.stm-list { display: grid; gap: 0.1rem; margin: 0 0 0.9rem; }
.stm-line { display: grid; grid-template-columns: 1fr auto; gap: 0.75rem; align-items: baseline;
  padding: 0.45rem 0; border-bottom: 1px solid var(--line, rgba(127,127,127,0.18)); }
.stm-line dt { margin: 0; font-size: 0.86rem; opacity: 0.75; }
.stm-line dd { margin: 0; font-weight: 600; text-align: right; }
.stm-pick { display: block; text-align: left; margin-bottom: 0.6rem; }
.stm-pick strong { display: block; }

/* 0313 — where he went wrong, after a training. */
.training-mistakes h2 { margin-bottom: 0.4rem; }
.mistake { padding: 0.75rem 0; border-top: 1px solid var(--line, rgba(127,127,127,0.2)); }
.mistake p { margin: 0.2rem 0; }
.mistake-yours span, .mistake-right span { font-weight: 700; margin-right: 0.25rem; }
.mistake-yours { color: var(--danger, #e5484d); }
.mistake-right { color: var(--ok, #30a46c); }
.training-retake-note { font-weight: 600; margin-top: 0.5rem; }

/* An employee's own terms under his contract (0326): the label on the left, the value on the right, one line each. */
.contract-terms-list { display: grid; gap: 0.45rem; margin: 0; }
.contract-terms-list > div { display: flex; justify-content: space-between; gap: 1rem; align-items: baseline; }
.contract-terms-list dt { color: var(--text-muted); }
.contract-terms-list dd { margin: 0; text-align: right; }
