/* Sora — the wordmark typeface from the SchoolFleet logo kit ("School" 500,
   "Fleet" 800). Self-hosted rather than pulled from Google Fonts: staff use this
   app behind a content filter that has swallowed third-party requests before,
   and a blocked font would silently drop the wordmark to a fallback face.
   One file covers the whole range — Sora ships as a variable font. */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('/static/fonts/sora-latin-var.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* ---- Official tokens from the logo kit (exports/tokens.json). These are the
     brand's own values — don't re-derive them by eye from the artwork. ---- */
  --sf-primary: #ea580c;
  --sf-primary-dark: #9a3412;
  --sf-primary-mid: #fb923c;
  --sf-primary-light: #fed7aa;
  --sf-tint: #fff7ed;
  --sf-ink: #1a1d24;
  --sf-icon-gradient: linear-gradient(145deg, #c2410c, #fb923c);
  --sf-font-wordmark: 'Sora', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  /* Dark theme (default) — neutral graphite grey with the SchoolFleet orange
     accent taken from the chevron logo. Backgrounds are strictly grey; orange
     lives only on brand accents.

     Brand orange and status colors are deliberately separate systems:
       brand   = orange  (identity: actions, nav, logo, avatars)
       status  = red / yellow / green (meaning: overdue, due soon, ok)
     The logo's orange sits close to a warning amber, so "due soon" uses a
     cooler yellow to stay distinguishable from brand chrome. Never use
     --accent to signal status, and never use --warning as decoration. */

  /* Logo palette — the three chevrons, lightest to deepest. Aliased to the
     kit's official tokens above; change them there, not here. */
  --brand-100: var(--sf-primary-light);
  --brand-400: var(--sf-primary-mid);
  --brand-600: var(--sf-primary);
  --brand-800: var(--sf-primary-dark);

  /* One softened step off the brand orange, for the ONE place the full-strength
     orange was too loud: the selected tab in a dialog (see .mst-btn.active).
     Not a general-purpose accent — everything else stays on --accent. */
  --brand-soft: #ec7022;

  /* BOYS AND GIRLS — a third system beside the two above, and deliberately so:
     this is neither an action nor a status, it is WHOSE. The dispatch board's
     groups, the run badges, the season plans, attendance and the school's own
     account strip all say it, and they say it in the same blue and pink so that
     one glance answers it anywhere in the app. Blue and pink because that is
     what these schools call the two halves themselves — and because orange is
     the brand and can never mean anything. Both themes share the values; they
     read on white and on graphite alike. Anything that wants them softened
     mixes them down (color-mix) rather than picking a second pink. */
  --cat-boys: #3b82f6;
  --cat-girls: #ec4899;

  /* The app header's height — 14+14 padding + 46px logo + 1px border, plus the
     notch on a phone. Anything that pins under the header (the subsection tab
     bar) needs it, so it's a token rather than a number copied twice. The
     desktop query restates it without the safe-area inset. */
  --topbar-h: calc(75px + env(safe-area-inset-top, 0px));
  /* ...and the tab bar under it, for anything that pins below BOTH. Zero unless
     the screen actually has one — see the rule next to .disp-tabs. */
  --subbar-h: 0px;

  --bg: #15171b;
  --bg-2: #1e2127;
  --bg-3: #2d323b;
  --surface: #1a1d22;
  --surface-2: #262a31;
  --border: #2e333b;
  --text: #e7e9ec;
  --text-dim: #99a0a8;
  --muted: #6a7178;
  --accent: #f97316;     /* brand orange, lifted for contrast on dark grey */
  --accent-soft: rgba(249, 115, 22, 0.16);
  --brand-grad: linear-gradient(135deg, var(--brand-400), var(--brand-600));
  --success: #34d399;    /* status: ok / healthy — stays green by design */
  --warning: #eab308;    /* status: due soon — yellow, not orange (see above) */
  --danger: #ef4444;     /* status: overdue */
  --critical: #dc2626;
  --radius: 14px;
  --radius-sm: 10px;
  --radius-lg: 20px;
  /* One height for every toolbar control (search, segmented toggle, selects) so
     a row of them lines up. Change here, not per-control. */
  --control-h: 43px;
  /* Shared lift for toolbar controls — they read as one row of the same object. */
  --shadow-control: 0 1px 2px rgba(0, 0, 0, 0.34), 0 2px 8px rgba(0, 0, 0, 0.22);
  --shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
  --topbar-bg: rgba(26, 29, 34, 0.92);
  --modal-bg: #1e2127;   /* dialog surface — neutral, same family as the app */

  /* Navigation chrome (top bar + bottom nav) — a NEUTRAL GREY surface, no brand
     tint. The logo and the accent controls carry the orange; the chrome itself
     stays quiet so the app reads calm and professional. Light theme below. */
  --chrome-bg: #1a1d22;
  --chrome-text: #e7e9ec;
  --chrome-dim: rgba(231, 233, 236, 0.72);
  --chrome-muted: rgba(231, 233, 236, 0.55);
  --chrome-border: rgba(255, 255, 255, 0.07);
  --chrome-accent: #fb923c;
  --chrome-active-bg: rgba(251, 146, 60, 0.16);
  --chrome-badge: #eab308;
  /* Inputs that sit in the chrome (the fleet search) — a grey field, never tinted. */
  --field-bg: #22262d;
  /* Nav count bubbles. A plain count ("11 jobs") is INFORMATION, not a warning —
     it stays a quiet neutral. Only a real status count (overdue) goes red. */
  --badge-bg: #4b5563;
  --badge-fg: #ffffff;
}

/* Light theme — clean neutral grey, low-glare, brand orange accent. */
:root[data-theme="light"] {
  --bg: #f3f4f6;
  --bg-2: #e9ebee;
  --bg-3: #dbdee2;
  --surface: #ffffff;
  --surface-2: #eef0f3;
  --border: #dfe2e6;
  --text: #1c2025;
  --text-dim: #5d646c;
  --muted: #8a9199;
  --accent: #ea580c;     /* brand orange — the deepest chevron */
  --accent-soft: rgba(234, 88, 12, 0.12);
  --success: #15803d;    /* status: ok — green, darkened for light bg */
  --warning: #a16207;    /* status: due soon — yellow-brown, not orange */
  --danger: #dc2626;
  --critical: #b91c1c;
  --shadow: 0 6px 18px rgba(30, 40, 50, 0.10);
  --topbar-bg: rgba(255, 255, 255, 0.9);
  --modal-bg: #ffffff;   /* dialog surface — neutral, same family as the app */

  /* Chrome (header/footer) = a clean NEUTRAL surface, no brand tint. */
  --chrome-bg: #ffffff;
  --chrome-text: #1c2025;
  --chrome-dim: rgba(28, 32, 37, 0.66);
  --chrome-muted: rgba(28, 32, 37, 0.5);
  --chrome-border: rgba(28, 32, 37, 0.10);
  --chrome-accent: #ea580c;
  --chrome-active-bg: rgba(234, 88, 12, 0.10);
  --chrome-badge: #a16207;
  /* Inputs that sit in the chrome (the fleet search) — a grey field, never tinted. */
  --field-bg: #f3f4f6;
  /* Nav count bubbles — see the dark theme for the reasoning. */
  --badge-bg: #64748b;
  --badge-fg: #ffffff;
  /* Softer on light: a low-glare lift, not a dark halo. */
  --shadow-control: 0 1px 2px rgba(16, 24, 40, 0.06), 0 2px 8px rgba(16, 24, 40, 0.07);
}

/* Keep faint-tinted status pills readable on a light background */
:root[data-theme="light"] .cpill-overdue,
:root[data-theme="light"] .cflag-overdue { color: #be123c; }
:root[data-theme="light"] .cpill-soon,
:root[data-theme="light"] .cflag-soon { color: #a16207; }
:root[data-theme="light"] .cpill-ok { color: #15803d; }
/* Same reason as the three above: #fca5a5 on a white card is a pill you can see
   but not read, and "MISSING" is the one word on it that matters. */
:root[data-theme="light"] .cpill-missing { color: #b91c1c; }

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

body { padding-bottom: calc(100px + env(safe-area-inset-bottom, 0px)); overflow-x: hidden; }
/* The auth screens (login, OTP, password) replace the whole shell — no tab bar,
   no rail — so body's room for that chrome is dead space that shoves the card
   off centre. See also the rail padding in the desktop query at the end. */
body:has(.auth-wrap) { padding-bottom: 0; }
/* A school signed in to a linked account gets a second row in the header (see
   .tacct), and --topbar-h is what everything that pins under the header
   measures. Declared on BODY rather than in the desktop query's :root because
   body is the ancestor of everything that reads it, so this one line is true at
   both widths — the desktop restatement drops the notch inset, which is already
   zero on a desk. */
body.has-acct-head { --topbar-h: calc(119px + env(safe-area-inset-top, 0px)); }

.loading {
  text-align: center;
  padding: 80px 20px;
  color: var(--text-dim);
}

/* ---------- Top bar ---------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  gap: 12px;
  background: var(--topbar-bg);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand-logo { height: 44px; width: 44px; object-fit: contain; flex-shrink: 0; }
/* Cluster the action icons together on the right edge of the header */
.topbar-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap; justify-content: flex-end; }
.logo {
  width: 40px; height: 40px;
  background: var(--brand-grad);
  border-radius: 12px;
  display: grid;
  place-items: center;
  font-size: 22px;
}
.topbar h1 { font-size: 17px; margin: 0; line-height: 1.1; }
.subtitle { font-size: 11px; color: var(--text-dim); margin: 2px 0 0; }
.icon-btn {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  width: 40px; height: 40px;
  border-radius: 10px;
  font-size: 18px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}
/* --surface-hi was never defined, so this fell back to .icon-btn's own
   background and the button sat dead under the cursor. See the hover layer at
   the foot of this file for the rest. */
.icon-btn:hover { background: var(--surface-2); border-color: var(--muted); }
.icon-btn:active { transform: scale(0.95); }

/* App top bar: the logo leads on the LEFT at a size that lets the chevrons
   read, with the company name as the headline beside it. The surface is a
   neutral grey (--chrome-*) — the logo supplies the only color up here. */
.topbar-app {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 12px;
  padding: 14px 16px;
  padding-right: 64px; /* room for the absolutely-positioned profile button */
  padding-top: calc(14px + env(safe-area-inset-top, 0px));
  background: var(--chrome-bg);
  backdrop-filter: none;
  border-bottom: 1px solid var(--chrome-border);
  /* Whose account the header is showing, when it is showing one — set once by
     the cat- classes below and read by the label and the tint, so a second
     declaration of the pink can't disagree with the first. Brand orange for an
     account that is neither half. */
  --cat: var(--accent);
}
.topbar-app.cat-boys { --cat: var(--cat-boys); }
.topbar-app.cat-girls { --cat: var(--cat-girls); }
/* The header's own first row. One wrapper for every role, so the school's
   second row can be a sibling of it rather than a special case inside it. */
.tbar-row { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }
.topbar-app .brand { gap: 13px; min-width: 0; flex: 1; }
.topbar-app .brand-logo { height: 46px; width: 46px; }
/* The office's own mark, beside the app's (Settings → Organization → Logo).
   It is drawn exactly as the app's mark is: same 46px, no rule between them
   and nothing boxing it in. A hairline separator sat here first, on the
   argument that two marks are two marks — but it framed the office's logo and
   left the app's bare, which reads as one of the two being an attachment.
   The gap says they're two. The width is capped and the mark is `contain`ed
   rather than cropped: a wordmark is wide, the company name beside it is the
   headline, and a logo that pushed the name off the header would be the wrong
   one of the two to keep. The HEIGHT stays 46px so --topbar-h, which is
   measured from it, is still true. */
.brand-marks { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.topbar-app .brand-logo-org { width: auto; max-width: 104px; }
.topbar-app .brand-text { min-width: 0; }
/* The company name is the headline. Truncate rather than wrap — carrier names
   get long and a two-line header shifts the whole app down. */
.topbar-app h1 {
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -0.015em;
  line-height: 1.15;
  color: var(--chrome-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar-app .subtitle {
  color: var(--chrome-dim);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Header profile avatar (opens the account / More screen) */
.topbar-profile {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  margin-top: calc(env(safe-area-inset-top, 0px) / 2);
  width: 38px; height: 38px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  background: var(--brand-grad);
  color: #fff;
  font-family: inherit;
  font-size: 16px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: filter 0.15s, transform 0.15s;
}
.topbar-profile:active { transform: translateY(-50%) scale(0.92); }
.topbar-profile.active { box-shadow: 0 0 0 2px var(--chrome-bg), 0 0 0 4px var(--accent); }

/* ---------- The school's account, in the header ----------
 *
 * A school's identity is the one piece of chrome in this app that is different
 * per user, and it used to be a card at the top of every screen they have. That
 * was the right fact drawn in the wrong place: it never changes between their
 * four screens, it is never what they scrolled to, and it was costing a phone
 * eighty pixels of the page it sat on. It is the header now — the ACCOUNT is
 * the headline and the carrier's name is the line under it, because two marks
 * to the left already say who they bus with and nothing else on the screen says
 * which of their two accounts this tab is on.
 *
 * The second row is where the account's own facts go — the grades that IDENTIFY
 * it since 0113, and what it owes. Given a row of its own rather than crammed
 * beside the name because a grade list is long ("Grade 5, Grade 6, Grade 7,
 * Grade 8") and the one thing it must not do is ellipsise: an account whose
 * grades are cut off is an account you cannot tell from its sibling, which is
 * the entire job of this block.
 *
 * THE COLOUR IS WHOSE IT IS. The tint washes the whole width of that row in the
 * boys' blue or the girls' pink — the same two the dispatch board, the run
 * badges and the season plans use — so the answer is readable from across a
 * room and from any screen, without reading a word. Falls back to the brand
 * orange, which is the one place orange means nothing but "no answer given". */
.topbar-app.topbar-acct-on {
  /* The rows carry the padding now, so the tinted one can run edge to edge. */
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding: 0;
}
.topbar-acct-on .tbar-row {
  padding: 14px 64px 14px 16px;
  padding-top: calc(14px + env(safe-area-inset-top, 0px));
}
/* The avatar is absolute to the header, so a header that has grown a second row
   would centre it between the two. Pin it to the first row instead: 14px of
   padding plus half the 8px it is shorter than the 46px logo beside it. */
.topbar-acct-on .topbar-profile {
  top: calc(18px + env(safe-area-inset-top, 0px));
  margin-top: 0;
  transform: none;
}
.topbar-acct-on .topbar-profile:active { transform: scale(0.92); }
/* Fixed height, because --topbar-h is a number and this row is part of it. */
.tacct {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 44px;
  box-sizing: border-box;
  padding: 0 16px;
  border-top: 1px solid var(--chrome-border);
  background: linear-gradient(to right,
    color-mix(in srgb, var(--cat) 13%, transparent),
    transparent 64%);
}
.tacct-sub {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  color: var(--chrome-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* What they owe, as the control that opens their bills. Orange because it is a
   button and not a status — the same reading as every other pressable thing in
   the app, and the reason a balance is never painted red here. */
.tacct-bal {
  flex: none;
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 5px 11px;
  border-radius: 999px;
  background: var(--accent-soft);
  border: 1px solid transparent;
  color: var(--accent);
  font-family: inherit;
  cursor: pointer;
}
.tacct-bal:hover { border-color: var(--accent); }
.tacct-bal-n { font-size: 14px; font-weight: 800; font-variant-numeric: tabular-nums; }
.tacct-bal-l {
  font-size: 10px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; opacity: 0.8;
}
/* SAID IN A WORD as well as in a colour. A colour alone is a code you have to
   have been taught — and it is invisible to anybody who can't separate the two,
   which is a real fraction of any roster. The word is the fact; the colour is
   what makes it findable across the room. Small, uppercase and tinted rather
   than filled: it is a label on the school's name, and a solid pink block
   beside that name would outrank the name. */
.brand-h { display: flex; align-items: center; gap: 8px; min-width: 0; }
.brand-h h1 { min-width: 0; }
.brand-cat {
  flex: none;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
  color: var(--cat);
  background: color-mix(in srgb, var(--cat) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--cat) 40%, transparent);
}

/* ---------- Tabs ---------- */
.tabs {
  position: sticky;
  /* Sits directly under the sticky app header — keep in step with
     .topbar-app's measured height (14+14 padding + 46px logo + 1px border)
     or the two overlap. */
  top: calc(75px + env(safe-area-inset-top, 0px));
  z-index: 40;
  display: flex;
  overflow-x: auto;
  padding: 10px 12px;
  gap: 8px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-dim);
  padding: 9px 14px;
  border-radius: 10px;
  font-size: 14px;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  cursor: pointer;
  position: relative;
  transition: all 0.15s;
}
.tab.active {
  background: var(--accent);
  color: white;
  border-color: var(--accent);
}
.tab .ti { font-size: 12px; }
.badge {
  display: inline-block;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 10px;
  font-size: 11px;
  font-weight: 700;
  background: var(--accent);
  color: #fff;
  text-align: center;
  line-height: 20px;
  margin-left: 4px;
}
/* On the active (accent-filled) tab, use a translucent badge so it stays visible */
.tab.active .badge { background: rgba(255, 255, 255, 0.28); color: #fff; }

/* ---------- Main ---------- */
main { padding: 16px; max-width: 900px; margin: 0 auto; }

.section-title {
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-dim);
  border-left: 3px solid var(--accent);
  padding-left: 10px;
  margin: 22px 0 12px;
}
.muted { color: var(--text-dim); }
.small { font-size: 12px; }
.empty {
  text-align: center;
  padding: 60px 20px;
  background: var(--surface);
  border-radius: var(--radius);
  color: var(--text-dim);
}

/* ---------- KPI cards ---------- */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  margin-bottom: 4px;
}
@media (min-width: 640px) { .kpi-grid { grid-template-columns: repeat(4, 1fr); } }
.kpi {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.kpi-label { font-size: 11px; text-transform: uppercase; color: var(--text-dim); letter-spacing: 0.5px; }
.kpi-value { font-size: 26px; font-weight: 700; }
.kpi.success { border-color: var(--success); }
.kpi.success .kpi-value { color: var(--success); }
.kpi.warning { border-color: var(--warning); }
.kpi.warning .kpi-value { color: var(--warning); }
.kpi.danger { border-color: var(--danger); }
.kpi.danger .kpi-value { color: var(--danger); }

/* ---------- Mechanic cards ---------- */
.mech-grid {
  display: grid;
  gap: 12px;
  grid-template-columns: 1fr;
}
@media (min-width: 640px) { .mech-grid { grid-template-columns: repeat(3, 1fr); } }
.mech-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius);
  padding: 14px;
}
.mech-card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.mech-card-head h3 { margin: 0; font-size: 16px; }
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
.mech-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 12px;
}
.mech-stats div { text-align: center; background: var(--bg); padding: 8px 4px; border-radius: 8px; }
.mech-stats span {
  display: block;
  font-size: 20px;
  font-weight: 700;
}
.mech-stats span.urgent { color: var(--danger); }
.mech-stats label { font-size: 10px; text-transform: uppercase; color: var(--text-dim); }

/* ---------- Turnaround by mechanic ---------- */
.ta-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 12px 16px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.ta-sub { font-size: 12px; color: var(--text-dim); margin-bottom: 6px; }
.ta-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
}
.ta-row:last-child { border-bottom: none; }
.ta-rank { width: 24px; text-align: center; font-size: 14px; flex: none; }
.ta-name {
  font-weight: 600;
  max-width: 38%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ta-bar {
  flex: 1;
  min-width: 24px;
  max-width: 180px;
  height: 8px;
  background: var(--bg-3);
  border-radius: 4px;
  overflow: hidden;
}
.ta-bar-fill { display: block; height: 100%; border-radius: 4px; }
.ta-days { font-weight: 700; font-size: 15px; margin-left: auto; flex: none; }
.ta-count { font-size: 12px; color: var(--text-dim); white-space: nowrap; flex: none; min-width: 52px; text-align: right; }
.ta-muted { font-size: 12px; color: var(--muted); margin-left: auto; }
.ta-fast { color: var(--success); }
.ta-mid  { color: var(--warning); }
.ta-slow { color: var(--danger); }
.ta-bar-fill.ta-fast { background: var(--success); }
.ta-bar-fill.ta-mid  { background: var(--warning); }
.ta-bar-fill.ta-slow { background: var(--danger); }

/* ---------- Fleet health: DOT / PM due-soon cards ---------- */
.due-grid { display: grid; grid-template-columns: 1fr; gap: 12px; margin-bottom: 16px; }
@media (min-width: 720px) { .due-grid { grid-template-columns: 1fr 1fr; } }
.due-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; }
.due-card h3 { margin: 0 0 10px; font-size: 15px; }
.due-row {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  width: 100%; text-align: left;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 10px; margin-bottom: 6px; cursor: pointer; color: var(--text); font-size: 13px;
}
.due-row:last-child { margin-bottom: 0; }
.due-row:hover { border-color: var(--accent); }
.due-bus { font-weight: 700; }
.due-when { color: var(--text-dim); }
.due-when.warn { color: var(--warning); }

/* ---------- Bus group ---------- */
.bus-group {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 14px;
  overflow: hidden;
}
.bus-group-head {
  padding: 10px 14px;
  background: var(--surface-2);
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 14px;
  border-bottom: 1px solid var(--border);
}
.bus-group-head .count {
  background: var(--accent);
  color: white;
  font-size: 12px;
  padding: 2px 9px;
  border-radius: 10px;
  font-weight: 700;
}

/* ---------- Request card ---------- */
.req-card {
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  border-left: 3px solid transparent;
}
.req-card:last-child { border-bottom: none; }
.req-card.priority-out_of_service { border-left-color: #000; background: rgba(220, 38, 38, 0.12); }
.req-card.priority-critical { border-left-color: var(--critical); background: rgba(220, 38, 38, 0.06); }
.req-card.priority-high { border-left-color: var(--danger); }
.req-card.priority-medium { border-left-color: var(--warning); }
.req-card.priority-low { border-left-color: var(--text-dim); }
.req-card.req-flash { animation: req-flash 2s ease-out; }
@keyframes req-flash {
  0%   { background: rgba(37, 99, 235, 0.28); }
  100% { background: transparent; }
}

.req-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; margin-bottom: 8px; }
.req-title { font-weight: 600; font-size: 15px; flex: 1; }
.req-meta { display: flex; gap: 6px; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
.req-desc { color: var(--text-dim); font-size: 13px; margin: 0 0 8px; line-height: 1.4; white-space: pre-wrap; }
.req-foot { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px; align-items: center; }
.req-info { font-size: 12px; display: flex; gap: 5px; align-items: center; flex-wrap: wrap; }
.req-actions { display: flex; flex-wrap: wrap; gap: 6px; }

.prio {
  font-size: 10px;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 6px;
  font-weight: 700;
  letter-spacing: 0.5px;
}
.prio-out_of_service { background: #111827; color: #fca5a5; border: 1px solid var(--critical); }
.prio-critical { background: var(--critical); color: white; }
.prio-high { background: var(--danger); color: white; }
.prio-medium { background: var(--warning); color: #1f2937; }
.prio-low { background: var(--bg-3); color: var(--text-dim); }

.status {
  font-size: 10px;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 6px;
  font-weight: 600;
  border: 1px solid var(--border);
  color: var(--text-dim);
}
.status-open { color: var(--accent); border-color: var(--accent); }
.status-in_progress { color: var(--warning); border-color: var(--warning); }
.status-completed { color: var(--success); border-color: var(--success); }

.age {
  font-size: 11px;
  padding: 3px 7px;
  border-radius: 6px;
  font-weight: 700;
  background: var(--bg-3);
}
.age-ok { color: var(--text-dim); }
.age-warn { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
.age-bad { background: rgba(239, 68, 68, 0.2); color: var(--danger); }

.chip {
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 10px;
  border: 1px solid var(--border);
  font-weight: 600;
}
.chip-unassigned { background: var(--bg-3); color: var(--text-dim); }

/* ---------- Open Jobs — collapsed mechanic rows → per-mechanic dialog ---------- */
.jobs-rows { display: flex; flex-direction: column; gap: 10px; }
.jobs-empty {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px 16px;
  text-align: center;
  color: var(--success);
  font-weight: 700;
}

/* Mechanic card (main screen): avatar, name + one-line status, chevron */
.mech-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 12px 14px;
  cursor: pointer;
  font-family: inherit;
  color: var(--text);
  text-align: left;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.mech-row:active { background: var(--surface-2); }
.mech-row-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: #fff;
  font-weight: 800;
  font-size: 16px;
  flex-shrink: 0;
}
.mech-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.mech-row-name { font-weight: 700; font-size: 15px; }
/* Right-side turnaround rank + avg — an emphasized badge to draw the eye */
.mech-row-rank {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 5px 10px;
  min-width: 46px;
}
.rank-mark { font-size: 20px; line-height: 1; }
.rank-mark.num { font-size: 15px; font-weight: 800; color: var(--text-dim); }
.rank-avg { font-size: 11px; font-weight: 800; color: var(--accent); line-height: 1; white-space: nowrap; }
.mech-row-arrow { color: var(--muted); font-size: 18px; flex-shrink: 0; }

/* One-line status: leading urgency dot, bold numbers, rank medal, tinted age */
.mstat-line { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; }
.mstat-line b { color: var(--text); font-weight: 700; }
.s-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 7px;
  vertical-align: middle;
}
.mstat-line .s-bad { color: var(--danger); font-weight: 700; }
.mstat-line .s-warn { color: var(--warning); font-weight: 700; }

/* Per-mechanic dialog — a large, near-full-height sheet */
.modal:has(.mech-modal) {
  max-width: 720px;
  height: 92vh;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.mech-modal { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.mech-modal-head {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 14px;
  flex-shrink: 0;
}
.mech-modal-title { margin: 0; font-size: 18px; font-weight: 800; flex: 1; min-width: 0; }
.mech-modal-x {
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: 14px;
  cursor: pointer;
  flex-shrink: 0;
}
.mech-modal-body { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; }

/* In-dialog New / Working / Completed tabs (swipeable) */
.dlg-tabs {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  gap: 4px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px;
}
.dlg-tab {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  border: none;
  background: none;
  border-radius: 999px;
  /* Three tabs share the row now — keep each on one line on a narrow phone. */
  padding: 8px 6px;
  min-width: 0;
  white-space: nowrap;
  font-family: inherit;
  font-weight: 700;
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
}
.dlg-tab.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); }
.dlg-tab-n {
  font-size: 11px;
  font-weight: 700;
  background: var(--bg-3);
  color: var(--text-dim);
  padding: 1px 7px;
  border-radius: 999px;
}
.dlg-tab.active .dlg-tab-n { background: var(--accent); color: #fff; }
@media (max-width: 380px) {
  .dlg-tab { font-size: 12px; padding: 8px 4px; }
  .dlg-tab-n { padding: 1px 5px; }
}
.dlg-panel { display: flex; flex-direction: column; gap: 14px; }

/* Open-tab toolbar: bus-number search + sort dropdown. The dialog is a modal,
   where `.modal input/select` force width:100% — reset that so the two controls
   sit side by side (search grows, sort stays content-width). */
.jobs-toolbar { display: flex; gap: 8px; align-items: center; }
.jobs-toolbar .search-input { width: auto; min-width: 0; flex: 1 1 auto; padding: 9px 14px; font-size: 13px; }
.jobs-toolbar .sort-select { width: auto; flex: 0 0 auto; }
#jobs-groups { display: flex; flex-direction: column; gap: 14px; }

/* Completed-tab time buckets (collapsible; recent open by default) */
.cbucket { display: flex; flex-direction: column; gap: 12px; }
.cbucket-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 11px 13px;
  cursor: pointer;
  font-family: inherit;
  color: var(--text);
  font-weight: 700;
  font-size: 14px;
  text-align: left;
}
.cbucket-head:active { background: var(--bg-3); }
.cbucket-chev { color: var(--muted); font-size: 11px; width: 12px; text-align: center; flex-shrink: 0; }
.cbucket-label { flex: 1; min-width: 0; }
.cbucket-count {
  background: var(--bg-3);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 700;
  min-width: 22px;
  text-align: center;
  padding: 1px 7px;
  border-radius: 999px;
}
.cbucket-body { display: flex; flex-direction: column; gap: 14px; }
.dlg-panel.slide-in-right { animation: dlg-in-right 0.18s ease-out; }
.dlg-panel.slide-in-left { animation: dlg-in-left 0.18s ease-out; }
@keyframes dlg-in-right { from { transform: translateX(28px); opacity: 0.35; } to { transform: none; opacity: 1; } }
@keyframes dlg-in-left  { from { transform: translateX(-28px); opacity: 0.35; } to { transform: none; opacity: 1; } }

/* Contained per-bus box */
.bus-box {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 10px;
}
.bus-box-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 4px 9px;
}
.bus-box-num { font-weight: 800; font-size: 13px; color: var(--text); }
.bus-box-status {
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--sc);
  background: color-mix(in srgb, var(--sc) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--sc) 45%, transparent);
  padding: 1px 7px;
  border-radius: 8px;
}
.bus-box-model {
  font-size: 11px;
  color: var(--muted);
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bus-box-count {
  background: var(--bg-3);
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 700;
  min-width: 18px;
  text-align: center;
  padding: 1px 6px;
  border-radius: 9px;
}
.bus-box-cards { display: flex; flex-direction: column; gap: 8px; }

.jobs-col-more { padding: 6px; text-align: center; color: var(--muted); font-size: 11px; }

/* Clean rounded card */
.job-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border);
  border-radius: 14px;
  padding: 11px 13px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.job-card.priority-out_of_service { border-left-color: var(--critical); }
.job-card.priority-critical { border-left-color: var(--critical); }
.job-card.priority-high { border-left-color: var(--danger); }
.job-card.priority-medium { border-left-color: var(--warning); }
.job-card.priority-low { border-left-color: var(--muted); }
.job-card.req-flash { animation: req-flash 2s ease-out; }
/* Inside a bus box the cards are flatter (the box provides the containment). */
.bus-box-cards .job-card { box-shadow: none; border-radius: 10px; background: var(--surface); }

.job-card-tap {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  color: var(--text);
  text-align: left;
}
.job-card-title {
  font-weight: 700;
  font-size: 14px;
  width: 100%;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.job-card-age {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: var(--text-dim);
  background: var(--bg-3);
  padding: 4px 8px;
  border-radius: 999px;
  flex-shrink: 0;
}
.job-card-age.age-warn { color: var(--warning); background: rgba(224, 160, 18, 0.16); }
.job-card-age.age-bad { color: var(--danger); background: rgba(239, 68, 68, 0.16); }
.job-card-age-done { color: var(--success); background: rgba(34, 197, 94, 0.14); text-transform: none; }
.job-card-line2 { display: flex; align-items: center; gap: 6px; min-width: 0; }
.job-card-sub {
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-transform: capitalize;
}

.prio-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--muted); }
.prio-dot.prio-out_of_service,
.prio-dot.prio-critical { background: var(--critical); }
.prio-dot.prio-high { background: var(--danger); }
.prio-dot.prio-medium { background: var(--warning); }
.prio-dot.prio-low { background: var(--muted); }

/* One-tap primary action on the card face */
.job-card-cta { flex: 0 0 auto; }
.job-cta {
  font-size: 12px;
  font-weight: 700;
  padding: 7px 12px;
  border-radius: 9px;
  border: 1px solid transparent;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
}
.job-cta:active { transform: scale(0.96); }
.job-cta-go { background: var(--accent); color: #fff; }
.job-cta-done { background: var(--success); color: #fff; }
.job-cta-ghost { background: var(--surface-2); color: var(--text); border-color: var(--border); }

/* Inline-expanded body (full width below the card face) */
.job-card-expand {
  flex-basis: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 2px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.job-card-expand .req-desc { margin: 0; }
.job-card-expand .req-info { font-size: 12px; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.job-card-expand .req-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.req-completion { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 12px; }
.rc-chip {
  display: inline-flex;
  align-items: center;
  font-weight: 700;
  color: var(--text);
  background: var(--bg-3);
  padding: 2px 9px;
  border-radius: 999px;
  white-space: nowrap;
}
.rc-parts { color: var(--text-dim); }

/* ---------- Buttons ---------- */
.btn-primary, .btn-success, .btn-ghost, .btn-outline, .btn-danger-ghost {
  font-size: 12px;
  padding: 7px 11px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  cursor: pointer;
  border: 1px solid transparent;
  transition: all 0.15s;
  font-family: inherit;
}
/* The primary action carries the logo's two-tone orange. */
.btn-primary { background: var(--brand-grad); color: white; }
.btn-primary:active { transform: scale(0.97); }
.btn-success { background: var(--success); color: white; }
.btn-success:active { transform: scale(0.97); }
.btn-ghost { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.btn-ghost:active { transform: scale(0.97); }
.btn-outline { background: transparent; color: var(--text); border-color: var(--border); }
.btn-outline.w-full { width: 100%; }
.btn-danger-ghost { background: transparent; color: var(--danger); border-color: var(--danger); }

/* Keyboard focus — a brand-tinted ring, consistent across every control. */
:where(button, a, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---------- Alerts panel (Fleet tab) ---------- */
.alerts-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  margin-bottom: 16px;
}
.alerts-head {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.alerts-head h2 {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.3px;
}
.alerts-counts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.alerts-block {
  margin-top: 12px;
}
.alerts-block:first-of-type { margin-top: 0; }
.alerts-block h3 {
  margin: 0 0 8px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-dim);
  font-weight: 700;
}
.alerts-critical h3 {
  color: var(--critical);
  background: rgba(220, 38, 38, 0.12);
  border: 1px solid rgba(220, 38, 38, 0.35);
  padding: 8px 10px;
  border-radius: 8px;
  animation: pulse-bg 2s ease-in-out infinite;
}
.alerts-warn h3 { color: var(--warning); }

@keyframes pulse-bg {
  0%, 100% { background: rgba(220, 38, 38, 0.12); }
  50%      { background: rgba(220, 38, 38, 0.25); }
}

.alert-row {
  display: grid;
  grid-template-columns: 24px 52px 1fr auto 14px;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 10px;
  margin-bottom: 5px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: 8px;
  font-family: inherit;
  font-size: 13px;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: transform 0.1s, background 0.15s;
}
.alert-row:hover { background: var(--surface-2); }
.alert-row:active { transform: scale(0.99); }
.alert-row.alert-critical {
  border-left-color: var(--critical);
  background: rgba(220, 38, 38, 0.08);
  font-weight: 600;
}
.alert-row.alert-warn { border-left-color: var(--warning); }
.alert-row.alert-soon { border-left-color: var(--accent); }

.alert-icon { font-size: 16px; }
.alert-bus { font-weight: 700; color: var(--text); }
.alert-type {
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.alert-label {
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  text-align: right;
}
.alert-row.alert-critical .alert-label { color: var(--critical); }
.alert-row.alert-warn .alert-label { color: var(--warning); }
.alert-row.alert-soon .alert-label { color: var(--accent); }
.alert-arrow { color: var(--text-dim); }

.empty-mini {
  text-align: center;
  padding: 18px;
  color: var(--success);
  font-weight: 600;
}

/* Critical bus card */
.bus-card-critical {
  border-color: var(--critical) !important;
  box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.4);
}
.dot-cell-critical {
  background: rgba(220, 38, 38, 0.18) !important;
  border: 1px solid var(--critical);
}
.bus-card-flash {
  animation: card-flash 1.5s ease;
}
@keyframes card-flash {
  0%   { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); }
  50%  { box-shadow: 0 0 0 8px rgba(59, 130, 246, 0); }
  100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
}

/* ---------- Fleet / Bus ---------- */
.bus-list {
  display: grid;
  gap: 12px;
  grid-template-columns: 1fr;
}
@media (min-width: 720px) { .bus-list { grid-template-columns: repeat(2, 1fr); } }
.bus-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
}
.bus-card header { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.bus-card h3 { margin: 0; font-size: 17px; }
.pill {
  font-size: 11px;
  padding: 4px 9px;
  border-radius: 10px;
  text-transform: uppercase;
  font-weight: 700;
}
.pill-ok { background: rgba(16, 185, 129, 0.15); color: var(--success); }
.pill-warn { background: rgba(245, 158, 11, 0.15); color: var(--warning); }
.pill-bad { background: rgba(239, 68, 68, 0.15); color: var(--danger); }
.bus-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin: 12px 0;
}
.bus-grid > div { background: var(--bg); padding: 8px 10px; border-radius: 8px; }
.bus-grid label { font-size: 10px; text-transform: uppercase; color: var(--text-dim); display: block; }
.bus-grid span { font-size: 14px; font-weight: 600; }
.bus-grid .ok { color: var(--success); }
.bus-grid .warn { color: var(--warning); }
.bus-grid .bad { color: var(--danger); }

.pm-section {
  background: var(--bg);
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 12px;
}
.pm-section h4 {
  margin: 0 0 8px;
  font-size: 11px;
  text-transform: uppercase;
  color: var(--text-dim);
  letter-spacing: 0.5px;
}
.pm-row {
  display: flex;
  justify-content: space-between;
  padding: 5px 0;
  font-size: 13px;
  border-bottom: 1px dashed var(--border);
}
.pm-row:last-child { border-bottom: none; }
.pm-overdue { color: var(--danger); font-weight: 600; }
.pm-soon { color: var(--warning); font-weight: 600; }
.pm-missing { color: var(--text-dim); font-style: italic; }
.pm-section h4 .muted { text-transform: none; letter-spacing: 0; font-weight: 400; }
.seasonal-tag {
  display: inline-block;
  font-size: 9px;
  padding: 1px 6px;
  border-radius: 6px;
  background: rgba(59, 130, 246, 0.18);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-left: 6px;
  vertical-align: middle;
  font-weight: 700;
}

.bus-actions { display: flex; gap: 8px; }
.bus-actions > * { flex: 1; }

/* ---------- FAB ---------- */
.fab {
  position: fixed;
  bottom: calc(92px + env(safe-area-inset-bottom, 0px));
  right: 18px;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--accent);
  color: white;
  font-size: 28px;
  border: none;
  box-shadow: 0 8px 20px rgba(20, 45, 25, 0.4);
  cursor: pointer;
  z-index: 45;
  /* Same reason as .bottom-nav: stay composited so the swipe transform on
     #view doesn't make this fixed button flicker out. */
  will-change: transform;
}
.fab:active { transform: scale(0.94); }

/* ---------- Bottom navigation (app-style tab bar) ---------- */
.bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 48;
  display: flex;
  justify-content: center;
  align-items: stretch;
  gap: 6px;
  padding: 10px 4px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  background: var(--chrome-bg);
  border-top: 1px solid var(--chrome-border);
  /* Keep the bar on its own GPU layer. Without this, WebKit/iOS drops a
     fixed element from compositing while a sibling (#view) is mid-transform
     during a swipe — the footer vanishes and only "blinks" back on repaint. */
  transform: translateZ(0);
  will-change: transform;
}
.nav-item {
  position: relative;
  flex: 0 0 66px;
  max-width: 78px;
  background: none;
  border: none;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 2px 2px;
  color: var(--chrome-text);
  font-family: inherit;
  transition: color 0.15s;
  min-width: 0;
}
/* The highlight bubble lives on the icon only (WhatsApp-style): a wide,
   stretched rounded-rectangle behind the icon — never behind the label.
   Icons are always full color on every tab. */
.nav-item .ni-icon {
  font-size: 21px;
  line-height: 1;
  filter: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 18px;
  border-radius: 14px;
  transition: background 0.15s, transform 0.15s;
}
.nav-item:active .ni-icon { transform: scale(0.92); }
.nav-item .ni-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* Active state is shown ONLY by the bubble behind the icon — the label and
   icon colors stay the same on every tab. */
.nav-item.active .ni-icon { background: var(--chrome-active-bg); }
/* Count bubble, pinned to the top-right of the icon bubble. The 2px ring in the
   chrome color cuts it cleanly off the icon underneath instead of letting the
   two collide. */
.nav-item .ni-badge {
  position: absolute;
  top: 1px;
  left: calc(50% + 8px);
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 800;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  background: var(--badge-bg);
  color: var(--badge-fg);
  box-shadow: 0 0 0 2px var(--chrome-bg);
}
.nav-item .ni-badge.danger { background: var(--danger); color: #fff; }

/* Pointer feedback on the chrome. The bubble is the tab's own language, so a
   hover just previews it at half strength — on the tab you're already on there
   is nothing to preview, so it stays put. */
@media (hover: hover) and (pointer: fine) {
  .nav-item:not(.active):hover .ni-icon { background: var(--bg-3); }
  .nav-item:hover .ni-label { color: var(--accent); }
  .topbar-profile:hover { filter: brightness(1.1); }
}

/* On a phone the bar carries only the sections marked for it; the rest are
   reached through the index. (The desktop rail below flips this round.) */
.nav-item[data-bar="0"] { display: none; }

/* ---------- Hold a tab and drag it to move it ----------
   The bar is neither a scroll surface nor a swipe surface, so the tabs own the
   touch outright: `touch-action: none` is what stops the page scrolling out
   from under a drag that starts here. Setting it once a hold completes is too
   late — a browser fixes the gesture's meaning at the first touch.

   These rules must stay AFTER `.nav-item:active .ni-icon`: the two have the
   same specificity, so the lift only beats the press-in by coming later. */
.nav-item { touch-action: none; }
.bottom-nav.reordering { cursor: grabbing; }
/* The tab in your hand is off the page: the bubble grows and lifts. The rest
   slide past it as the bar reorders under the finger. The accent tint is the
   same one .dp-row.dragging uses — "held" is one idea in this app, and the tab
   bar shouldn't spell it differently. It has to be a tint the ACTIVE tab isn't
   already wearing, or picking a tab up looks like merely selecting it. */
.nav-item.dragging .ni-icon {
  background: color-mix(in srgb, var(--accent) 24%, transparent);
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  transform: scale(1.14);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
}
.nav-item.dragging .ni-label { color: var(--accent); font-weight: 700; }


/* ---------- "More" screen ---------- */
.more-account {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 18px;
}
.more-avatar {
  width: 48px; height: 48px;
  border-radius: 50%;
  background: var(--brand-grad);
  color: #fff;
  display: grid;
  place-items: center;
  font-size: 20px;
  font-weight: 700;
  flex-shrink: 0;
}
.more-account-name { font-size: 16px; font-weight: 600; }
.more-account-role { font-size: 13px; color: var(--text-dim); text-transform: capitalize; }
.more-section-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  margin: 16px 4px 8px;
}
.more-group {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 6px;
}
.more-row {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 14px 16px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  text-align: left;
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  text-decoration: none;
}
.more-group .more-row:last-child { border-bottom: none; }
.more-row:active { background: var(--surface-2); }
.more-row-icon { font-size: 20px; width: 24px; text-align: center; flex-shrink: 0; }
.more-row-body { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.more-row-title { font-weight: 600; }
.more-row-sub { font-size: 12px; color: var(--text-dim); }
.more-row-chevron { color: var(--muted); font-size: 20px; flex-shrink: 0; }
.more-row-danger .more-row-title { color: var(--danger); }
.more-row-danger .more-row-icon { color: var(--danger); }
/* What's waiting inside a section, on its row in the index. Sits before the
   chevron so the row still reads "name — what's in it — how much of it". */
.more-row-count {
  flex-shrink: 0;
  min-width: 20px;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  text-align: center;
}
/* A section row names its children; on a narrow screen that list is the first
   thing worth clipping, and clipping it mid-word is fine — it's a preview. */
.more-row-sub {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- Modal ---------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 100;
  display: grid;
  place-items: center;
  padding: 16px;
  backdrop-filter: blur(4px);
}
.modal {
  background: var(--modal-bg);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 18px;
  width: 100%;
  max-width: 420px;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: var(--shadow);
}

/* Animated "Saved" success state shown inside the Save & Upload dialog. */
.modal-saved { text-align: center; max-width: 360px; }
.save-success {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 18px 8px 10px; animation: save-pop .3s ease both;
}
@keyframes save-pop { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: scale(1); } }
.save-check { width: 84px; height: 84px; }
.save-check svg { width: 100%; height: 100%; }
.save-check-circle {
  stroke: var(--accent); stroke-width: 3; stroke-linecap: round;
  stroke-dasharray: 151; stroke-dashoffset: 151;
  animation: save-stroke .5s cubic-bezier(.65,0,.45,1) .05s forwards;
}
.save-check-mark {
  stroke: var(--accent); stroke-width: 4; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 36; stroke-dashoffset: 36;
  animation: save-stroke .35s cubic-bezier(.65,0,.45,1) .45s forwards;
}
@keyframes save-stroke { to { stroke-dashoffset: 0; } }
.save-success-text {
  font-size: 22px; font-weight: 700; color: var(--text); letter-spacing: .3px;
  opacity: 0; animation: save-fade .3s ease .5s forwards;
}
.save-success .muted { opacity: 0; animation: save-fade .3s ease .65s forwards; }
.save-done-btn { margin-top: 14px; min-width: 140px; padding: 10px 18px; border-radius: 999px; opacity: 0; animation: save-fade .3s ease .8s forwards; }
@keyframes save-fade { to { opacity: 1; } }

/* Confirmation toast (e.g. "✓ Saved") — floats above everything, auto-dismisses */
.toast {
  position: fixed;
  left: 50%;
  bottom: 32px;
  transform: translate(-50%, 16px);
  z-index: 9000;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 18px;
  border-radius: 999px;
  background: var(--accent, #ea580c);
  color: #fff;
  font-weight: 600;
  font-size: 14px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.toast-show {
  opacity: 1;
  transform: translate(-50%, 0);
}
.toast-check {
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.25);
  font-size: 13px;
  line-height: 1;
}

/* An undo toast holds a button, so it has to be reachable — the plain toast is
   pointer-events:none so it can never swallow a tap meant for the screen. */
.toast-undo {
  pointer-events: auto;
  position: fixed;
  padding-right: 6px;
  padding-bottom: 13px;   /* room for the countdown bar */
  overflow: hidden;
  max-width: min(440px, calc(100vw - 24px));
}
.toast-msg { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The button. A filled pill on the toast's own colour — high enough contrast to
   read as the one thing here you can press, without shouting over the message. */
.toast-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: 4px;
  padding: 7px 15px;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.94);
  color: var(--accent, #ea580c);
  font: inherit;
  font-weight: 700;
  font-size: 13.5px;
  letter-spacing: 0.01em;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s ease, transform 0.12s ease, box-shadow 0.15s ease;
}
.toast-btn:hover { background: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18); }
.toast-btn:active { transform: scale(0.95); }
.toast-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.toast-btn:disabled { opacity: 0.6; cursor: default; transform: none; }
.toast-btn-icon { font-size: 15px; line-height: 1; }

/* Countdown: the window draining away, so nobody has to guess how long the
   offer stands. Bottom edge, full width, no layout cost. */
.toast-bar {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  width: 100%;
  transform-origin: left center;
  background: rgba(255, 255, 255, 0.55);
  animation: toast-drain linear forwards;
}
@keyframes toast-drain { from { transform: scaleX(1); } to { transform: scaleX(0); } }

/* Something went wrong — same toast, different reading. */
.toast-err { background: var(--danger, #dc2626); }
.toast-err .toast-btn { color: var(--danger, #dc2626); }

@media (prefers-reduced-motion: reduce) {
  .toast { transition: opacity 0.01ms; }
  /* The bar still has to run — it's the timer, not decoration — but it can do
     it as a fade rather than a slide. */
  .toast-bar { animation-name: toast-drain-fade; }
  @keyframes toast-drain-fade { from { opacity: 0.55; } to { opacity: 0; } }
}
.modal h3 { margin: 0 0 12px; font-size: 18px; }
/* Driver & bus detail get a wider modal so info/files lay out in columns */
.modal:has(.driver-detail) { max-width: 780px; }
/* A label with no control under it is a group heading ("Address"), so it keeps
 * the small uppercase caption. Labels that DO head a control get notched into
 * the box instead — see "Field names sit in the box" below. */
.modal label { display: block; margin-top: 18px; margin-bottom: 7px; font-size: 12px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; }
.modal label:first-of-type { margin-top: 4px; }
.modal { --notch-bg: var(--modal-bg); }
/* A checkbox line is a statement you tick, not a field name — sentence case,
 * not the uppercase caption every other label wears. Every call site used to
 * inline these three rules; they live here so the ticks all match. */
.modal label.check-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--text);
  text-transform: none;
  letter-spacing: 0;
}
.modal label.check-row input[type="checkbox"] { width: auto; flex: none; margin: 0; }
.modal input, .modal select, .modal textarea {
  width: 100%;
  /* Outlined, not filled: the notched label has to sit ON the border, so the
     box can't be a different colour from what's behind it. */
  background: var(--modal-bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 11px;
  border-radius: 8px;
  font-size: 14px;
  font-family: inherit;
}
.modal input:focus, .modal select:focus, .modal textarea:focus { border-color: var(--accent); }

/* A value that's chosen in a search sheet rather than typed or dropped down
   (the bus on a swap request). It has to wear the same box as the fields above
   and below it — same padding, same radius, same notched label — or the one
   field you tap through reads as a stray button in the middle of the form. */
.modal .pickfield {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: var(--modal-bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 11px;
  border-radius: 8px;
  font-size: 14px;
  font-family: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.modal .pickfield:hover { border-color: var(--muted); }
.modal .pickfield:focus-visible { border-color: var(--accent); outline: none; }
.pickfield-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pickfield-val { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pickfield.empty .pickfield-val { font-weight: 500; color: var(--text-dim); }
.pickfield-sub { font-size: 12.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pickfield-caret { flex: none; color: var(--text-dim); font-size: 12px; }

/* ── Field names sit in the box ────────────────────────────────────────────
 * The field name is notched into the top edge of its own control, the way
 * most modern apps label a field, rather than floating above it as a caption.
 * It doesn't animate on focus: every field here has a name worth showing even
 * when it's empty, so the label just lives on the border — and it stays at
 * 13px, close enough to the 14px value below it to read at arm's length.
 *
 * No markup changes needed: the label is inline-block so its background paints
 * over only its own slice of the line, and a negative bottom margin drops it
 * onto the border of whatever follows. `:has()` keeps this off the labels that
 * head a group of fields rather than a single control — those would otherwise
 * float over nothing. `--notch-bg` is what's behind the control, set per
 * surface (dialogs are a different colour from cards).
 */
.modal label:not(.check-row):is(:has(+ input), :has(+ select), :has(+ textarea), :has(+ .addr-autosuggest), :has(+ .pickfield)),
.addr-fields .af-lbl,
.blbl:is(:has(+ input), :has(+ select), :has(+ textarea)),
.org-field-label {
  /* Block, not inline-block: a negative margin under an inline box can't pull
     the control up, because the line box it sits in keeps its own height.
     fit-content is what stops the background covering the whole border. */
  display: block;
  width: fit-content;
  align-self: flex-start;      /* a flex column would stretch it over the whole border */
  position: relative;
  z-index: 1;
  max-width: calc(100% - 20px);
  margin: 16px 0 -8px 10px;
  padding: 1px 6px;
  background: var(--notch-bg, var(--surface));
  font-size: 13px;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-dim);
  pointer-events: none;        /* the chip overlaps the box — let clicks through */
}
.modal label:is(:has(+ input:focus), :has(+ select:focus), :has(+ textarea:focus), :has(+ .addr-autosuggest input:focus), :has(+ .pickfield:focus-visible)),
.addr-fields .af:focus-within .af-lbl,
.blbl:is(:has(+ input:focus), :has(+ select:focus), :has(+ textarea:focus)),
.org-field:focus-within .org-field-label { color: var(--accent); }
.modal-actions {
  display: flex;
  gap: 8px;
  margin-top: 16px;
  justify-content: flex-end;
}
.modal-actions button { padding: 10px 16px; font-size: 14px; }

/* Two-up rows inside modals (used by the requirement editor) */
.form-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.form-row-2 > div { min-width: 0; }

/* Where a bus count came from, under the box the head count filled in
   (attachBusSuggest). It is only ever on screen while the figure IS the
   arithmetic's — typing over it takes the line away, since it would then be
   describing a number nobody put there. */
.bus-suggest { display: block; margin: 5px 2px 0; line-height: 1.4; }

/* Appearance chooser (Auto / Day / Night) — selectable option rows */
.theme-picker { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.theme-opt {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 12px 14px; text-align: left; cursor: pointer;
  background: var(--card); color: var(--text);
  border: 1.5px solid var(--border); border-radius: 12px;
  -webkit-tap-highlight-color: transparent; transition: border-color .12s, background .12s;
}
.theme-opt:hover { border-color: var(--muted); }
.theme-opt.sel { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--card)); }
.theme-opt-icon { font-size: 20px; width: 26px; text-align: center; flex-shrink: 0; }
.theme-opt-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.theme-opt-title { font-weight: 700; font-size: 15px; }
.theme-opt-sub { font-size: 12.5px; color: var(--muted); }
.theme-opt-check { margin-left: auto; color: var(--accent); font-weight: 800; font-size: 16px; opacity: 0; flex-shrink: 0; }
.theme-opt.sel .theme-opt-check { opacity: 1; }

/* Notifications modal (More → Notifications). */
.push-steps { margin: 8px 0 12px; padding-left: 20px; font-size: 14px; line-height: 1.65; }
.push-steps li::marker { color: var(--accent); }
.push-steps li { margin-bottom: 4px; }
/* Reserve the line so the modal doesn't jump when a status message appears. */
.push-msg { min-height: 18px; margin: 4px 0 2px; }

/* The modal now answers two questions — what this DEVICE does, and how the
   ACCOUNT is reached — so each gets a titled block with a rule between them.
   Without the separation the channel chips read as more device settings, and
   turning "Push" off for a category would look like turning the phone off. */
.notif-block { padding: 4px 0 2px; }
.notif-block + .notif-block { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.notif-block-head {
  font-size: 11px; font-weight: 800; letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--muted); margin-bottom: 8px;
}
.notif-device-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 2px; }

/* The categories fall into runs — Trips, Drivers, Fleet — so an office with six
   of them has three places to aim at instead of one column to read end to end.
   The heading is a quiet caption; the rows under it do the work. */
.notif-group + .notif-group { margin-top: 14px; }
.notif-group-head {
  font-size: 11px; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--accent); margin: 0 0 2px;
}

/* Label left, channels right, one line each. These were fat filled pills — 30px
   of orange lozenge per channel, three or four to a row, seven rows — and a
   screen of them read as a wall of buttons rather than a list of answers you
   scan down. A tick is the smallest thing that can say on or off. */
.notif-pref {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 14px; flex-wrap: wrap; padding: 7px 0;
}
.notif-pref + .notif-pref { border-top: 1px solid var(--border); }
.notif-pref-label { font-weight: 600; font-size: 14px; }

.notif-chs { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.notif-ch {
  display: inline-flex; align-items: center; gap: 6px;
  border: 0; background: none; padding: 2px 0; cursor: pointer;
  font-size: 12.5px; font-weight: 550; color: var(--muted);
  transition: color 0.15s ease;
}
.notif-tick {
  width: 15px; height: 15px; border-radius: 4px; flex-shrink: 0;
  border: 1.5px solid var(--border); background: var(--surface);
  transition: background 0.15s ease, border-color 0.15s ease;
}
.notif-ch.on { color: var(--text); }
.notif-ch.on .notif-tick {
  background: var(--accent); border-color: var(--accent);
  /* The check is drawn rather than typed: a glyph would sit on the text
     baseline and no font metric puts it in the middle of a 15px box. */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M3.5 8.6l3 3 6-6.6' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-size: 15px 15px;
}
.notif-ch:disabled { opacity: 0.4; cursor: not-allowed; }
/* Off is an action, not a fourth channel, so it's plain text — and lit it goes
   grey rather than brand-orange, since orange on this row means "you'll be
   told", which is the one thing it must never say. */
.notif-off {
  border: 0; background: none; padding: 2px 0; cursor: pointer;
  font-size: 12.5px; font-weight: 550; color: var(--muted); opacity: 0.75;
}
.notif-off.on { opacity: 1; font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }

/* Admin page — a Settings-style category menu that drills into one section.
   Panes are shown one at a time via the [hidden] attribute (toggled in JS). */
.admin-menu {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 10px;
}
/* [hidden] must beat the explicit display rules above/below it. */
.admin-menu[hidden],
.admin-detail[hidden],
.admin-pane[hidden] { display: none; }
.admin-menu-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  color: var(--text);
  cursor: pointer;
  /* The row is the affordance — an underline through the title AND its
     description turns a tidy list into a wall of blue-ish rules. Matters
     wherever this class is an <a> rather than a <button>. */
  text-decoration: none;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
  transition: border-color 0.12s ease, transform 0.12s ease, box-shadow 0.12s ease;
}
/* Gated on a real pointer: unqualified, a tap on a phone leaves the row lifted
   and ringed until you touch something else. */
@media (hover: hover) and (pointer: fine) {
  .admin-menu-item:hover {
    border-color: var(--accent);
    transform: translateY(-1px);
    box-shadow: var(--shadow);
  }
  .admin-menu-item:hover .admin-menu-chev { color: var(--accent); transform: translateX(2px); }
}
.admin-menu-item:active { transform: scale(0.995); }
.admin-menu-ico {
  font-size: 22px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  border-radius: 10px;
  flex-shrink: 0;
}
.admin-menu-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.admin-menu-title { font-weight: 600; font-size: 15px; }
.admin-menu-sub { color: var(--text-dim); font-size: 12px; }
.admin-menu-chev {
  color: var(--muted); font-size: 22px; line-height: 1; flex-shrink: 0;
  transition: color 0.12s ease, transform 0.12s ease;
}
.admin-back {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  padding: 4px 0;
  margin-bottom: 4px;
}
.admin-back:hover { text-decoration: underline; }

/* Photo picker: file input + camera button */
.photo-pick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.photo-pick input[type="file"] { flex: 1; min-width: 0; }

/* Add-choice menu (FAB) */
.add-choice { display: flex; flex-direction: column; gap: 10px; margin: 12px 0; }
.choice-btn {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 14px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface-2); color: var(--text); cursor: pointer;
}
.choice-btn:hover { border-color: var(--accent); }
.choice-icon { font-size: 24px; flex: none; width: 34px; text-align: center; }
.choice-text { display: flex; flex-direction: column; gap: 2px; }
.choice-text strong { font-size: 15px; }
.choice-text small { color: var(--text-dim); font-size: 12px; }

/* Bulk import editable grid */
/* The driver grid carries the whole record — two dozen columns — so this dialog
   takes whatever width the window has rather than the standard 420px. */
.modal:has(.bulk-grid) { max-width: 1180px; width: 96vw; }
.bulk-grid-wrap { overflow: auto; max-height: 48vh; border: 1px solid var(--border); border-radius: 8px; }
.bulk-grid { border-collapse: collapse; width: 100%; }
.bulk-grid th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface-2); color: var(--text-dim);
  text-align: left; padding: 6px 8px; white-space: nowrap;
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.3px;
  border-bottom: 1px solid var(--border);
}
/* Two-row head: group names above the columns. Both rows stick, so the second
   has to be pushed down by the first's fixed height — at top:0 it would pin
   over it. */
.bulk-grid thead tr.bulk-grp-row th {
  top: 0; height: 24px; padding: 4px 8px;
  color: var(--accent); font-size: 10px; text-align: center;
  border-bottom: 1px solid var(--border);
  border-left: 1px solid var(--border);
}
.bulk-grid thead tr.bulk-grp-row th:first-child,
.bulk-grid thead tr.bulk-grp-row th:empty { border-left: 0; }
.bulk-grid thead tr.bulk-grp-row + tr th { top: 24px; }
.bulk-grid td { padding: 0; border: 1px solid var(--border); }
.bulk-cell {
  width: 100%; min-width: 96px; box-sizing: border-box;
  border: 0; background: transparent; color: var(--text);
  padding: 6px 8px; font-size: 12px;
}
.bulk-cell:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
/* State / class codes are stored upper case, so show them that way — a cell
   reading "ny" beside a record that says "NY" looks like it didn't take. */
.bulk-cell-up { text-transform: uppercase; }
.bulk-rowdel { text-align: center; }
.bulk-del-row { background: none; border: 0; color: var(--muted); cursor: pointer; font-size: 13px; padding: 4px 8px; }
.bulk-del-row:hover { color: var(--danger); }
/* What the last paste actually did — which columns matched, what was dropped. */
.bulk-note { margin-top: 6px; line-height: 1.4; }
.bulk-note:empty { display: none; }
.bulk-note-warn { color: var(--warning); }

/* Driver multi-select */
.bulk-bar {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 8px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 8px 12px; margin-bottom: 10px; position: sticky; top: 0; z-index: 5;
}
.bulk-bar-left { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.bulk-bar-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.deck-check { width: 24px; flex: none; text-align: center; font-size: 18px; }
.deck-row.deck-selected { outline: 2px solid var(--accent); outline-offset: -2px; }
.sch-checks { display: flex; flex-direction: column; gap: 4px; max-height: 160px; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 8px; margin-top: 4px; }
.sch-check { font-weight: 400; display: flex; align-items: center; gap: 6px; font-size: 13px; }

/* Bulk import result */
.bulk-result { margin-top: 10px; }
.bulk-summary { font-size: 14px; margin-bottom: 6px; }
.bulk-errors { margin: 0; padding-left: 18px; max-height: 160px; overflow: auto; font-size: 12px; color: var(--text-dim); }
.bulk-errors li { margin-bottom: 2px; }

/* Bus document upload row */
.bus-doc-upload { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 10px; }
.bus-doc-upload > * { flex: 1; min-width: 110px; }
.bus-doc-upload button { flex: 0 0 auto; }

/* Maintenance request photo thumbnail */
/* Request photo shown as a partial banner across the top of the card */
.req-photo-banner {
  display: block;
  margin: -12px -14px 10px;   /* cancel .req-card padding so it spans edge-to-edge */
  height: 64px;               /* thin header strip (~ card row height) */
  overflow: hidden;
  cursor: zoom-in;
  border-bottom: 1px solid var(--border);
}
.req-photo-banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
  display: block;
}

.dot-banner {
  background: rgba(220, 38, 38, 0.15);
  border: 1px solid var(--critical);
  color: #fecaca;
  padding: 10px 12px;
  border-radius: 8px;
  margin-bottom: 12px;
  font-size: 13px;
  line-height: 1.4;
}
.dot-banner strong { color: #fff; }
.modal select option:disabled { color: #555; }

.modal-group {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px 14px;
  margin: 14px 0 0;
}
.modal-group legend {
  padding: 0 6px;
  font-size: 12px;
  text-transform: uppercase;
  color: var(--text-dim);
  letter-spacing: 0.5px;
  font-weight: 700;
}
.modal-group legend .muted { text-transform: none; letter-spacing: 0; font-weight: 400; }
.modal-group label { margin-top: 8px; }
.modal-group label .muted { text-transform: none; letter-spacing: 0; }
/* A toggle inside a modal fieldset. The switch is itself a <label>, so the rule
   above would give it a top margin and knock it out of line with the sentence
   it sits beside. */
.modal-group .toggle-row { margin-top: 6px; }
.modal-group .toggle-row .switch { margin-top: 0; }

/* ============================================================
 * STAFF / DRIVERS
 * ============================================================ */
.staff-toolbar {
  display: flex;
  gap: 8px;
  margin: 12px 0;
  flex-wrap: wrap;
}
.staff-toolbar button { flex: 1; min-width: 160px; }

/* Driver cards */
.driver-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}
@media (min-width: 720px) {
  .driver-list { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 1080px) {
  .driver-list { grid-template-columns: 1fr 1fr 1fr; }
}

.driver-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
  cursor: pointer;
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.driver-card:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.25);
}
.driver-card header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 8px;
}
.driver-card h3 { margin: 0; font-size: 16px; }
.driver-card footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 10px;
  gap: 8px;
}
.driver-card-critical { border-left: 4px solid var(--critical); }
.driver-card-warn { border-left: 4px solid #f59e0b; }

.cpills { display: flex; flex-direction: column; gap: 4px; align-items: flex-end; }
.cpill {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  white-space: nowrap;
}
.cpill-overdue { background: rgba(220,38,38,0.2); color: #fecaca; border: 1px solid var(--critical); }
.cpill-soon { background: rgba(245,158,11,0.18); color: #fcd34d; border: 1px solid #f59e0b; }
.cpill-ok { background: rgba(16,185,129,0.16); color: #6ee7b7; border: 1px solid var(--success); }

.cflag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 8px 0 4px;
}
.cflag {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border);
  color: var(--text-dim);
}
.cflag-overdue { background: rgba(220,38,38,0.15); color: #fecaca; border-color: var(--critical); }
.cflag-soon { background: rgba(245,158,11,0.12); color: #fcd34d; border-color: #f59e0b; }

/* Safety drill grid */
.drill-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px;
  margin-top: 6px;
}
.drill-card {
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.drill-card.drill-ok { border-left: 3px solid var(--success); }
.drill-card.drill-soon { border-left: 3px solid var(--warning); background: rgba(234, 179, 8, 0.08); }
.drill-card.drill-overdue { border-left: 3px solid var(--critical); background: rgba(220,38,38,0.07); }
.drill-card.drill-pending { border-left: 3px solid #6b7280; }

/* Driver detail modal */
.driver-detail { width: 100%; }
.dd-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 12px;
  gap: 12px;
}
.dd-head h3 { margin: 0 0 4px; }

.dd-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--border);
  margin-bottom: 12px;
  overflow-x: auto;
}
.dd-tab {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-dim);
  padding: 8px 14px;
  cursor: pointer;
  font-size: 14px;
  white-space: nowrap;
}
.dd-tab:hover { color: var(--text); }
.dd-tab.active { color: var(--text); border-bottom-color: var(--accent, #3b82f6); }

.dd-pane[hidden] { display: none; }
/* Status pane (driver & bus detail) */
.lifecycle-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.section-h-action { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.section-h-action button { text-transform: none; letter-spacing: 0; }
.evt-row.evt-clickable {
  width: 100%; text-align: left; cursor: pointer; font: inherit; color: var(--text);
}
.evt-row.evt-clickable:hover { border-color: var(--accent); }
.cf-flash { animation: cfflash 1.6s ease; }
@keyframes cfflash {
  0%, 100% { box-shadow: none; }
  15%, 60% { box-shadow: 0 0 0 3px var(--accent); }
}
.status-bar {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 2px 0 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.status-bar .cpill { font-size: 13px; padding: 6px 14px; border-radius: 999px; }

/* Organized info sections (driver & bus detail) */
.info-section { margin-bottom: 14px; }
.info-section-h {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--accent); font-weight: 700; margin: 0 0 8px;
  padding-bottom: 5px; border-bottom: 1px solid var(--border);
}
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px; }
.info-item { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.info-item.wide { grid-column: 1 / -1; }
.info-item label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px;
  color: var(--text-dim); font-weight: 600; margin: 0;
}
.info-item .v { font-size: 14px; word-break: break-word; }

.dd-field {
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: 8px;
  align-items: flex-start;
  padding: 6px 0;
  border-bottom: 1px solid rgba(255,255,255,0.04);
  font-size: 14px;
}
.dd-field label {
  margin: 0;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-dim);
  font-weight: 600;
}

/* Compliance grid (inside detail modal) */
.comp-cat { margin-bottom: 16px; }
.comp-cat h4 {
  margin: 8px 0 6px;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-dim);
}
.comp-grid {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.comp-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8px;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
}
.comp-row.comp-overdue { border-left: 3px solid var(--critical); background: rgba(220,38,38,0.06); }
.comp-row.comp-soon { border-left: 3px solid #f59e0b; background: rgba(245,158,11,0.05); }
.comp-row.comp-ok { border-left: 3px solid var(--success); }
.comp-row.comp-missing { border-left: 3px solid #6b7280; background: rgba(107,114,128,0.06); }
.comp-info { flex: 1; min-width: 0; }
.comp-name { font-size: 14px; font-weight: 600; }
.comp-meta { margin: 2px 0; }
.comp-status-line {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 4px;
  font-size: 12px;
}
.comp-pill {
  font-size: 10px;
  font-weight: 800;
  padding: 2px 7px;
  border-radius: 4px;
  letter-spacing: 0.5px;
}
.comp-pill-overdue { background: var(--critical); color: #fff; }
.comp-pill-soon { background: #f59e0b; color: #1f1300; }
.comp-pill-ok { background: var(--success); color: #04261c; }
.comp-pill-missing { background: #6b7280; color: #fff; }
.comp-pill-n\/a { background: #374151; color: #cbd5e1; }
.comp-when { color: var(--text-dim); }
.comp-actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex-shrink: 0;
}

/* Document upload + list */
.upload-form {
  background: rgba(255,255,255,0.03);
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 12px;
}
.upload-row {
  display: flex;
  gap: 6px;
  margin-top: 8px;
  flex-wrap: wrap;
}
.upload-row select, .upload-row input[type="text"] { flex: 1; min-width: 130px; }

.doc-list { display: flex; flex-direction: column; gap: 6px; }
.doc-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
}
.doc-meta { flex: 1; min-width: 0; }
.doc-actions { display: flex; gap: 4px; flex-shrink: 0; }

/* Misc form helpers */
.form-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 8px;
}
.form-grid-3 {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 8px;
  margin-top: 4px;
}
.form-grid-2 label, .form-grid-3 label { margin-top: 4px; }
/* A label that wraps to two lines used to push its own box a line lower than
 * the one beside it. Each cell is a column that pins its label-plus-control
 * pair to the bottom, so every box in a row sits on the same line no matter
 * how long the field name is. The pair moves together — the label is welded to
 * the top of its box by a negative margin, so `auto` has to go on the label. */
.form-grid-2 > div, .form-grid-3 > div { display: flex; flex-direction: column; }
.modal .form-grid-2 > div > label:first-child, .modal .form-grid-3 > div > label:first-child,
.form-grid-2 > div > label.check-row, .form-grid-3 > div > label.check-row { margin-top: auto; }

.btn-xs {
  padding: 4px 8px;
  font-size: 12px;
}

/* ================================================================
   Additions for Fleet/Staff restructure (matched to dark theme)
   ================================================================ */

/* Section switch (top-level Fleet | Staff) */
.section-switch {
  display: inline-flex;
  background: var(--bg-2);
  padding: 4px;
  border-radius: 999px;
  border: 1px solid var(--border);
}
.section-switch button, .ss-btn {
  border: none;
  background: transparent;
  padding: 8px 16px;
  border-radius: 999px;
  font-weight: 600;
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
  transition: all 0.18s ease;
  display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit;
}
.section-switch button.active, .ss-btn.active {
  background: var(--accent);
  color: #fff;
}
.section-switch button:hover:not(.active), .ss-btn:hover:not(.active) {
  color: var(--text);
}
.ss-badge {
  display: inline-block;
  padding: 1px 7px;
  background: rgba(255,255,255,0.18);
  color: #fff;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
}

/* Driver toolbar (search + filter) */
.driver-toolbar {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  /* Keep the search bar + view/sort controls pinned under the header — and
     under the section's tab bar, which is part of the header now — while the
     fleet / staff lists scroll beneath them. */
  position: sticky;
  top: calc(var(--topbar-h) + var(--subbar-h));
  z-index: 30;
  background: var(--bg);
  /* Full-bleed across main's 16px gutters so scrolling content can't
     peek through the sides or top of the pinned bar. */
  margin: 0 -16px 14px;
  padding: 10px 16px;
}
.driver-toolbar-info {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--text-dim);
  font-weight: 500;
  margin-bottom: 12px;
}
.dti-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dti-check { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; color: var(--text-dim); }
.btn-xs.active-toggle { background: var(--accent); color: #fff; border-color: var(--accent); }
.search-input {
  flex: 1;
  min-width: 200px;
  height: var(--control-h);
  padding: 0 16px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--field-bg);
  font-size: 14px;
  color: var(--text);
  font-family: inherit;
  outline: none;
  box-shadow: var(--shadow-control);
  transition: all 0.15s ease;
}
.search-input::placeholder { color: var(--muted); }
.search-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
/* Selects in a toolbar are built to match .search-input exactly — same height,
   field colour, pill radius and lift — so the row reads as one control group. */
.filter-select {
  height: var(--control-h);
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--field-bg);
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  outline: none;
  cursor: pointer;
  box-shadow: var(--shadow-control);
  transition: all 0.15s ease;
}
.filter-select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
/* The "Sort by" dropdown is the same object as the other toolbar selects —
   only its label reads quieter. Don't re-style its box here. */
.sort-select { color: var(--text-dim); }
.sort-select:hover { color: var(--text); }

/* Bus avatar (driver bus number / standby) */
.bus-avatar {
  flex-shrink: 0;
  width: 48px; height: 48px;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800;
  font-size: 17px;
  color: #fff;
  letter-spacing: 0.5px;
  background: var(--accent);
}
.bus-avatar.bus-num { background: var(--accent); }
/* Standby has no bus of its own — grey, not a status color. */
.bus-avatar.bus-sb {
  background: var(--muted);
  font-size: 14px;
}
.bus-avatar.bus-large {
  width: 80px; height: 80px;
  font-size: 28px;
  border-radius: 18px;
}

/* Deck-style driver list */
.driver-deck {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.deck-row {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px 14px;
  cursor: pointer;
  transition: all 0.15s ease;
  text-align: left;
  width: 100%;
  font-family: inherit;
  color: var(--text);
}
.deck-row:hover {
  background: var(--surface-2);
  border-color: var(--accent);
}
.deck-row.deck-row-warn     { border-left: 3px solid var(--warning); }
.deck-row.deck-row-critical { border-left: 3px solid var(--danger); }
.deck-row.deck-row-ok       { border-left: 3px solid var(--success); }
.deck-row.alert-driver      { border-left: 3px solid var(--danger); }
.deck-info { flex: 1; min-width: 0; }
.deck-name {
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
  display: flex; align-items: center; gap: 6px;
}
.deck-sub {
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 2px;
}
.deck-stats {
  display: flex; align-items: center; gap: 6px; flex-shrink: 0;
}
.deck-flag {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(255,255,255,0.06);
  color: var(--text-dim);
  margin-top: 4px;
  display: inline-block;
}
.deck-flag.cflag-ok      { background: rgba(16,185,129,0.15); color: var(--success); }
.deck-flag.cflag-soon    { background: rgba(245,158,11,0.15); color: var(--warning); }
.deck-flag.cflag-overdue { background: rgba(239,68,68,0.18);  color: var(--danger); }
.deck-flag.cflag-missing { background: rgba(148,163,184,0.15); color: var(--muted); }
.deck-arrow {
  color: var(--muted);
  font-size: 20px;
  margin-left: 4px;
  line-height: 1;
}

/* Status compliance pills inside deck rows */
.cpill {
  display: inline-flex;
  padding: 3px 9px;
  font-size: 11px;
  font-weight: 600;
  border-radius: 999px;
}
.cpill-ok      { background: rgba(16,185,129,0.15); color: var(--success); }
.cpill-soon    { background: rgba(245,158,11,0.15); color: var(--warning); }
.cpill-overdue { background: rgba(239,68,68,0.18);  color: var(--danger); }
/* "The driver sent this in and nobody has filed it yet." Deliberately BLUE and
   not green: it is news, not an answer — the requirement it sits on is still
   whatever it was, and a second green pill beside a red one would say the
   opposite. */
.cpill-sent    { background: rgba(59,130,246,0.18); color: #93c5fd; }
:root[data-theme="light"] .cpill-sent { color: #1d4ed8; }

/* Schedule pills (multi-route assignment indicators) */
.sched-pills { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.sched-pill {
  display: inline-flex;
  padding: 2px 9px;
  font-size: 10px;
  font-weight: 600;
  background: rgba(59,130,246,0.15);
  color: #93c5fd;
  border-radius: 999px;
}

/* Diabetic / dx badge */
.dx-badge {
  display: inline-flex; align-items: center;
  padding: 2px 7px;
  font-size: 10px;
  font-weight: 700;
  background: rgba(168,85,247,0.18);
  color: #c4b5fd;
  border-radius: 999px;
}

/* Broadcast hero */
.broadcast-hero {
  background: linear-gradient(135deg, var(--accent) 0%, #14532d 100%);
  color: #fff;
  border-radius: 16px;
  padding: 22px;
  margin-bottom: 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  box-shadow: var(--shadow);
}
.broadcast-hero h2 { margin: 0 0 4px; font-size: 20px; color: #fff; }
.broadcast-hero p  { margin: 0; color: rgba(255,255,255,0.85); font-size: 13px; }
.broadcast-hero .btn-primary,
.broadcast-hero .btn-large {
  background: #fff;
  color: var(--accent);
  font-weight: 700;
}
.broadcast-hero .btn-primary:hover,
.broadcast-hero .btn-large:hover { background: #f1f5f9; }

/* Schedule cards on broadcast tab */
.schedule-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}
.schedule-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: var(--text);
  transition: all 0.15s ease;
}
.schedule-card:hover { border-color: var(--accent); background: var(--surface-2); }
.sched-name { font-weight: 700; font-size: 14px; color: var(--text); }
.sched-time { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.sched-count {
  margin-top: 10px;
  display: inline-flex;
  padding: 3px 10px;
  background: rgba(59,130,246,0.18);
  color: #93c5fd;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
}

/* Drill cards (safety drills on Staff dashboard) */
.drill-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
}
.drill-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
  box-shadow: var(--shadow);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.drill-card:hover { transform: translateY(-2px); box-shadow: 0 10px 26px rgba(4, 14, 8, 0.5); }

/* Compliance history viewer */
.history-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.history-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 13px;
}

/* ---------- Selectable option cards (bus / schedule pickers) ----------
   Not tiny checkboxes: the whole card is the target (48px min, thumb-sized),
   and the native box is restyled into the round tick on the trailing edge. */
.checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
  max-height: 300px;
  overflow-y: auto;
  padding: 2px 10px 2px 2px;  /* room for the scrollbar, so it can't sit on a card */
  margin-top: 2px;
}
/* `label.cb-line`, not `.cb-line` — it has to outrank `.modal label`, which
   turns every label in a dialog into a block-level uppercase field caption. */
label.cb-line {
  /* row-reverse puts the tick on the trailing edge without wrapping the label
     text in a span (some .cb-line callers pass a bare text node). */
  display: flex; flex-direction: row-reverse; align-items: center;
  justify-content: space-between; gap: 10px;
  min-height: 48px;
  margin: 0;
  padding: 10px 14px;
  border: 1.5px solid transparent;
  border-radius: 12px;
  background: var(--surface-2);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.3;
  text-transform: none;
  letter-spacing: normal;
  color: var(--text);
  cursor: pointer;
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
label.cb-line:hover { border-color: var(--border); }
label.cb-line:active { background: var(--bg-3); }

/* The checkbox itself = the indicator, pushed to the trailing edge. */
label.cb-line input[type=checkbox] {
  appearance: none; -webkit-appearance: none;
  flex: none;
  margin: 0;
  width: 22px; height: 22px;
  border: 2px solid var(--muted);
  border-radius: 50%;
  background: var(--surface);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
label.cb-line input[type=checkbox]:checked {
  border-color: var(--accent);
  background: var(--accent) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'><path d='M20 6 9 17l-5-5'/></svg>") center / 13px 13px no-repeat;
}
label.cb-line input[type=checkbox]:disabled { opacity: 0.45; cursor: default; }

/* Selected card: accent tint + accent edge, so a glance reads the picks. */
label.cb-line:has(input[type=checkbox]:checked) {
  background: var(--accent-soft);
  border-color: var(--accent);
  font-weight: 600;
}
label.cb-line:has(input[type=checkbox]:focus-visible) {
  box-shadow: 0 0 0 3px var(--accent-soft);
  border-color: var(--accent);
}

/* ---------- Driver picker (searchable tap-list) ---------- */
.driver-picker { display: flex; flex-direction: column; gap: 8px; }
/* Same search field as the page toolbars — one search style app-wide. */
.dp-search {
  width: 100%;
  height: var(--control-h);
  padding: 0 16px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--field-bg);
  color: var(--text);
  font-size: 14px;
  outline: none;
  box-shadow: var(--shadow-control);
  transition: all 0.15s ease;
}
.dp-search::placeholder { color: var(--muted); }
.dp-search:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.dp-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
}
.dp-count { font-size: 12px; color: var(--text-dim); font-weight: 600; }
.dp-head-btns { display: flex; gap: 6px; }
.dp-list {
  display: flex; flex-direction: column; gap: 4px;
  max-height: 260px;
  overflow-y: auto;
  padding: 6px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.dp-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 12px;
  border-radius: 9px;
  border: 1px solid transparent;
  font-size: 14px;
  color: var(--text);
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s, border-color 0.12s;
}
.dp-row:hover { background: rgba(255,255,255,0.05); }
.dp-row .dp-name { min-width: 0; }
.dp-row .dp-bus { flex-shrink: 0; }
.dp-row.selected {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  border-color: var(--accent);
}
.dp-row.selected .dp-name { font-weight: 600; }
.dp-row.selected::before {
  content: "✓";
  color: var(--accent);
  font-weight: 700;
  margin-right: -2px;
}
.dp-row-locked { cursor: default; opacity: 0.85; }
.dp-row-locked:hover { background: transparent; }

.cc-head-btns { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }

/* Admin (hidden /admin/templates) */
.admin-view {
  max-width: 1000px;
  margin: 0 auto;
  padding: 16px;
}
.template-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
}
.tpl-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
  display: flex; flex-direction: column; gap: 10px;
}
.tpl-head {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
}
.tpl-active {
  font-size: 11px; font-weight: 700;
  padding: 2px 8px;
  background: rgba(16,185,129,0.18);
  color: var(--success);
  border-radius: 999px;
}
.tpl-upload {
  display: flex; gap: 6px; align-items: center;
  flex-wrap: wrap;
}
.tpl-actions { display: flex; gap: 6px; flex-wrap: wrap; }

/* Block info banner (e.g. "DS-870 required first") */
.alert-block-info {
  background: rgba(59,130,246,0.12);
  border: 1px solid rgba(59,130,246,0.3);
  color: #93c5fd;
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 13px;
  margin-bottom: 10px;
}

/* w-full util */
.w-full { width: 100%; }

/* ============================================================
 * NEW: restructure — centered tabs, file cabinet, big buttons,
 * course sessions, modal section tabs, address autosuggest
 * ============================================================ */

/* Centered tabs (top-level + sub-tabs) */
.tabs-centered { justify-content: center; }
.subtabs {
  display: flex;
  gap: 8px;
  padding: 10px 14px 14px;
  overflow-x: auto;
  scrollbar-width: none;
}
.subtabs::-webkit-scrollbar { display: none; }
.subtabs-centered { justify-content: center; flex-wrap: wrap; }
.subtab {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s;
}
.subtab:hover { background: var(--surface-2); }
.subtab.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.subtab .badge {
  background: rgba(255,255,255,0.2);
  color: inherit;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
}

/* ============================================================
 * File cabinet — real separated folders with collapsible files
 * ============================================================ */
.folders { position: relative; }

/* Folder tabs — sit like the raised tabs on hanging file folders, tucked over
   the top edge of the open folder body. Exactly one body shows at a time. */
.folder-tabs {
  display: flex;
  gap: 4px;
  padding-left: 6px;
  position: relative;
  z-index: 2;
}
.folder-tab {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  text-align: left;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 14px 14px 0 0;
  background: var(--surface-2);
  color: var(--text-dim);
  cursor: pointer;
  position: relative;
  top: 3px;                 /* inactive tabs sit slightly recessed */
  transition: background 0.15s, color 0.15s, top 0.12s;
}
.folder-tab:hover { background: var(--bg-3); color: var(--text); }
.folder-tab.active {
  top: 0;
  background: var(--surface);
  color: var(--text);
  border-top: 3px solid var(--folder-accent, var(--accent));
  padding-top: 9px;         /* offset the thicker accent top border */
}
.folder-tab-hire { --folder-accent: var(--accent); }
.folder-tab-recurring { --folder-accent: var(--warning); }
.folder-tab-other { --folder-accent: var(--muted); }

.folder-tab-icon { font-size: 22px; line-height: 1; flex-shrink: 0; }
.folder-tab-text { display: flex; flex-direction: column; min-width: 0; }
.folder-tab-title {
  font-weight: 700; font-size: 14px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.folder-tab-sub {
  font-size: 11px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.folder-tab-chips {
  margin-left: auto;
  display: flex; flex-wrap: wrap; gap: 5px;
  justify-content: flex-end;
}

/* Folder body — the open folder interior; only the active one is visible.
   Driven by `.active`, never the [hidden] attribute, so two bodies can never
   stack on top of each other. */
.folder-body { display: none; }
.folder-body.active {
  display: block;
  position: relative;
  z-index: 1;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0 12px 12px 12px;
  padding: 12px;
  box-shadow: var(--shadow);
  animation: cab-reveal 0.18s ease;
}
/* No tabs above it (e.g. the bus cabinet) → fully rounded folder */
.folders > .folder-body.active:first-child { border-radius: 12px; }

/* Files stack like papers in the folder */
.cabinet-files {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Individual file — collapsible <details>; status shows on the closed row */
.cabinet-file {
  background: var(--bg);
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: 8px;
  overflow: hidden;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.cabinet-file[open] { box-shadow: var(--shadow); }
.cabinet-file.cabinet-file-missing { border-color: rgba(239,68,68,0.4); }
.cabinet-file.status-overdue { border-left-color: var(--critical); }
.cabinet-file.status-soon    { border-left-color: var(--warning); }
.cabinet-file.status-ok      { border-left-color: var(--success); }
.cabinet-file.status-missing { border-left-color: var(--danger); }
.cabinet-file.status-optional { border-left-color: var(--border); }

.cf-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.cf-summary::-webkit-details-marker { display: none; }
.cf-summary:hover { background: var(--surface-2); }
.cf-name { font-weight: 600; flex: 1; min-width: 0; }
.cf-count {
  display: inline-block;
  margin-left: 8px;
  padding: 0 7px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 700;
}
.cf-summary-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.cf-caret { color: var(--muted); font-size: 12px; transition: transform 0.18s ease; }
.cabinet-file[open] .cf-caret { transform: rotate(90deg); }

.cf-body {
  padding: 12px 14px 14px;
  border-top: 1px solid var(--border);
  animation: cab-reveal 0.16s ease;
}
.cf-detail { font-size: 12px; color: var(--text-dim); margin-bottom: 10px; }
/* The filed document a read-only card's buttons act on */
.cf-file-line {
  font-size: 12px;
  color: var(--text);
  margin-bottom: 10px;
  overflow-wrap: anywhere;
}

/* Big action buttons (Prefill, Upload, Download) */
.cf-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}
.big-btn {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 12px 14px;
  min-width: 110px;
  min-height: 72px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  text-decoration: none;
  font-size: 12px;
  font-weight: 600;
  transition: transform 0.1s, background 0.15s;
}
.big-btn:hover { background: var(--bg-3); }
.big-btn:active { transform: scale(0.97); }
.big-btn .big-icon { font-size: 22px; line-height: 1; }
.big-btn .big-label { text-align: center; }
.big-btn-print {
  background: rgba(59,130,246,0.15);
  border-color: rgba(59,130,246,0.45);
  color: #93c5fd;
}
.big-btn-upload {
  background: rgba(16,185,129,0.15);
  border-color: rgba(16,185,129,0.45);
  color: #6ee7b7;
}
.big-btn-download {
  background: rgba(245,158,11,0.15);
  border-color: rgba(245,158,11,0.45);
  color: #fcd34d;
}
/* Read-only pair on the driver's own cabinet — the only two actions they get */
.big-btn-view {
  background: rgba(59,130,246,0.15);
  border-color: rgba(59,130,246,0.45);
  color: #93c5fd;
}
.big-btn-share {
  background: rgba(168,85,247,0.15);
  border-color: rgba(168,85,247,0.45);
  color: #d8b4fe;
}
/* Light theme: the tinted backgrounds are pale, so use darker label colors for contrast */
:root[data-theme="light"] .big-btn-print {
  background: rgba(59,130,246,0.12);
  border-color: rgba(59,130,246,0.5);
  color: #1d4ed8;
}
:root[data-theme="light"] .big-btn-upload {
  background: rgba(16,185,129,0.14);
  border-color: rgba(16,185,129,0.55);
  color: #047857;
}
:root[data-theme="light"] .big-btn-download {
  background: rgba(245,158,11,0.16);
  border-color: rgba(245,158,11,0.55);
  color: #b45309;
}
:root[data-theme="light"] .big-btn-view {
  background: rgba(59,130,246,0.12);
  border-color: rgba(59,130,246,0.5);
  color: #1d4ed8;
}
:root[data-theme="light"] .big-btn-share {
  background: rgba(168,85,247,0.14);
  border-color: rgba(168,85,247,0.5);
  color: #7e22ce;
}

/* Folder-tab status chips (rolled-up missing / due-soon counts) */
.cab-chip {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  border: 1px solid transparent;
}
.cab-chip-missing { color: var(--danger);  background: rgba(239,68,68,0.14);  border-color: rgba(239,68,68,0.35); }
.cab-chip-soon    { color: var(--warning); background: rgba(224,160,18,0.14); border-color: rgba(224,160,18,0.35); }
.cab-chip-ok      { color: var(--success); background: rgba(34,197,94,0.14);  border-color: rgba(34,197,94,0.35); }
.cab-chip-sent    { color: #93c5fd;        background: rgba(59,130,246,0.14); border-color: rgba(59,130,246,0.35); }
:root[data-theme="light"] .cab-chip-sent { color: #1d4ed8; }

/* ---- A document that's been handed over but not filed yet ----
   Both cabinets draw this: the driver's, to say their upload arrived and who
   it's waiting on; the office's, to put the one action that closes the loop
   right under the file. Same block, because it's the same fact seen from two
   sides — and it must never look like a filed document, which is why it's a
   tinted strip rather than another .cf-file-line. */
.cf-sent {
  border: 1px solid rgba(59,130,246,0.4);
  background: rgba(59,130,246,0.10);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 10px;
}
.cf-sent-head { font-size: 12px; font-weight: 700; color: var(--text); }
.cf-sent-meta { font-size: 12px; color: var(--text-dim); margin-top: 2px; overflow-wrap: anywhere; }
.cf-sent-note { font-size: 12px; color: var(--text-dim); font-style: italic; margin-top: 4px; }
.cf-sent-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }

@keyframes cab-reveal {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* On a phone there isn't room for icon + title + sub + chips on one line — the
   roll-up chips drop to their own row under the title and the "One-time · 7
   files" sub line goes, rather than letting the two collide. */
@media (max-width: 620px) {
  .folder-tab { flex-wrap: wrap; row-gap: 4px; gap: 8px; padding: 9px 10px; }
  .folder-tab-icon { font-size: 18px; }
  .folder-tab-sub { display: none; }
  .folder-tab-chips { flex-basis: 100%; margin-left: 0; justify-content: flex-start; }
  .cab-chip { font-size: 10px; padding: 1px 6px; }
}

@media (max-width: 420px) {
  .folder-tab { padding: 9px 8px; }
  .folder-tab-title { font-size: 13px; }
}

/* Existing uploaded docs list under a cabinet-file */
.cabinet-docs {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* A bus document's row. The title takes its own line and the dates sit under it:
   an MC-300 carries a filed date, an expiry chip and a "logged DOT" chip, and on
   one line those crushed the filename down to a two-letter column. */
.cabinet-doc {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 8px;
  font-size: 12px;
  padding: 4px 6px;
  border-radius: 6px;
}
.cabinet-doc:hover { background: var(--surface-2); }
.cab-doc-title { flex: 1 1 100%; min-width: 0; overflow-wrap: anywhere; font-weight: 500; }
.cabinet-doc > .muted { flex: 1; }
.cabinet-no-files {
  font-size: 12px;
  color: var(--text-dim);
  font-style: italic;
}

/* Document history window (opened from the big "History" button) */
.docwin-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 55vh;
  overflow-y: auto;
}
.docwin-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}
.docwin-row:hover { background: var(--surface-2); }
.docwin-meta {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.docwin-title {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Three-dots actions trigger on each document row */
.docwin-menu-btn {
  flex: none;
  font-size: 20px;
  line-height: 1;
  padding: 4px 10px;
  letter-spacing: 1px;
}

/* Per-document action sheet (View / Rename / Download / Delete) */
.doc-actions-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.doc-actions-menu {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 14px 0 4px;
}
.doc-actions-menu button {
  width: 100%;
  justify-content: flex-start;
  text-align: left;
  font-size: 15px;
  padding: 12px 14px;
}

/* In-app document viewer (embeds the file instead of downloading it) */
.modal:has(.doc-viewer) { max-width: 920px; width: 96vw; }
.doc-viewer { display: flex; flex-direction: column; min-height: 0; }
.doc-viewer-head {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: space-between;
  margin-bottom: 10px;
}
.doc-viewer-title {
  margin: 0;
  /* Takes the slack so Print + Open stay grouped at the right edge. */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.doc-viewer-head .btn-xs { flex: 0 0 auto; }
.doc-viewer-frame {
  flex: 1;
  min-height: 0;
  height: 72vh;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: auto;
  background: var(--surface-2);
}
/* PDF pages / images stack vertically and scroll inside the frame */
.doc-viewer-frame.doc-viewer-scroll {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 12px;
}
.doc-viewer-page {
  max-width: 100%;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
  background: #fff;
}
.doc-viewer-img { max-width: 100%; height: auto; }
.doc-viewer-frame iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
.doc-viewer-msg {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
}
.doc-viewer-fallback-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  align-items: center;
}

/* Rename input + fixed extension suffix */
.rename-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.rename-row input { flex: 1; min-width: 0; }
.rename-ext {
  color: var(--muted);
  font-weight: 600;
  white-space: nowrap;
}

/* Share dialog — the file being sent, shown above the recipient field */
.doc-share-file {
  margin: 0 0 14px;
  word-break: break-word;
}

/* Course session cards */
/* ---- Courses page layout & polish ---- */
.course-section { margin-bottom: 26px; }
.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 22px 0 14px;
}
.section-head .section-title { margin: 0; }
.btn-sm { padding: 7px 14px; font-size: 13px; font-weight: 600; }

.course-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.course-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 18px 14px 22px;
  box-shadow: var(--shadow);
  overflow: hidden;
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.course-card::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 5px;
  background: linear-gradient(180deg, var(--brand-400), var(--brand-600));
  opacity: 0.55;
}
.course-card-active::before { opacity: 1; }
.course-card:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  box-shadow: 0 10px 26px rgba(4, 14, 8, 0.5);
}
.course-card-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 14px;
  gap: 12px;
  flex-wrap: wrap;
}
.cc-title { display: flex; gap: 12px; align-items: flex-start; min-width: 0; }
.cc-icon {
  flex-shrink: 0;
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  font-size: 20px;
  border-radius: 11px;
  background: linear-gradient(135deg, rgba(21,163,74,0.22), rgba(212,160,23,0.18));
  border: 1px solid var(--border);
}
.course-card-head h3 { margin: 0 0 5px; font-size: 18px; line-height: 1.2; }
.cc-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.course-block { margin-top: 14px; }
.course-block h4 { margin: 0 0 8px; font-size: 13px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.4px; }
.course-foot {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: flex-end;
}
.session-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 8px;
}
.session-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}
.session-actions {
  display: flex;
  gap: 6px;
  margin-top: 8px;
  flex-wrap: wrap;
}

/* Cabinet (collapsible "past sessions") */
.course-cabinet {
  margin-top: 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2);
}
.course-cabinet > summary {
  cursor: pointer;
  padding: 10px 12px;
  font-weight: 600;
  font-size: 13px;
  user-select: none;
}
.cabinet-list {
  padding: 8px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Drivers still-due panel */
.due-panel {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  overflow: hidden;
}
.due-panel-ok { border-color: rgba(16, 185, 129, 0.35); }
.due-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
}
.due-progress { flex: 1; min-width: 0; }
.due-progress-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 6px;
}
.due-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.due-frac { font-size: 13px; font-weight: 700; color: var(--text); }
.due-bar {
  height: 8px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}
.due-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--warning);
  transition: width 0.3s ease;
}
.due-bar-fill.ok { background: var(--success); }
.due-details { border-top: 1px solid var(--border); }
.due-details > summary {
  cursor: pointer;
  padding: 8px 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  user-select: none;
}
.due-list {
  list-style: none;
  margin: 0;
  padding: 0 12px 12px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 0 18px;
}
.due-list li {
  font-size: 13px;
  color: var(--text);
  padding: 5px 0;
  border-bottom: 1px solid var(--border);
}
.cabinet-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
}
.cab-info { flex: 1; min-width: 0; }
.cab-actions { display: flex; gap: 6px; flex-wrap: wrap; }

/* Modal section tabs (used in driver new/edit modal) */
.modal-section-tabs {
  display: flex;
  gap: 6px;
  margin: 8px 0 12px;
  flex-wrap: wrap;
}
.mst-btn {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
/* The one place that runs a softened orange instead of --accent: these pills sit
   inside a dialog, at small size, right next to the title — full-strength brand
   orange there reads louder than the thing it's labelling. Everywhere else keeps
   the brand orange; don't spread --brand-soft. */
.mst-btn.active {
  background: var(--brand-soft);
  border-color: var(--brand-soft);
  color: #fff;
}
.mst-pane { animation: fadeIn 0.15s; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* Driver-detail tabs centered */
.dd-tabs-centered { justify-content: center; }

/* An address entered as five fields that still read as one block: each box
 * gets its own small caption (a placeholder disappears the moment someone
 * types, and then nobody can tell State from County), and the gaps are wide
 * enough that City / State / ZIP don't look jammed together on a phone. */
.addr-fields { display: grid; gap: 12px; margin-top: 6px; }
.addr-fields .af-row { display: grid; gap: 12px; }
.addr-fields .af-street { grid-template-columns: 2.2fr 1fr; }
.addr-fields .af-city { grid-template-columns: 2fr 0.8fr 1.1fr; }
/* Street / City / State / ZIP / County are notched into their boxes by the
 * shared rule up in the modal block — a caption that reads differently from
 * the field names above and below it looks like a bug. Only the top margin
 * differs: these rows already carry their own 12px gap. */
.addr-fields .af-lbl { margin-top: 0; }
@media (max-width: 400px) {
  /* Street + Apt side by side leaves the street box too narrow to read the
   * suggestion you just picked. */
  .addr-fields .af-street { grid-template-columns: 1fr; }
}

/* Address autosuggest dropdown */
.addr-autosuggest { position: relative; }
/* When the input is wrapped for autosuggest it's no longer a direct grid/flex
 * child, so it can't inherit width that way — keep it filling the wrapper. */
.addr-autosuggest > input { width: 100%; box-sizing: border-box; }
.addr-list {
  position: absolute;
  top: 100%;
  left: 0; right: 0;
  z-index: 100;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-top: 2px;
  max-height: 200px;
  overflow-y: auto;
  box-shadow: var(--shadow);
}
.addr-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  color: var(--text);
  padding: 8px 12px;
  cursor: pointer;
  font-size: 13px;
}
.addr-item:hover { background: var(--surface-2); }

/* Refresher schedule modal driver grid */
.sr-driver-grid { max-height: 200px; overflow-y: auto; padding: 4px; }
.sr-session-row { padding: 8px 0; border-bottom: 1px dashed var(--border); }
.sr-session-row:last-child { border-bottom: none; }

/* Dashboard "Upcoming" event list */
.evt-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 18px;
}
.evt-row {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
}
.evt-row.evt-warn { border-left: 4px solid var(--warning); }
.evt-row.evt-bad  { border-left: 4px solid var(--danger); }
.evt-label { font-weight: 600; font-size: 14px; }
.evt-sub   { margin-top: 2px; }
.see-all { margin-top: 6px; align-self: flex-start; }

/* Drill cards (existing) — extend with actions */
.drill-actions {
  display: flex;
  gap: 6px;
  margin-top: 6px;
  flex-wrap: wrap;
}

/* Compliance "missing" pill (file-cabinet) */
.cpill-missing {
  background: rgba(239,68,68,0.18);
  color: #fca5a5;
  border-color: rgba(239,68,68,0.4);
}

/* Compliance "optional" pill — a never-uploaded optional doc, shown neutrally */
.cpill-na {
  background: rgba(148,163,184,0.14);
  color: var(--muted);
  border-color: var(--border);
}

/* ---------- Admin PDF field inspector ---------- */
.tpl-fields {
  margin-top: 10px;
  padding: 10px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.tpl-fields-summary {
  margin-bottom: 6px;
}
.tpl-fields-table-wrap {
  max-height: 320px;
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
}
.tpl-fields-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.tpl-fields-table th,
.tpl-fields-table td {
  padding: 4px 8px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}
.tpl-fields-table th {
  background: rgba(255,255,255,0.04);
  font-weight: 600;
  position: sticky;
  top: 0;
}
.tpl-fields-table code {
  background: rgba(255,255,255,0.06);
  padding: 1px 4px;
  border-radius: 3px;
  font-size: 11px;
}
.tpl-field-mapped { background: rgba(34,197,94,0.05); }

/* ---------- Admin Organization Settings ---------- */
.admin-section {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin: 14px 0;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
.admin-section-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 16px;
  margin: 0 0 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
/* Nested item cards sit on the section surface — drop to the page bg so they
   read as insets rather than card-on-same-card. */
.admin-section .mech-card,
.admin-section .tpl-card { background: var(--bg); }
/* Instructor / requirement rows: full-width divided list inside the card,
   not floating rows with side margins. */
.admin-section .user-row {
  margin: 0;
  padding: 12px 2px;
  border-bottom: 1px solid var(--border);
}
.admin-section .user-row:last-child { border-bottom: none; }
/* An optional one-line hint under a section header. */
.admin-hint {
  margin: -6px 0 14px;
  color: var(--text-dim);
  font-size: 12px;
}
.org-settings-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 10px;
}
/* A card that is a title, a line and its controls — no body of its own. The
   header rule and the space under it are what a section wears when something
   follows it, and here nothing does, so both come off and the buttons ride up
   onto the title row. (The logo picker is the only one so far: its preview box
   was the tallest thing on the Organization screen, and the app header on the
   same page is a truer preview than any box beside a button.) */
.admin-section-slim { padding: 13px 16px; }
.admin-section-slim .admin-section-h {
  margin: 0;
  padding-bottom: 0;
  border-bottom: none;
  flex-wrap: wrap;
}
.admin-section-slim .admin-hint { margin: 6px 0 0; }
.org-logo-btns { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* Sits at the end of the hint line, so a save says so without the card
   changing height under the finger that pressed it. */
.admin-hint .org-save-status { margin-left: 6px; color: var(--text); }

/* The background-cut choice. Two tiles, each the whole answer: the picture is
   the question, so the picture gets the room and the words under it are a
   caption. The plate is the HEADER's colour, not the card's — this is a
   decision about how the mark will sit up there, and a white logo cut out of a
   white letterhead is invisible on a light header, which is the one thing this
   dialog exists to let somebody see before they save it. */
.modal .logo-cut-pick {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin: 14px 0 4px;
}
.modal .logo-cut-opt {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 0 0 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s;
}
.modal .logo-cut-opt:hover,
.modal .logo-cut-opt:focus-visible {
  border-color: var(--accent);
  transform: translateY(-1px);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
}
.modal .logo-cut-shot {
  display: grid;
  place-items: center;
  /* Tall enough to judge an edge by. The header draws the mark at 46px and a
     halo at 46px is a smudge; at 96 it is a halo. */
  min-height: 116px;
  padding: 10px;
  background: var(--chrome-bg);
  border-bottom: 1px solid var(--border);
}
.modal .logo-cut-shot img { max-width: 100%; max-height: 96px; object-fit: contain; }
.modal .logo-cut-lbl {
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
  text-align: center;
}
@media (prefers-reduced-motion: reduce) {
  .modal .logo-cut-opt { transition: none; }
  .modal .logo-cut-opt:hover, .modal .logo-cut-opt:focus-visible { transform: none; }
}
/* No gap: the field name is notched into the top of the input (shared rule in
 * the modal block), so the two have to touch. */
.org-field {
  display: flex;
  flex-direction: column;
}
.org-field-label { margin-top: 0; }
.org-field input {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 10px;
  font-size: 14px;
  outline: none;
}
.org-field input:focus {
  border-color: var(--accent);
}
.org-settings-actions {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 8px;
}
.org-save-status {
  transition: opacity 0.2s;
}

/* ---- Toggle switch (replaces checkboxes) ---- */
.switch {
  position: relative;
  display: inline-block;
  width: 42px;
  height: 24px;
  flex-shrink: 0;
}
.switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.switch .slider {
  position: absolute;
  inset: 0;
  background: var(--border);
  border-radius: 999px;
  transition: background 0.18s ease;
  cursor: pointer;
}
.switch .slider::before {
  content: "";
  position: absolute;
  height: 18px;
  width: 18px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
  transition: transform 0.18s ease;
}
.switch input:checked + .slider {
  background: var(--accent);
}
.switch input:checked + .slider::before {
  transform: translateX(18px);
}
.switch input:disabled + .slider {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ---- Toggle rows (label + switch) ---- */
.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}
.toggle-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 14px;
  color: var(--text);
}
.toggle-row-strong .toggle-label {
  font-weight: 600;
}

/* ---- Daily report settings panel ---- */
.report-settings {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.report-settings .org-field select {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 13px;
  outline: none;
}
.report-settings .org-field select:focus {
  border-color: var(--accent);
}
.report-section-toggles {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
}

/* ---- Mechanic cards ---- */
.mech-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 14px;
}
.mech-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.mech-card-head {
  display: flex;
  align-items: center;
  gap: 10px;
}
.mech-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  flex-shrink: 0;
}
.mech-name {
  font-weight: 600;
  font-size: 15px;
  color: var(--text);
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px;
}
.mech-head-actions {
  display: flex;
  gap: 4px;
}
.icon-btn-sm {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 4px 8px;
  font-size: 13px;
  cursor: pointer;
  line-height: 1;
  color: var(--text);
}
.icon-btn-sm:hover {
  background: var(--surface-2);
}
.icon-btn-sm.danger:hover {
  border-color: var(--danger);
}
.mech-card-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.mech-login {
  min-width: 0;
}
.mech-login-email {
  font-size: 14px;
  color: var(--text);
  word-break: break-word;
}
.mech-login-meta {
  margin-top: 2px;
}
.mech-nologin {
  color: var(--text-dim);
  font-style: italic;
}

/* ---- Pills ---- */
.pill {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
}
.pill-default {
  background: var(--accent);
  color: #fff;
}
.pill-off {
  background: var(--danger);
  color: #fff;
}

/* ===================== Auth (login / change password) ===================== */
.auth-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
/* The auth card is ALWAYS a light surface, in both themes. So it must pin its
   own ink — inheriting the theme's --text put near-white labels and near-white
   typed input on a white card in dark mode (i.e. invisible). */
.auth-card {
  width: 100%;
  max-width: 380px;
  background: #fff;
  color: var(--sf-ink);
  border-radius: 16px;
  padding: 32px 24px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
  text-align: center;
}
.auth-logo { font-size: 48px; line-height: 1; }
.auth-title { margin: 8px 0 2px; font-size: 28px; letter-spacing: 1px; }
.auth-sub { margin: 0 0 20px; color: #64748b; font-size: 14px; }

/* ---------- Brand mark + login entrance ----------
   The logo is three chevrons pointing right, so it means forward motion. The
   entrance says the same thing: each chevron flies in from the left, staggered
   left→right, so a wave travels across the mark and settles. Everything else on
   the card follows it in. Motion is opt-out via prefers-reduced-motion below. */
.auth-logo-svg {
  width: 160px;
  height: 160px;
  max-width: 70%;
  display: block;
  margin: 0 auto 10px;
  overflow: visible; /* chevrons start outside the viewBox — don't clip them */
}
.auth-intro .bm-chev {
  opacity: 0;
  /* transform on an SVG path resolves in USER UNITS (this viewBox is 60 wide),
     not screen px, and defaults to pivoting on the viewBox origin. Pin the box
     to the shape so the offsets below read as "a bit left of home". */
  transform-box: fill-box;
  transform-origin: center;
  animation: chev-fly-in 1.15s cubic-bezier(0.16, 0.85, 0.28, 1) forwards;
}
/* Stagger: the trailing (lightest) chevron leads, each next one 300ms behind —
   a slow, deliberate build rather than a quick snap. */
.auth-intro .bm-chev-1 { animation-delay: 0.1s; }
.auth-intro .bm-chev-2 { animation-delay: 0.4s; }
.auth-intro .bm-chev-3 { animation-delay: 0.7s; }

/* Offsets are viewBox user units: -18 of 60 ≈ a third of the mark's width. The
   longer the glide runs, the further it needs to travel or it reads as a crawl. */
@keyframes chev-fly-in {
  0%   { opacity: 0; transform: translateX(-18px) scaleX(0.74); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: translateX(0) scaleX(1); }
}

/* The wordmark and the form arrive after the mark has landed. */
.auth-intro .auth-title,
.auth-intro .auth-sub,
.auth-intro form,
.auth-intro .auth-link {
  opacity: 0;
  animation: auth-rise 0.6s cubic-bezier(0.16, 0.9, 0.3, 1) forwards;
}
/* The last chevron lands at ~1.85s; the card follows it in from there. */
.auth-intro .auth-title { animation-delay: 1.3s; }
.auth-intro .auth-sub   { animation-delay: 1.42s; }
.auth-intro form        { animation-delay: 1.54s; }
.auth-intro .auth-link  { animation-delay: 1.66s; }

@keyframes auth-rise {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The wordmark, exactly as the logo kit specifies (exports/schoolfleet-lockup.svg
   + README): Sora, "School" 500 in ink, "Fleet" 800 in primary orange, tracked
   -1.2 at 56px. It is NOT a gradient — this is live text, not an image, so it
   stays crisp, selectable, and animatable. */
.brand-wordmark {
  font-family: var(--sf-font-wordmark);
  font-size: 34px;
  letter-spacing: -0.021em; /* -1.2 / 56 from the lockup */
  line-height: 1.1;
  white-space: nowrap;
}
.brand-wordmark .wm-school { font-weight: 500; color: var(--sf-ink); }
.brand-wordmark .wm-fleet  { font-weight: 800; color: var(--sf-primary); }

@media (prefers-reduced-motion: reduce) {
  .auth-intro .bm-chev,
  .auth-intro .auth-title,
  .auth-intro .auth-sub,
  .auth-intro form,
  .auth-intro .auth-link {
    animation: none;
    opacity: 1;
    transform: none;
  }
}
.auth-card form { text-align: left; }
.auth-card label { display: block; font-size: 13px; font-weight: 600; margin: 12px 0 4px; color: var(--sf-ink); }
.auth-card input {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 12px;
  border: 1px solid #cbd5e1;
  border-radius: 9px;
  font-size: 15px;
  background: #fff;
  color: var(--sf-ink);
  transition: border-color 0.15s, box-shadow 0.15s;
}
/* Typing in a field lights its border. The keyboard ring (:focus-visible, set
   globally) is left alone — only the mouse/touch focus drops the outline. */
.auth-card input:focus {
  border-color: var(--sf-primary);
  box-shadow: 0 0 0 3px rgba(234, 88, 12, 0.16);
}
.auth-card input:focus:not(:focus-visible) { outline: none; }
/* Password field with an inline show/hide eye button. */
.auth-pass-wrap { position: relative; }
.auth-pass-wrap input { padding-right: 44px; }
.auth-pass-toggle {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: none;
  border: none;
  border-radius: 0 9px 9px 0;
  color: #64748b;
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.auth-pass-toggle svg { width: 19px; height: 19px; }

/* A forms-PIN box is a TEXT input the CSS masks, not a password field — the
   browser stuffs every password field it finds with the saved sign-in password,
   and a wrong PIN you can't read is worse than no PIN box. The mask comes off
   with the eye button (.pin-masked is dropped); the tracking stays either way so
   the digits don't shuffle when they're revealed. */
.pin-field { letter-spacing: 0.22em; font-family: inherit; }
.pin-masked { -webkit-text-security: disc; }

.auth-btn { width: 100%; margin-top: 18px; padding: 12px; font-size: 15px; }
.auth-err { color: #dc2626; font-size: 13px; margin-top: 10px; min-height: 16px; }
.auth-ok { color: var(--success); font-size: 13px; margin-top: 10px; min-height: 16px; }
.auth-link {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 13px;
  cursor: pointer;
  margin-top: 14px;
  padding: 4px;
  text-decoration: underline;
  border-radius: var(--radius-sm);
  transition: color 0.15s, background 0.15s;
}
.auth-link:active { color: var(--sf-primary-dark); }

/* ---------- Hover, pointer devices only ----------
   The rest of the app is mobile-first and only carries :active, but the sign-in
   card is where a lot of people meet the product on a desktop, so every control
   here answers the cursor. Gated on (hover: hover) so a tap on a phone can't
   leave the state stuck on after the finger lifts. */
@media (hover: hover) and (pointer: fine) {
  .auth-card input:hover:not(:focus) { border-color: #94a3b8; }
  .auth-card .auth-btn:not(:disabled):hover {
    filter: brightness(1.07);
    box-shadow: 0 6px 18px rgba(234, 88, 12, 0.3);
  }
  .auth-card .auth-btn:disabled { cursor: default; }
  .auth-pass-toggle:hover { color: var(--sf-primary); background: rgba(234, 88, 12, 0.08); }
  .auth-link:hover { color: var(--sf-primary-dark); background: var(--sf-tint); }
}

/* ===================== Driver app: "My Bus" ===================== */

/* Switcher for a driver who runs a morning bus and a different afternoon one. */
.mybus-switch {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 12px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.mybus-switch::-webkit-scrollbar { display: none; }
.mybus-chip {
  flex: none;
  padding: 8px 16px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.mybus-chip.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.mybus-chip:active { transform: scale(0.97); }

/* The header card: a bus THUMBNAIL beside the number, model and "can I drive
   it" line. Deliberately not a full-width hero — the photo is decoration, and
   at page width it pushed everything a driver actually needs below the fold. */
.mybus-hero {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  box-shadow: var(--shadow);
  margin-bottom: 16px;
}
/* Two-class selector on purpose: .bus-icon's own `width:100%` is declared
   later in this file, so a single .mybus-photo rule loses to it. */
.mybus-hero .mybus-photo { width: 108px; flex: none; }
/* Static thumbnail — the fleet grid's lift-on-hover would read as "tap me". */
.mybus-hero .mybus-photo:hover { transform: none; filter: none; }
.mybus-name { min-width: 0; flex: 1; }
.mybus-name h2 { margin: 0; font-size: 22px; }
.mybus-name p { margin: 3px 0 0; }

/* "Can I take this bus out today?" — a real text banner, because the
   windshield overlay it replaces is sized in cqw and would be ~5px tall on a
   thumbnail. */
.mybus-state {
  display: inline-block;
  margin-top: 8px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 800;
}
.mybus-state-note { display: block; margin-top: 4px; font-size: 12px; color: var(--text-dim); }
.mybus-state-ok   { background: rgba(34,197,94,0.16);  color: var(--success); }
.mybus-state-hold { background: rgba(224,160,18,0.18); color: var(--warning); }
.mybus-state-bad  { background: rgba(220,38,38,0.16);  color: var(--danger); }

/* The reference sections a driver drills into, as menu rows. Single column:
   this shell is phone-width, unlike the admin page's auto-fill grid. */
.mybus-menu { grid-template-columns: 1fr; margin-top: 16px; }
/* The back link out of one of those sections — the admin page's own copy sits
   flush against a pane heading, this one against a KPI row. */
#view > .admin-back { margin-bottom: 12px; }

.mybus-report { width: 100%; padding: 14px; font-size: 16px; margin-bottom: 18px; }
.mybus-kpis { margin-bottom: 4px; }
.mybus-kpi-v { font-size: 19px; }

.mybus-blank { text-align: center; padding: 44px 20px 26px; }
.mybus-blank-icon { font-size: 46px; line-height: 1; }
.mybus-blank h3 { margin: 12px 0 6px; }

/* ===================== Daily inspection (DVIR) =====================
   The card sits directly under the bus photo — before a run it's the first
   thing a driver deals with. It only wears the brand ring while a pre-trip is
   still owed; once signed it settles back into ordinary furniture. */
.insp-card {
  margin: 0 0 16px;
  padding: 12px 12px 8px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}
.insp-card-due { border-color: var(--brand-600); background: var(--accent-soft); }
.insp-card-h { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.insp-card-t { font-size: 14px; font-weight: 800; }

/* Pre and post sit in a tray of their own — a recessed panel inside the card,
   so the pair reads as one control with two halves instead of two tiles that
   happen to be side by side. The tray is translucent, not a flat colour: the
   card underneath turns orange while a pre-trip is owed, and an opaque tray
   would punch a grey hole through that tint. */
.insp-slots {
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
  padding: 6px; border-radius: 13px;
  background: rgba(0,0,0,0.16);
}
:root[data-theme="light"] .insp-slots { background: rgba(16,24,40,0.05); }

.insp-slot {
  display: flex; flex-direction: column; gap: 3px;
  padding: 10px; min-height: 64px;
  text-align: left; cursor: pointer;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--text);
  font: inherit;
  transition: background 0.15s, border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.insp-slot:active { transform: scale(0.985); }
/* Header line: fixed-width status mark, title, and the chevron pinned right. */
.insp-slot-h { display: flex; align-items: center; gap: 6px; }
.insp-slot-m { flex: none; width: 16px; text-align: center; font-size: 12px; line-height: 1; }
.insp-slot-k { font-size: 13.5px; font-weight: 800; min-width: 0; }
.insp-slot-go {
  flex: none; margin-left: auto; color: var(--muted); font-weight: 800;
  transition: color 0.15s, transform 0.15s;
}
/* Hangs off the title, not the tile edge — 16px mark + the 6px gap above it. */
.insp-slot-v { padding-left: 22px; font-size: 11.5px; color: var(--text-dim); line-height: 1.3; }
/* Not signed yet: the only slot that reads as an action. */
.insp-slot-todo { border-color: var(--brand-600); background: var(--bg-2); }
.insp-slot-todo .insp-slot-k, .insp-slot-todo .insp-slot-m, .insp-slot-todo .insp-slot-go { color: var(--accent); }
.insp-slot-done { border-color: rgba(34,197,94,0.4); }
.insp-slot-done .insp-slot-k, .insp-slot-done .insp-slot-m { color: var(--success); }
.insp-slot-flag { border-color: rgba(224,160,18,0.45); }
.insp-slot-flag .insp-slot-k, .insp-slot-flag .insp-slot-m { color: var(--warning); }
.insp-slot-bad { border-color: rgba(220,38,38,0.45); }
.insp-slot-bad .insp-slot-k, .insp-slot-bad .insp-slot-m { color: var(--danger); }

.insp-history {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; margin-top: 8px; padding: 9px 2px;
  border: none; background: none; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 700; color: var(--text-dim);
  transition: color 0.15s;
}
.insp-chev { color: var(--muted); transition: color 0.15s, transform 0.15s; }

/* Reports this driver signed today on a DIFFERENT bus — the spare they were
   handed this morning. Quieter than a slot: it's a receipt, not a thing to do. */
.insp-elsewhere { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.insp-elsewhere-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; padding: 8px 10px; text-align: left; cursor: pointer;
  border: 1px dashed var(--border); border-radius: 10px;
  background: var(--surface-2); color: var(--text);
  font: inherit; font-size: 12.5px; font-weight: 700;
  transition: background 0.15s, border-color 0.15s, transform 0.15s;
}
.insp-elsewhere-row:active { transform: scale(0.985); }

/* ---------- Fleet → Inspections: the day, bus by bus ----------
   The rows themselves are the file-cabinet accordion (.cabinet-file), because a
   bus's reports for a day are the same shape as a folder of documents and the
   status- colours already mean overdue / soon / ok / missing. Only two things
   here are new: the day's one-line score, and a second line inside the folder's
   name — the cabinet's names are a label, a bus's is a label plus who walked it. */
.insp-daystat {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px;
  padding: 9px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface-2);
  font-size: 13px; font-weight: 600; color: var(--text-dim);
}
.insp-daystat strong { font-size: 15px; font-weight: 800; color: var(--text); }
/* Green while every bus that ran was walked; red the moment one wasn't — this
   line is the answer to the only question the screen is asked in the morning. */
.insp-daystat.ok    { border-color: rgba(34,197,94,0.4); background: rgba(16,185,129,0.10); }
.insp-daystat.ok strong    { color: var(--success); }
.insp-daystat.needs { border-color: rgba(239,68,68,0.45); background: rgba(239,68,68,0.10); }
.insp-daystat.needs strong { color: var(--danger); }

.cf-name .cf-sub {
  display: block; margin-top: 2px;
  font-weight: 500; font-size: 12px; line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.insp-hist { display: block; }

/* ---------- "My Bus": pointer feedback ----------
   This screen is a driver's home, and most of it is a button — the bus chips,
   both big report buttons, the two inspection slots, the swap cards, the menu
   rows. On a phone the press state carries that; on a desktop nothing moved
   until the click landed, so each control now answers the cursor first.

   All of it is gated on a real pointer. These same controls are tapped on a
   phone all day, and an ungated :hover sticks to whatever was last touched
   until something else is — a stuck orange border on a bus chip reads as
   "selected", which is a lie about which bus you're looking at. */
@media (hover: hover) and (pointer: fine) {
  /* Bus switcher: an unpicked chip warms up; the picked one is already orange
     and must not shift, or hovering it would read as a state change. */
  .mybus-chip:not(.active):hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

  /* The two full-width actions. "Report a problem" is the loud one and gets
     lift; the wash/other-bus outlines just fill in. */
  .mybus-report.btn-primary:not(:disabled):hover {
    filter: brightness(1.07);
    box-shadow: var(--shadow);
  }
  .mybus-report.btn-outline:not(:disabled):hover {
    border-color: var(--accent);
    color: var(--accent);
    background: var(--accent-soft);
  }

  /* Inspection slots. The state border means something — todo orange, done
     green, flagged yellow, unsafe red — so hover never repaints it a different
     colour; it deepens the one that's already there and lifts the tile out of
     its tray. The old hover dropped the full card shadow under a 64px tile,
     which read as the whole slot detaching from the card. */
  .insp-slot:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-control);
  }
  .insp-slot:hover .insp-slot-go { color: var(--accent); transform: translateX(2px); }
  /* Pressing beats hovering: without this the lift above would still be in
     force during the click and the tile would never appear to go down. */
  .insp-slot:hover:active { transform: scale(0.985); box-shadow: none; }

  .insp-slot-todo:hover { background: var(--accent-soft); border-color: var(--accent); }
  .insp-slot-done:hover { border-color: rgba(34,197,94,0.75); }
  .insp-slot-flag:hover { border-color: rgba(224,160,18,0.8); }
  .insp-slot-bad:hover { border-color: rgba(220,38,38,0.8); }
  /* A signed slot opens the filed report, so its chevron stays the colour of
     the verdict rather than turning brand orange. */
  .insp-slot-done:hover .insp-slot-go { color: var(--success); }
  .insp-slot-flag:hover .insp-slot-go { color: var(--warning); }
  .insp-slot-bad:hover .insp-slot-go { color: var(--danger); }

  .insp-history:hover { color: var(--accent); }
  .insp-history:hover .insp-chev { color: var(--accent); transform: translateX(2px); }
  .insp-elsewhere-row:hover { border-color: var(--accent); background: var(--bg-3); }

  /* Swap board: "I'll take it" is a first-tap-wins race, so it should feel
     live under the cursor. */
  /* Not .swap-card-taken — somebody else has it, so there's nothing to react to. */
  .swap-board-driver .swap-card:not(.swap-card-taken):hover { border-color: var(--accent); }
  .swap-board-driver .btn-primary:not(:disabled):hover { filter: brightness(1.07); }
  .swap-board-driver .btn-ghost:not(:disabled):hover { border-color: var(--accent); color: var(--accent); }
}

/* "This isn't your bus" — a caution on the checklist, and the same sentence on
   the office's copy of a report signed by someone else's driver. */
.insp-other-note {
  margin: 8px 0 0; padding: 8px 10px; border-radius: 10px;
  background: var(--accent-soft); color: var(--accent);
  font-size: 12.5px; font-weight: 700; line-height: 1.35;
}

/* ---- The checklist screen ---- */
.insp-head { margin-bottom: 14px; }
.insp-head h3 { margin: 0 0 2px; font-size: 20px; }
.insp-req {
  margin-left: 6px; padding: 1px 7px; border-radius: 999px;
  font-size: 10px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase;
  background: var(--accent-soft); color: var(--accent);
}
.insp-allok-note { margin: 6px 2px 12px; }

/* Form controls. The app's input styling is scoped to `.modal`, and this form
   is a screen, not a dialog — so it brings its own. 16px is deliberate: any
   smaller and iOS Safari zooms the page in when the field takes focus. */
.insp-label {
  display: block; margin-bottom: 7px;
  font-size: 12px; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: .04em;
}
.insp-input, .insp-note {
  width: 100%; box-sizing: border-box;
  padding: 11px 12px; border-radius: 9px;
  background: var(--bg); border: 1px solid var(--border); color: var(--text);
  font-family: inherit; font-size: 16px;
}
.insp-note { font-size: 15px; }

.insp-group-h { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
/* "2 of 5" — the checklist reads as a sequence of sections to walk, not one
   long page to sign. */
.insp-group-step {
  margin-left: 6px; padding: 1px 7px; border-radius: 999px;
  background: var(--surface-2); color: var(--text-dim);
  font-size: 10px; font-weight: 800; letter-spacing: .04em;
}
/* The section shortcut. It sits UNDER the items, full width, because that's
   where the driver ends up after reading them — putting it in the header made
   it the first thing the thumb reached. */
.insp-group-ok {
  display: block; width: 100%; margin-top: 10px;
  padding: 11px; border-radius: 10px; cursor: pointer;
  border: 1px dashed var(--border); background: var(--surface-2); color: var(--text-dim);
  font: inherit; font-size: 13px; font-weight: 800; text-transform: none; letter-spacing: 0;
}
.insp-group-ok:active { transform: scale(0.99); }
.insp-items { display: flex; flex-direction: column; gap: 8px; }
.insp-item {
  padding: 9px 10px;
  border: 1px solid var(--border); border-left: 3px solid var(--border);
  border-radius: 10px; background: var(--surface-2);
}
.insp-item.is-ok { border-left-color: var(--success); }
.insp-item.is-defect { border-left-color: var(--danger); background: rgba(220,38,38,0.07); }
.insp-item-label {
  display: flex; align-items: center; gap: 9px;
  font-size: 13.5px; font-weight: 600; line-height: 1.35; margin-bottom: 8px;
}
/* The picture of the thing being checked. A fixed tile so thirty rows of very
   different glyphs still line their text up in one column. */
.insp-item-ico {
  flex: none; width: 30px; height: 30px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-2); border: 1px solid var(--border);
  font-size: 16px; line-height: 1;
}
.insp-item.is-ok .insp-item-ico { background: rgba(34,197,94,0.12); border-color: rgba(34,197,94,0.4); }
.insp-item.is-defect .insp-item-ico { background: rgba(220,38,38,0.12); border-color: rgba(220,38,38,0.4); }
.insp-item-text { min-width: 0; }
/* The dot that marks a safety-critical item. Colour alone would be the only
   signal, so it also carries a title for anyone reading it out. */
.insp-crit { color: var(--danger); font-weight: 900; }
.insp-item-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.insp-btn {
  padding: 9px; border-radius: 9px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg-2); color: var(--text-dim);
  font: inherit; font-size: 13px; font-weight: 800;
}
.insp-item.is-ok .insp-btn-ok { background: rgba(34,197,94,0.18); border-color: var(--success); color: var(--success); }
.insp-item.is-defect .insp-btn-bad { background: rgba(220,38,38,0.18); border-color: var(--danger); color: var(--danger); }
.insp-note { margin-top: 8px; }

/* ---- A fault the shop already has (migration 0108) ----
   The item arrives pre-ticked as a defect, so it is already wearing the red
   left edge above. What it needs on top of that is a reason: WARNING yellow,
   not another red, because "this was reported and is still being worked on" is
   a different state from "the driver just found this", and two items on one
   checklist that look identical would hide which of them is news. */
.insp-item.is-carried { border-left-color: var(--warning); }
.insp-item.is-carried.is-defect { background: rgba(234,179,8,0.08); border-left-color: var(--warning); }
.insp-item.is-carried.is-defect .insp-item-ico {
  background: rgba(234,179,8,0.14); border-color: rgba(234,179,8,0.45);
}
/* Ticked Fixed: the row goes green like any other OK item, and the yellow edge
   goes with it — the driver has answered the question the carry was asking. */
.insp-item.is-carried.is-ok { border-left-color: var(--success); }
.insp-carried {
  display: flex; flex-direction: column; gap: 4px;
  margin: 0 0 9px; padding: 7px 9px; border-radius: 9px;
  background: var(--bg-2); border: 1px solid var(--border);
}
.insp-carried-t { font-size: 11.5px; color: var(--text-dim); line-height: 1.35; }
.insp-carried-n { font-size: 12.5px; color: var(--text); line-height: 1.4; font-style: italic; }
/* The consequence of tapping Fixed, shown only once it has been tapped: a
   sentence sitting under every carried item all the time is a paragraph the
   driver scrolls past thirty times. */
.insp-fixed-note {
  margin: 8px 0 0; padding: 7px 9px; border-radius: 9px;
  background: rgba(34,197,94,0.1); border: 1px solid rgba(34,197,94,0.35);
  color: var(--success); font-size: 12px; line-height: 1.4;
}
/* The same fact at the top of the form, and again on the My Bus card — so the
   pre-ticked items are explained before they're met, not after. */
.insp-carried-note {
  margin: 8px 0 0; padding: 8px 10px; border-radius: 10px;
  background: rgba(234,179,8,0.1); border: 1px solid rgba(234,179,8,0.35);
  color: var(--warning); font-size: 12.5px; font-weight: 600; line-height: 1.4;
}
.insp-standing {
  display: flex; flex-direction: column; gap: 3px;
  margin-top: 10px; padding: 9px 11px; border-radius: 10px;
  background: rgba(234,179,8,0.09); border: 1px solid rgba(234,179,8,0.32);
}
.insp-standing-h { font-size: 12px; font-weight: 800; color: var(--warning); }
.insp-standing-r { font-size: 12.5px; color: var(--text); line-height: 1.4; }
.insp-standing-f { margin-top: 3px; font-size: 11.5px; color: var(--text-dim); line-height: 1.4; }
/* The office's board: a bus can be walked clean every morning and still be
   broken, and this is the only thing on that row that says so. */
.insp-standing-tag { color: var(--warning); font-weight: 700; }

.insp-verdict { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.insp-vbtn {
  padding: 13px 8px; border-radius: 11px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-dim);
  font: inherit; font-size: 13.5px; font-weight: 800;
}
.insp-vbtn.active { background: rgba(34,197,94,0.18); border-color: var(--success); color: var(--success); }
.insp-vbtn-bad.active { background: rgba(220,38,38,0.18); border-color: var(--danger); color: var(--danger); }
.insp-unsafe-warn {
  margin: 10px 0 0; padding: 10px 12px; border-radius: 10px;
  background: rgba(220,38,38,0.1); border: 1px solid rgba(220,38,38,0.35);
  color: var(--danger); font-size: 12.5px; line-height: 1.45;
}

/* ---- Signature pad ----
   touch-action:none is load-bearing: without it the browser scrolls the page
   under the finger instead of letting the canvas draw. */
.sigpad-wrap {
  position: relative; margin-top: 4px;
  border: 1px dashed var(--border); border-radius: 12px;
  background: #ffffff; overflow: hidden;
}
.sigpad { display: block; width: 100%; height: 170px; touch-action: none; cursor: crosshair; }
.sigpad-line {
  position: absolute; left: 18px; right: 18px; bottom: 34px; height: 1px;
  background: #cbd5e1; pointer-events: none;
}
.sigpad-hint {
  position: absolute; left: 22px; bottom: 40px;
  font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: #94a3b8; pointer-events: none;
}
.sigpad-actions { display: flex; justify-content: flex-end; margin: 6px 0 10px; }

/* Deliberately NOT sticky. The bottom nav is fixed at z-index 48, so a sticky
   bar would sit underneath it — and the driver has to be at the bottom of the
   form to sign anyway, which is exactly where this is. */
.insp-footer {
  display: flex; flex-direction: column; align-items: stretch; gap: 10px;
  margin-top: 18px;
}
.insp-progress { text-align: center; font-size: 12.5px; font-weight: 700; color: var(--text-dim); }
.insp-progress-done { color: var(--success); }
.insp-footer .btn-primary { width: 100%; padding: 15px; font-size: 16px; }

/* ---- A filed report, read back ---- */
.insp-verdict-row { display: flex; align-items: center; gap: 10px; margin: 10px 0 4px; }
.insp-answers { display: flex; flex-direction: column; gap: 5px; }
.insp-answer { display: flex; gap: 8px; font-size: 13px; line-height: 1.35; }
.insp-answer.is-defect { color: var(--danger); font-weight: 700; }
.insp-answer-m, .insp-answer-i { flex: none; }
.insp-answer-i { width: 18px; text-align: center; }
.insp-sig-shown {
  padding: 8px; border: 1px solid var(--border); border-radius: 12px;
  background: #ffffff;
}
.insp-sig-shown img { display: block; width: 100%; height: auto; }

/* ===================== Driver app: "My File Cabinet" =====================
   No page header here — the subsection bar names the screen, so the chips are
   the first thing on it. */
.cab-chips { display: flex; flex-wrap: wrap; gap: 6px; }

/* ===================== Driver app: "My Details" =====================
   The office's half of the record and the driver's half, on one screen. The
   office's half is drawn as FACTS — no boxes, no borders — because a field that
   looks like an input a driver can't use is a broken field; a labelled line is
   simply information. */
/* `card` on its own draws nothing in this stylesheet — every card-ish block
   brings its own surface — so this one does too. */
.pf-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin: 0 0 14px;
}
.pf-h { margin: 0 0 2px; font-size: 14px; font-weight: 800; }
/* Outside a modal nothing sets a control's width, so an input keeps its
   intrinsic size and pushes the grid — and the whole page — wider than the
   phone. Same box as a dialog's fields, drawn on the card's own surface so the
   notched .blbl labels sit on the border rather than over a mismatched fill. */
.pf-card input, .pf-card select, .pf-card textarea {
  width: 100%;
  min-width: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 11px;
  border-radius: 8px;
  font-size: 14px;
  font-family: inherit;
}
.pf-card input:focus, .pf-card select:focus, .pf-card textarea:focus { border-color: var(--accent); }
.pf-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 6px; }
.pf-grid > div { display: flex; flex-direction: column; min-width: 0; }
.pf-full { grid-column: 1 / -1; }
.pf-card .pf-h:not(:first-child) {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.pf-card .muted.small { margin: 0 0 10px; }
.pf-facts { display: flex; flex-direction: column; gap: 2px; margin-top: 10px; }
.pf-fact {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.pf-fact:last-child { border-bottom: 0; }
.pf-fact-label { color: var(--text-dim); flex: none; }
.pf-fact-value { font-weight: 600; text-align: right; overflow-wrap: anywhere; }
/* Some facts are something to tap — the trip sheet's address opens a map, its
   phone number dials. Styled as the app's own links rather than the browser's
   default blue, which reads as a page from somewhere else. */
.pf-fact-value a { color: var(--accent); font-weight: 700; text-decoration: none; }

/* The one date on this screen a driver can be late for, beside the field they'd
   change when they fix it. */
.pf-warn {
  font-size: 12.5px;
  font-weight: 600;
  padding: 9px 11px;
  border-radius: 10px;
  margin: 0 0 12px;
  color: var(--warning);
  background: rgba(224,160,18,0.12);
  border: 1px solid rgba(224,160,18,0.35);
}
.pf-warn-bad {
  color: var(--danger);
  background: rgba(239,68,68,0.12);
  border-color: rgba(239,68,68,0.35);
}

.pf-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.pf-status { flex: 1; color: var(--danger); }
.pf-actions .btn-primary { min-width: 120px; }

/* ===================== Users admin screen ===================== */
.users-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 16px 8px;
}
.user-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin: 8px 16px; padding: 12px; }
.user-email { font-weight: 600; }
.user-actions { flex-shrink: 0; }

/* Role filter + search above the grouped list. */
.user-toolbar { padding: 0 16px 10px; display: flex; flex-direction: column; gap: 10px; }
.user-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.user-tab {
  font-size: 12.5px; font-weight: 600; padding: 6px 12px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border); color: var(--text-dim); cursor: pointer;
}
.user-tab b { font-weight: 800; opacity: .75; }
.user-tab.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.user-tab.active b { opacity: .9; }
.user-search {
  width: 100%; padding: 9px 12px; font-size: 14px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
}

/* One card per role, with its rows as a divided list inside. */
.user-group {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  margin: 0 16px 14px; overflow: hidden;
}
.user-group-head {
  padding: 10px 14px; background: var(--surface-2); border-bottom: 1px solid var(--border);
  display: flex; justify-content: space-between; align-items: center;
  font-size: 14px; font-weight: 700;
}
.user-group-head .count {
  background: var(--accent); color: #fff; font-size: 12px; font-weight: 700;
  padding: 2px 9px; border-radius: 10px;
}
.user-group .user-row { margin: 0; padding: 12px 14px; border-bottom: 1px solid var(--border); background: none; border-radius: 0; box-shadow: none; }
.user-group .user-row:last-child { border-bottom: none; }
.user-group .empty { padding: 14px; }
.user-row-off .user-main { opacity: .6; }

/* Inside the admin pane the groups already sit in a section card — drop the
   outer margins so they line up with the rest of the pane. */
.admin-section .user-toolbar { padding: 0 0 10px; }
.admin-section .user-group { margin: 0 0 12px; background: var(--bg); }
.admin-section .user-group .user-row { padding: 12px 14px; }
.btn-danger { background: #dc2626; color: #fff; border: none; border-radius: 8px; padding: 8px 12px; cursor: pointer; }

/* ============================================================
 * FLEET — grid of real (CC0) school-bus images with info overlaid
 * ============================================================ */
.bus-icon-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  align-items: end;
}
@media (max-width: 560px) { .bus-icon-grid { grid-template-columns: repeat(2, 1fr); } }

/* Fleet view toggle (Buses / Cards) — segmented control */
.view-toggle {
  display: inline-flex;
  align-items: center;
  height: var(--control-h);
  gap: 4px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px;
  flex-shrink: 0;
  box-shadow: var(--shadow-control);
}
.vt-btn {
  display: inline-flex; align-items: center; gap: 7px;
  border: 0; background: transparent;
  color: var(--text-dim);
  font-family: inherit; font-size: 13px; font-weight: 600;
  padding: 8px 15px; border-radius: 999px;
  cursor: pointer; white-space: nowrap;
  transition: background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.vt-btn .vt-ico { font-size: 15px; line-height: 1; }
.vt-btn:hover:not(.active) { color: var(--text); background: rgba(255,255,255,0.05); }
.vt-btn.active {
  background: var(--accent); color: #fff;
  box-shadow: 0 2px 6px rgba(0,0,0,0.28);
}
.vt-btn.active:active { transform: scale(0.97); }

/* Fleet group headers: Needs Service / Good to Go */
.fleet-group-h {
  display: flex; align-items: center; gap: 8px;
  font-size: 14px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.5px;
  margin: 20px 0 10px; padding-bottom: 6px;
  border-bottom: 2px solid var(--border);
}
.fleet-group-h span {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 20px; padding: 0 7px; border-radius: 999px;
  font-size: 12px; font-weight: 800; color: #fff;
}
.fleet-group-h.needs { color: var(--danger); border-color: rgba(239,68,68,0.45); }
.fleet-group-h.needs span { background: var(--danger); color: #fff; }
.fleet-group-h.due { color: var(--warning); border-color: rgba(224,160,18,0.45); }
.fleet-group-h.due span { background: var(--warning); color: #1f2937; }
.fleet-group-h.ok { color: var(--success); border-color: rgba(34,197,94,0.4); }
.fleet-group-h.ok span { background: var(--success); }
/* A group that is neither good news nor bad — buses nobody owes a walk-around
   on. Without a variant the count bubble is white text on nothing. */
.fleet-group-h.idle { color: var(--text-dim); }
.fleet-group-h.idle span { background: var(--muted); }

/* Frameless tile: the bus image fills it; info is overlaid absolutely.
   container-type makes cqw units below scale the overlays to the tile size. */
.bus-icon {
  position: relative;
  display: block;
  width: 100%;
  container-type: inline-size;
  background: none;
  border: 0;
  padding: 0;            /* no padding: overlay %s map directly onto the image */
  cursor: pointer;
  font-family: inherit;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.bus-icon:hover { transform: translateY(-4px); filter: drop-shadow(0 6px 10px rgba(0,0,0,0.35)); }
.bus-icon:active { transform: translateY(-1px) scale(0.99); }
.bus-icon-retired { opacity: 0.55; filter: grayscale(0.6); }

/* Multi-select mode: selectable bus tile + corner checkbox. */
.bus-icon.bus-selected { outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 10px; }
.bus-check {
  position: absolute;
  top: 4px;
  left: 6px;
  z-index: 3;
  font-size: 22px;
  line-height: 1;
  color: var(--accent);
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
  pointer-events: none;
}

.bus-photo { width: 100%; height: auto; display: block; pointer-events: none; }

/* Out-of-service buses gently pulse so they stand out. */
.bus-icon-oos { animation: bus-pulse 2.2s ease-in-out infinite; }
@keyframes bus-pulse {
  0%, 100% { filter: drop-shadow(0 0 0 rgba(239,68,68,0)); }
  50%      { filter: drop-shadow(0 0 8px rgba(239,68,68,0.6)); }
}

/* --- Overlays positioned on the front-view bus image (percentages) --- */
.bus-ov {
  position: absolute;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: inherit; line-height: 1; white-space: nowrap;
  pointer-events: none;
}
/* Windshield text — the glass is painted flat in the image itself (dark, or
   red for out-of-service), so we just lay the open-job titles on it */
.bus-ws {
  position: absolute;
  left: 18%; top: 13%; width: 64%; height: 24%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.8cqw; padding: 0.6cqw 2cqw; overflow: hidden;
  text-align: center; pointer-events: none;
}
.bus-ws-job {
  display: flex; align-items: flex-start; gap: 1.8cqw; justify-content: center;
  font-size: 4.2cqw; font-weight: 800; color: #ffffff;
  letter-spacing: 0.2px; line-height: 1.25; max-width: 100%; min-width: 0;
  text-shadow: 0 1px 3px rgba(0,0,0,1), 0 0 3px rgba(0,0,0,0.9);
}
.bus-ws-dot { width: 2.8cqw; height: 2.8cqw; border-radius: 50%; flex: none; margin-top: 0.9cqw; box-shadow: 0 0 0 1.5px rgba(255,255,255,0.6); }
/* Long titles wrap to a second line (clamped) instead of sticking out */
.bus-ws-txt {
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere; word-break: break-word;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
.bus-ws-more { font-size: 3.4cqw; color: #eef3ec; font-weight: 800; text-shadow: 0 1px 2px rgba(0,0,0,0.95); }
.bus-ws-empty { font-size: 4.4cqw; font-weight: 800; color: #c8f7d6; text-shadow: 0 1px 3px rgba(0,0,0,0.9); }

/* Status — a plate-style label covering the license plate */
.bus-ov-status {
  left: 50%; top: 81.5%; transform: translate(-50%, -50%);
  min-width: 21%;
  background: var(--st); color: #fff;
  font-size: 3.4cqw; font-weight: 800; letter-spacing: 0.3px;
  padding: 1.4cqw 3cqw; border-radius: 2.2cqw;
  border: 0.6cqw solid rgba(255,255,255,0.92);
  text-shadow: 0 1px 1px rgba(0,0,0,0.5);
  box-shadow: 0 2px 5px rgba(0,0,0,0.5);
}
/* App bus number — bold black in a white oval with thick green outline,
   on BOTH bumper sides (where the real numbers are) */
.bus-ov-num {
  top: 81%; transform: translate(-50%, -50%);
  min-width: 7cqw; padding: 1.5cqw 2.8cqw;
  background: #fff; color: #111;
  border: 1.9cqw solid var(--accent); border-radius: 50%;   /* small oval, thick ring */
  font-size: 5.6cqw; font-weight: 800; line-height: 1;
  box-shadow: 0 1px 4px rgba(0,0,0,0.45);
}
.bus-ov-num-l { left: 22%; }
.bus-ov-num-r { left: 78%; }

/* Days-out-of-service chip beside the status label */
.oos-days {
  margin-left: 1.8cqw;
  background: rgba(255,255,255,0.95);
  color: #b01616;
  font-size: 3cqw; font-weight: 900;
  padding: 0.3cqw 1.8cqw; border-radius: 1.4cqw;
  text-shadow: none;
}

/* ============================================================
 * DRIVER PHOTOS — grid of cards + avatars
 * ============================================================ */
/* One rule for every width: the card has a minimum it needs to hold a photo
   and a name, and the grid fits as many as the screen allows — one on a phone,
   two on a tablet, four on a desk. No breakpoints to keep in step. */
.pfp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(258px, 1fr));
  gap: 10px;
}

/* The round avatar (photo or initials) */
.pfp {
  display: inline-flex; align-items: center; justify-content: center;
  width: 56px; height: 56px; border-radius: 50%;
  overflow: hidden; flex-shrink: 0;
  background: var(--surface-2);
  border: 2px solid var(--border);
}
.pfp img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pfp-initials {
  color: #fff; font-weight: 800; letter-spacing: 0.5px;
  background: hsl(var(--pfp-hue, 150), 45%, 42%);
  border-color: hsl(var(--pfp-hue, 150), 45%, 32%);
}
.pfp-lg { width: 72px; height: 72px; font-size: 26px; }
.pfp-xl { width: 92px; height: 92px; font-size: 32px; border-width: 3px; }

/* Color-coded ring around the photo (mirrors bus status colors) */
.pfp-ring-ok       { border-color: var(--success); box-shadow: 0 0 0 3px rgba(34,197,94,0.18); }
.pfp-ring-soon     { border-color: var(--warning); box-shadow: 0 0 0 3px rgba(245,158,11,0.18); }
.pfp-ring-missing  { border-color: var(--warning); box-shadow: 0 0 0 3px rgba(245,158,11,0.18); }
.pfp-ring-overdue  { border-color: var(--danger);  box-shadow: 0 0 0 3px rgba(239,68,68,0.20); }
.pfp-ring-muted    { border-color: var(--muted); filter: grayscale(0.5); }

/* Row card: photo left, name over status beside it, bus chip on the right rail
   — the shape every people list uses, and the same one on a phone as on a desk.
   Two rows with the photo spanning both, so the card is exactly as tall as its
   contents; the 1:1 square it replaced was mostly empty space. */
.pfp-card {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  align-content: center;
  column-gap: 13px;
  row-gap: 3px;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 13px 52px 13px 14px;   /* right pad clears the bus chip */
  cursor: pointer;
  font-family: inherit;
  color: var(--text);
  transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}
.pfp-card .pfp-lg { grid-row: 1 / span 2; width: 62px; height: 62px; font-size: 23px; }
.pfp-card:hover { transform: translateY(-3px); box-shadow: 0 8px 18px rgba(0,0,0,0.28); border-color: var(--accent); }
.pfp-card:active { transform: translateY(-1px) scale(0.99); }
.pfp-selected { outline: 2px solid var(--accent); outline-offset: -2px; }

.pfp-name {
  font-weight: 700; font-size: 15px; line-height: 1.2;
  display: flex; align-items: center; gap: 4px; justify-content: flex-start; flex-wrap: wrap;
}
.pfp-tag {
  justify-self: start;
  font-size: 11px; font-weight: 700;
  padding: 2px 10px; border-radius: 999px;
  background: rgba(255,255,255,0.06); color: var(--text-dim);
}
.pfp-tag.cflag-ok      { background: rgba(16,185,129,0.15); color: var(--success); }
.pfp-tag.cflag-soon    { background: rgba(245,158,11,0.15); color: var(--warning); }
.pfp-tag.cflag-missing { background: rgba(245,158,11,0.15); color: var(--warning); }
.pfp-tag.cflag-overdue { background: rgba(239,68,68,0.18);  color: var(--danger); }
.pfp-tag.cflag-muted   { background: rgba(148,163,184,0.15); color: var(--muted); }

/* bus-number chip on the card's right rail — top-left is the photo's spot */
.pfp-bus {
  position: absolute; right: 13px; top: 50%; transform: translateY(-50%);
  min-width: 26px; height: 22px; padding: 0 7px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: #fff;
  font-size: 12px; font-weight: 800; border-radius: 8px;
}
.pfp-bus.bus-sb { background: var(--muted); color: #fff; }
/* "+N" suffix shown on the bus badge when a driver runs more than one bus */
.pfp-bus-more { margin-left: 3px; font-size: 10px; font-weight: 700; opacity: 0.85; }
/* Select tick takes the photo's corner, since the right rail is the bus chip's. */
.pfp-check { position: absolute; top: 7px; left: 7px; font-size: 20px; line-height: 1; }

/* Driver-detail header photo + controls */
.dd-photo { position: relative; flex-shrink: 0; }
.dd-photo-bus {
  position: absolute; bottom: -2px; left: -4px;
  min-width: 24px; height: 22px; padding: 0 7px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: #fff; font-size: 12px; font-weight: 800;
  border-radius: 8px; border: 2px solid var(--surface);
}
.dd-photo-bus.bus-sb { background: var(--muted); color: #fff; }

/* The avatar looks unchanged, but it is tappable to open the photo popup. */
.dd-photo-click { display: block; cursor: pointer; border-radius: 50%; }
.dd-photo-click:active { transform: scale(0.97); }

/* Popup window with the photo actions. */
.photo-menu { text-align: center; }
.photo-menu h3 { margin: 0 0 14px; }
.photo-menu-actions { display: flex; flex-direction: column; gap: 10px; }
.photo-menu-actions button { width: 100%; }

/* Full-size profile photo viewer. */
.photo-view { text-align: center; }
.photo-view img {
  display: block; width: 100%; max-height: 70vh; object-fit: contain;
  border-radius: 12px; border: 1px solid var(--border); margin-bottom: 12px;
}
.photo-view button { width: 100%; }

/* ============================================================
 * Fill & Sign — full-screen interactive PDF
 * ============================================================ */
.fs-overlay {
  position: fixed; inset: 0; z-index: 4000;
  /* Soft, neutral backdrop so the white form pages read cleanly. */
  background:
    radial-gradient(120% 90% at 50% -10%, rgba(21,163,74,.08), transparent 60%),
    var(--bg-3);
  display: flex; flex-direction: column;
}
.fs-bar {
  display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap;
  padding: 10px 14px; background: var(--surface);
  color: var(--text); border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow); flex: 0 0 auto;
}
.fs-title {
  font-weight: 700; font-size: 15px; letter-spacing: .2px;
  display: flex; align-items: center; gap: 8px;
}
.fs-title-ico {
  display: inline-grid; place-items: center; width: 30px; height: 30px;
  border-radius: 9px; background: linear-gradient(135deg, var(--accent), var(--warning));
  font-size: 15px;
}
.fs-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-right: auto; margin-left: 6px; }
/* A pill that groups related controls (pen / zoom). */
.fs-group {
  display: flex; align-items: center; gap: 4px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 3px 5px;
}
.fs-group-label {
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .6px; color: var(--muted); padding: 0 4px 0 6px;
}
.fs-tool {
  background: transparent; color: var(--text); border: 1px solid transparent;
  border-radius: 999px; padding: 6px 12px; font-size: 13px; font-weight: 600;
  cursor: pointer; min-width: 34px; transition: background .12s, color .12s;
}
.fs-tool:hover { background: var(--surface-2); }
.fs-tool:active { transform: scale(0.96); }
/* Pen-thickness stepper: − / preview-dot / +. The dot scales with the width. */
.fs-step {
  display: inline-grid; place-items: center; width: 32px; height: 30px;
  background: transparent; color: var(--text); border: none; border-radius: 999px;
  font-size: 20px; line-height: 1; font-weight: 600; cursor: pointer; transition: background .12s;
}
.fs-step:hover { background: var(--surface); }
.fs-step:active { transform: scale(.94); }
.fs-step:disabled { opacity: .3; cursor: default; }
.fs-pen-preview { display: inline-grid; place-items: center; width: 24px; height: 24px; }
.fs-pen-dot { display: block; border-radius: 50%; background: var(--text); transition: width .1s, height .1s; }
/* Pen-color swatches (black / blue). */
.fs-ink-c {
  display: inline-grid; place-items: center; width: 30px; height: 30px;
  background: transparent; border: 1px solid transparent; border-radius: 999px;
  cursor: pointer; transition: box-shadow .12s;
}
.fs-ink-c:hover { background: var(--surface); }
.fs-swatch {
  display: block; width: 16px; height: 16px; border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.5);
}
.fs-ink-on { box-shadow: inset 0 0 0 2px var(--accent); }
.fs-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fs-actions .btn-primary { padding: 8px 16px; border-radius: 999px; }
.fs-status { color: var(--text-dim); text-align: center; padding: 40px 16px; }
.fs-error {
  color: var(--text); background: rgba(239,68,68,.12); border: 1px solid rgba(239,68,68,.35);
  border-radius: var(--radius); padding: 16px; max-width: 560px; margin: 32px auto;
}
/* Viewport: we do our own pan/zoom via a transform, so no native scroll. */
.fs-pages {
  flex: 1 1 auto; overflow: hidden; position: relative; touch-action: none;
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
/* Inner wrapper that is translated + scaled by the gesture controller. */
.fs-zoom {
  position: absolute; top: 0; left: 0; transform-origin: 0 0; will-change: transform;
  display: flex; flex-direction: column; align-items: center; gap: 18px; width: max-content;
}
.fs-page {
  position: relative; background: #fff; border-radius: 4px;
  box-shadow: 0 8px 28px rgba(4, 14, 8, 0.35), 0 0 0 1px rgba(0,0,0,.04);
}
.fs-page canvas { display: block; border-radius: 4px; }
/* Full-page handwriting layer */
.fs-ink { position: absolute; inset: 0; touch-action: none; cursor: crosshair; }

/* Small "external attendee" tag shown after an outside driver's name. */
.badge-ext {
  display: inline-block; font-size: 10px; font-weight: 800; letter-spacing: .5px;
  line-height: 1; padding: 3px 6px; border-radius: 6px; vertical-align: middle;
  background: rgba(37, 99, 235, .14); color: #2563eb;
  box-shadow: inset 0 0 0 1px rgba(37, 99, 235, .3);
}

/* Field inputs sitting on the rendered form */
/* The Fill & Sign cabinet button */
.big-btn-sign { border-color: #d97706; }
.big-btn-sign .big-icon { color: #d97706; }

/* Bus picker (DS-873 / DS-875 vehicle section) */
.bus-pick-list {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  max-height: 50vh; overflow: auto; margin: 8px 0;
}
.bus-pick-item {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface, #fff); cursor: pointer; text-align: left;
}
.bus-pick-item:hover { border-color: #2563eb; background: rgba(37,99,235,.06); }
@media (max-width: 520px) { .bus-pick-list { grid-template-columns: 1fr; } }

/* ---- Compliance report: collapsible requirement picker ---- */
.cr-disclose { border: 1px solid var(--border); border-radius: 10px; background: var(--bg-2); }
.cr-disclose-sum {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 11px 12px; font-size: 14px; font-weight: 600; color: var(--text);
}
.cr-disclose-sum::-webkit-details-marker { display: none; }
.cr-disclose-sum::after { content: "▸"; color: var(--text-dim); transition: transform .15s ease; }
.cr-disclose[open] .cr-disclose-sum::after { transform: rotate(90deg); }
.cr-disclose .dp-count { font-weight: 500; }
.cr-disclose > .driver-picker { padding: 0 10px 10px; }

/* ---- Compliance report: drag-to-reorder rows ---- */
.cr-disclose .dp-row { justify-content: flex-start; gap: 10px; }
.cr-disclose .dp-row.selected::before { display: none; }
.cr-disclose .dp-row.selected::after {
  content: "✓"; margin-left: auto; color: var(--accent); font-weight: 700;
}
.cr-disclose .dp-name { flex: 1 1 auto; min-width: 0; }
.dp-grip {
  flex: 0 0 auto; color: var(--text-dim); letter-spacing: -2px;
  cursor: grab; user-select: none; font-size: 15px; line-height: 1;
}
.dp-row.dragging {
  opacity: .96; border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  box-shadow: 0 6px 16px rgba(0,0,0,0.28);
}
.dp-list.reordering { touch-action: none; cursor: grabbing; }

/* ============================================================
 * SMS messaging (two-way texting with drivers) — Broadcast screen
 * ============================================================ */
.sms-devnote {
  margin-top: 10px;
  padding: 8px 10px;
  border-radius: 8px;
  background: rgba(212, 160, 23, 0.12);
  border: 1px solid rgba(212, 160, 23, 0.4);
  color: var(--text);
  font-size: 12.5px;
}

.sms-thread-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }
.sms-thread-row {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 12px; padding: 12px 14px; cursor: pointer;
}
.sms-thread-row:hover { border-color: var(--accent); }
.sms-thread-row.unread { border-left: 3px solid var(--accent); }
.sms-thread-main { flex: 1; min-width: 0; }
.sms-thread-name { font-weight: 700; color: var(--text); }
.sms-thread-preview { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sms-thread-meta { text-align: right; flex-shrink: 0; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.sms-unread {
  display: inline-block; min-width: 20px; padding: 1px 6px;
  background: var(--accent); color: #fff; border-radius: 999px;
  font-size: 12px; font-weight: 700; text-align: center;
}

/* Chat thread (inside a modal) */
.sms-chat { display: flex; flex-direction: column; }
.sms-chat-log {
  display: flex; flex-direction: column; gap: 8px;
  max-height: 52vh; overflow-y: auto;
  padding: 10px; margin: 10px 0;
  background: var(--bg); border: 1px solid var(--border); border-radius: 12px;
}
.sms-bubble { max-width: 78%; padding: 8px 12px; border-radius: 14px; }
.sms-bubble-body { white-space: pre-wrap; word-break: break-word; }
.sms-bubble-time { margin-top: 3px; font-size: 11px; opacity: 0.8; }
.sms-in { align-self: flex-start; background: var(--bg-3); border-bottom-left-radius: 4px; }
.sms-out { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
.sms-out .sms-bubble-time { color: #eafff0; }
.sms-failed { color: #ffd5d5; }
.sms-chat-compose { display: flex; gap: 8px; align-items: flex-end; }
.sms-chat-compose textarea { flex: 1; resize: vertical; }

/* ============================================================
 * Roster progress screen — built on the shared design tokens
 * (--surface / --border / --radius) and shared components
 * (.section-head, .section-title, .badge, .pill-*, .cpill-*).
 * ============================================================ */
.tasks-wrap { padding: 16px; max-width: 900px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }

/* Cards share the app's card recipe (cf. .mech-card / .due-card). */
.tp-card, .task-section {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
}
.task-section .section-head { margin: 0 0 12px; }
.task-section .section-head + .muted { margin-top: -6px; }
.badge-done { background: var(--success); }
/* Neutral "low" priority pill, completing the shared .pill-* family. */
.pill-low { background: var(--bg-3); color: var(--text-dim); }

/* Today's focus — the paced "finish N drivers" mission. */
.focus-stepper { display: inline-flex; align-items: center; gap: 4px; }
.focus-stepper .btn-xs { padding: 3px 10px; font-size: 15px; line-height: 1; font-weight: 700; }
.focus-goal { font-size: 12px; font-weight: 700; color: var(--text-dim); min-width: 46px; text-align: center; }
.focus-list { display: flex; flex-direction: column; gap: 6px; }
.focus-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; padding: 10px 11px; cursor: pointer; color: var(--text); }
.focus-row:hover { background: var(--surface-2); }
.focus-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.focus-name { font-size: 14px; font-weight: 700; }
.focus-items { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.focus-badge { background: var(--warning); }

/* Collapsible section header (tap to fold heavy sections). */
.sec-collapsible { width: 100%; background: none; border: none; cursor: pointer; padding: 0; color: inherit; text-align: left; }
.sec-head-right { display: inline-flex; align-items: center; gap: 8px; }
.sec-caret { color: var(--text-dim); font-size: 12px; }

/* ============================================================
 * Vibrant layer — inject some color/energy into the progress screen
 * while staying readable in both light and dark themes.
 * ============================================================ */
/* Colorful, gradient cards */
.tp-card { background: linear-gradient(135deg, rgba(21,163,74,.16), rgba(224,160,18,.13)) , var(--surface); border-color: rgba(21,163,74,.4); }
.focus-section { background: linear-gradient(135deg, rgba(245,158,11,.14), rgba(236,72,153,.10)), var(--surface); border-color: rgba(245,158,11,.4); }

/* Distinct accent bar per section header */
.focus-section .section-title { border-left-color: #f59e0b; }
.collect-section .section-title { border-left-color: #14b8a6; }

/* Punchier ring + progress meter */
.tp-ring-num { font-size: 23px; font-weight: 800; }
.tp-meter, .gap-mini-meter, .tp-board-meter, .focus-mini { background: rgba(0,0,0,.12); }
.tp-meter > span { background: linear-gradient(90deg, var(--brand-400), var(--brand-600)) !important; }

/* Brighter chips + badges */
.tp-chip { border: none; }
.tp-chip.warn { color: #92600b; background: rgba(245,158,11,.22); }
.tp-chip.ok { color: #0f7a3a; background: rgba(16,185,129,.22); }
.tp-chip.fire { color: #b4460b; background: rgba(249,115,22,.20); }
.badge { background: linear-gradient(135deg, var(--accent), #37b56a); }
.focus-badge { background: linear-gradient(135deg, #f59e0b, #ef8f2a) !important; }


/* --- Gamified progress header --- */
.tp-top { display: flex; gap: 16px; align-items: center; }
.tp-ringwrap { flex: none; }
.tp-ring-bg { fill: none; stroke: var(--border); stroke-width: 8; }
.tp-ring-fg { fill: none; stroke-width: 8; stroke-linecap: round; transform: rotate(-90deg); transform-origin: 50% 50%; transition: stroke-dashoffset 0.7s cubic-bezier(.4,0,.2,1); }
.tp-ring-num { font-size: 22px; font-weight: 700; }
.tp-headmeta { flex: 1; min-width: 0; }
.tp-h1 { font-size: 17px; font-weight: 700; color: var(--text); }
.tp-h2 { font-size: 13px; color: var(--text-dim); margin-top: 1px; }
.tp-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.tp-chip { font-size: 11.5px; font-weight: 600; padding: 4px 9px; border-radius: 999px; background: var(--bg-2); color: var(--text-dim); border: 1px solid var(--border); }
.tp-chip.warn { color: var(--warning); background: rgba(245,158,11,.15); border-color: transparent; }
.tp-chip.ok { color: var(--success); background: rgba(16,185,129,.15); border-color: transparent; }
.tp-chip.fire { color: var(--accent); background: rgba(16,185,129,.12); border-color: transparent; }
.tp-next { font-size: 12px; color: var(--text-dim); margin-top: 8px; }
.tp-refresh { margin-top: 6px; padding-left: 0; }
.tp-meter { height: 9px; border-radius: 999px; background: var(--bg-2); overflow: hidden; margin-top: 14px; border: 1px solid var(--border); }
.tp-meter > span { display: block; height: 100%; border-radius: 999px; transition: width 0.7s cubic-bezier(.4,0,.2,1); }
.tp-alldone { text-align: center; padding: 22px 12px; font-size: 15px; font-weight: 700; color: var(--success); }

/* --- Leaderboard --- */
.tp-board { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
.tp-board-label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); margin-bottom: 8px; }
.tp-board-row { display: flex; align-items: center; gap: 10px; width: 100%; background: none; border: none; padding: 6px 2px; cursor: pointer; color: var(--text); }
.tp-board-name { font-size: 13.5px; font-weight: 600; flex: none; width: 38%; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tp-board-meter { flex: 1; height: 7px; border-radius: 999px; background: var(--bg-2); overflow: hidden; }
.tp-board-meter > span { display: block; height: 100%; border-radius: 999px; }
.tp-board-pct { flex: none; font-size: 12.5px; font-weight: 700; color: var(--text-dim); width: 38px; text-align: right; }


/* --- Compliance-gap groups --- */
.gap-group { border: 1px solid var(--border); border-radius: 10px; margin-top: 6px; overflow: hidden; background: var(--bg-2); }
.gap-head { display: flex; align-items: center; gap: 10px; width: 100%; background: none; border: none; padding: 11px 12px; cursor: pointer; color: var(--text); }
.gap-driver { flex: none; width: 34%; text-align: left; font-size: 14px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gap-mini-meter { flex: 1; height: 7px; border-radius: 999px; background: var(--surface); overflow: hidden; }
.gap-mini-meter > span { display: block; height: 100%; border-radius: 999px; }
.gap-caret { flex: none; color: var(--text-dim); font-size: 12px; width: 14px; text-align: center; }
.gap-list { border-top: 1px solid var(--border); }
.gap-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; background: none; border: none; border-bottom: 1px solid var(--border); padding: 10px 12px; cursor: pointer; color: var(--text); }
.gap-row:last-child { border-bottom: none; }
.gap-row:hover { background: var(--surface-2); }
.gap-name { flex: 1; min-width: 0; font-size: 13.5px; }
.gap-go { flex: none; color: var(--text-dim); font-size: 16px; }

/* --- Manage-daily modal rows --- */

/* --- Confetti --- */
.confetti { position: fixed; inset: 0; pointer-events: none; z-index: 9999; overflow: hidden; }
.confetti span { position: absolute; top: -14px; width: 9px; height: 14px; border-radius: 2px; opacity: .95; animation: confetti-fall linear forwards; }
@keyframes confetti-fall {
  0% { transform: translate(0, -10px) rotate(0deg); opacity: 1; }
  100% { transform: translate(var(--drift, 0), 105vh) rotate(720deg); opacity: .9; }
}
@media (prefers-reduced-motion: reduce) {
  .confetti { display: none; }
  .tp-ring-fg, .tp-meter > span { transition: none; }
}

/* ============================================================
 * DISPATCH BOARD
 * ============================================================ */
.disp-wrap { padding: 0 0 24px; }
/* ---- The subsection bar: the header's second row ----
 * The bar is not something the screen carries — it's the bottom row of the app
 * header, in the header's own colour, butted straight up under the title with
 * no line between them. What's below it is the page.
 *
 * So the tabs are cut out of the header the way a filing cabinet cuts them: the
 * tab you're on is filled with the PAGE's colour, outlined, and open at the
 * bottom, so it breaks the header's edge and runs into the screen below — you're
 * looking at the front of the folder, and the folder is the screen. The rest are
 * bare labels in the header. That continuity is the whole of the active state; a
 * chip that merely changes colour never said which tab the content belonged to.
 *
 * It stays put when the page scrolls, pinned directly under the header at
 * --topbar-h — a tab bar that slides away takes "where am I" with it. (top: 0
 * put it BEHIND the header, which is a taller z-index.)
 *
 * The bar is what scrolls sideways, so seven labels stop shrinking rather than
 * squeezing into a phone's width as a ribbon of unreadable stubs; the active one
 * is scrolled into view on paint.
 */
.disp-tabs {
  position: sticky;
  top: var(--topbar-h);
  z-index: 5;
  background: var(--chrome-bg);
  /* The header's bottom edge, which the active tab breaks through. The active
     tab paints over it — children paint above an inset shadow. */
  box-shadow: inset 0 -1px 0 var(--chrome-border);
  margin: 0 -16px 12px;
  padding: 8px 16px 0;
}
/* Butted against the header: cancel main's top padding so the two whites meet.
   Only when the bar is the first thing on the screen — otherwise it would be
   pulled over whatever is above it. */
main > .disp-wrap:first-child > .disp-tabs { margin-top: -16px; }
/* ...and the header drops its own bottom edge, because the bar continues it. */
body:has(.disp-tabs) .topbar-app { border-bottom-color: transparent; }
/* 8px of strip + a 34px tab (18px line + 8/8 padding) — held constant by the
   tab's line-height so this number stays true whether or not a tab carries a
   badge. Anything else that pins itself under the header (the fleet and staff
   search toolbars) has to clear the bar too, and this is how it knows. */
body:has(.disp-tabs) { --subbar-h: 42px; }
.disp-segs {
  display: flex;
  /* Stretch, so every tab reaches the foot of the strip and the active one can
     meet the page. */
  align-items: stretch;
  /* The gap between two runs is the whole of the separation, so it has to be
     wider than the gap between two tabs of the same run by an obvious margin. */
  gap: 22px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
}
.disp-segs::-webkit-scrollbar { display: none; }
/* One run and no grouping: the tabs own the width, as they always have, and two
   or three children share it evenly. */
.disp-segs > .disp-seg:only-child { flex: 1 0 auto; }
/* A run of related children (Drivers carries its compliance screens AND its
   payroll; Trips its bookings AND its billing) is a set of tabs with its name at
   the head; the gap to the next run is what separates them. */
.disp-seg {
  display: flex;
  flex: 0 1 auto;
  align-items: stretch;
  gap: 3px;
}
/* The run's name, riding at the head of its own run. Small, uppercase and in the
   brand's colour, with none of a tab's outline or fill, so it reads as the name
   of the run rather than as a tab somebody disabled — which is exactly how a
   grey caption over the bar, and a grey caption in it, both failed. */
.sub-run-tag {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px 0 2px;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
  pointer-events: none;
}
/* A tab you're not on is a label in the header — no fill and no outline, so the
   header stays one clean surface and only the open folder is drawn. */
.disp-tab {
  position: relative;
  flex: 1 0 auto;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px 14px;
  font-size: 13px;
  /* Fixed, so a tab with a badge is exactly as tall as one without and the
     bar's height stays the 42px --subbar-h promises. */
  line-height: 18px;
  font-weight: 600;
  background: none;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 9px 9px 0 0;
  color: var(--text-dim);
  cursor: pointer;
  transition: background .13s ease, color .13s ease;
}
/* The front of the folder: the page's own colour, outlined, open at the bottom.
   Its last pixel row paints over the header's edge, which is what joins the tab
   to the screen under it. */
.disp-tab.active {
  z-index: 1;
  background: var(--bg);
  border-color: var(--border);
  color: var(--text);
  font-weight: 700;
}
/* Hover brings a tab forward without bringing it to the front. */
.disp-tab:not(.active):hover { background: var(--bg-2); color: var(--text); }
.disp-body { background: var(--modal-bg); border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--shadow); padding: 14px; display: flex; flex-direction: column; gap: 12px; }

.disp-datebar { display: flex; align-items: center; gap: 6px; }
.disp-datebar input[type="date"] { flex: 1; padding: 7px 9px; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; color: var(--text); font-size: 13.5px; }
.disp-daylabel { font-size: 13.5px; font-weight: 700; color: var(--text-dim); margin: -4px 2px 2px; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.disp-plan-tag { font-size: 12px; font-weight: 700; color: var(--accent); background: rgba(21,163,74,0.14); padding: 2px 9px; border-radius: 999px; }
.disp-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
/* The Sort dropdown rides the chips row — the day's facts on the left, how it's
   laid out on the right. Shrunk to the chips' scale: a full-height toolbar
   control here would read as the row's subject rather than its setting. */
.disp-chips .sort-select { height: 32px; margin-left: auto; padding: 0 10px; font-size: 12.5px; }
.disp-chip { font-size: 12px; font-weight: 600; padding: 3px 9px; border-radius: 999px; background: var(--surface); border: 1px solid var(--border); color: var(--text-dim); }
.disp-chip.ok { background: rgba(16,185,129,0.15); color: var(--success); border-color: transparent; }
.disp-chip.bad { background: rgba(239,68,68,0.15); color: var(--danger); border-color: transparent; }

/* Collapsible category groups (Boys / Girls / Mixed / Standby / Field trips) */
.disp-group { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.disp-group.collapsed { background: var(--bg); }
.disp-group-head { display: flex; align-items: center; gap: 10px; width: 100%; padding: 22px 16px; background: none; border: none; text-align: left; color: var(--text); }
.disp-group.collapsed .disp-group-head:active { background: var(--surface-2, var(--bg)); }
.disp-group-toggle { display: flex; align-items: center; gap: 9px; flex: 1; min-width: 0; background: none; border: none; padding: 0; cursor: pointer; color: var(--text); }
.disp-group-chev { font-size: 12px; color: var(--text-dim); width: 12px; }
.disp-group-count { font-size: 12px; font-weight: 600; color: var(--text-dim); }
.disp-group-head .run-badge { font-size: 13px; padding: 3px 11px; }
.disp-group-head .disp-group-count { font-size: 13px; }
/* "2 trips · 5 buses" reads as one fact — don't break it across lines. */
.disp-group.trips .disp-group-count { white-space: nowrap; }
/* Green counterpart to .run-gaps — same pill geometry as the run "✓ Covered"
 * badge so the head reads as one family of status chips, not a stray glyph. */
.disp-group-ok { margin-left: auto; flex: none; font-size: 11.5px; font-weight: 700; padding: 2px 9px; border-radius: 999px; background: rgba(16,185,129,0.15); color: var(--success); }
.disp-group-body { padding: 4px 13px 13px; display: flex; flex-direction: column; gap: 12px; }
.disp-group.cat-boys { border-left: 3px solid var(--cat-boys); }
.disp-group.cat-girls { border-left: 3px solid var(--cat-girls); }
.disp-group.cat-mixed, .disp-group.cat-other { border-left: 3px solid #a855f7; }
.disp-group.cat-standby { border-left: 3px solid var(--muted); }

.disp-run { background: transparent; border: none; }
.disp-group-body .disp-run + .disp-run { border-top: 1px solid var(--border); padding-top: 12px; }
.disp-run + .disp-run, .disp-run.trips { border-top: 1px solid var(--border); padding-top: 12px; }
.disp-run-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 0 2px 8px; }
.run-time { font-weight: 800; font-size: 14px; color: var(--text); min-width: 62px; }
.run-badge { font-size: 11.5px; font-weight: 700; padding: 2px 8px; border-radius: 999px; }
.run-badge.cat-boys { background: color-mix(in srgb, var(--cat-boys) 18%, transparent); color: var(--cat-boys); }
.run-badge.cat-girls { background: color-mix(in srgb, var(--cat-girls) 18%, transparent); color: var(--cat-girls); }
.run-badge.cat-standby { background: rgba(148,163,184,0.2); color: var(--muted); }
.run-badge.cat-mixed, .run-badge.cat-other { background: rgba(168,85,247,0.16); color: #a855f7; }
.run-place { font-size: 12px; color: var(--text-dim); }
.run-count { font-size: 12px; color: var(--text-dim); }
.run-gaps { margin-left: auto; font-size: 11.5px; font-weight: 700; padding: 2px 8px; border-radius: 999px; background: rgba(239,68,68,0.15); color: var(--danger); }

/* Per-run collapsible cards (the day's schedule, one comfortable row per run) */
.disp-run-card { background: var(--bg); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
.disp-run-card.attn { border-color: rgba(239,68,68,0.45); }
.disp-run-toggle { display: flex; flex-direction: column; gap: 7px; width: 100%; text-align: left; background: none; border: none; padding: 12px 13px; cursor: pointer; color: var(--text); -webkit-tap-highlight-color: transparent; }
.disp-run-toggle:active { background: var(--surface); }
.disp-run-card.open .disp-run-toggle { border-bottom: 1px solid var(--border); }
.run-toggle-main { display: flex; align-items: center; gap: 9px; }
.run-toggle-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; padding-left: 19px; }
.run-dot { flex: none; width: 10px; height: 10px; border-radius: 999px; }
.run-dot.ok { background: var(--success); }
.run-dot.bad { background: var(--danger); }
.run-name-lbl { flex: 1; min-width: 0; font-size: 14.5px; font-weight: 700; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.disp-run-chev { flex: none; font-size: 13px; color: var(--text-dim); }
.run-status { font-size: 11.5px; font-weight: 800; padding: 2px 9px; border-radius: 999px; }
.run-status.ok { background: rgba(16,185,129,0.15); color: var(--success); }
.run-status.bad { background: rgba(239,68,68,0.15); color: var(--danger); }
.disp-run-card .disp-slot-grid { padding: 10px 13px 13px; }

/* Header row: the expand toggle plus a per-run "change time today" button. */
.disp-run-headrow { display: flex; align-items: stretch; }
.disp-run-headrow .disp-run-toggle { flex: 1; width: auto; min-width: 0; }
.disp-run-time-btn { flex: none; align-self: stretch; background: none; border: none; border-left: 1px solid var(--border); padding: 0 13px; font-size: 16px; cursor: pointer; color: var(--text-dim); -webkit-tap-highlight-color: transparent; }
.disp-run-time-btn:active { background: var(--surface); }

/* Re-timed run (early dismissal / delayed opening): new time in amber, original
 * struck out, plus a "moved" tag. */
.disp-run-card.retimed { border-color: rgba(217,119,6,0.45); }
.run-time.moved { color: #d97706; }
.run-time-was { font-size: 11px; color: var(--text-dim); text-decoration: line-through; white-space: nowrap; }
.run-moved-tag { flex: none; font-size: 9.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; padding: 1px 6px; border-radius: 999px; background: rgba(217,119,6,0.15); color: #d97706; }

/* Closed session (school off): grey, no coverage demanded. */
.disp-group.closed { opacity: .9; }
.disp-group-toggle.static { cursor: default; flex: 1; }
.disp-group-closed { font-size: 12.5px; font-weight: 700; color: var(--text-dim); }

/* Template list rows with an inline delete. */
.menu-row { display: flex; align-items: center; gap: 6px; }
.menu-row .menu-item { flex: 1; min-width: 0; }

.disp-slot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 8px; padding: 0; }
.disp-slot { display: flex; align-items: stretch; justify-content: space-between; gap: 6px; padding: 8px 10px; background: var(--bg); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 9px; }
.disp-slot.gap { border-left-color: var(--danger); background: rgba(239,68,68,0.06); }
.slot-lines { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.slot-bus { font-size: 15.5px; font-weight: 800; color: var(--text); letter-spacing: .01em; }
.slot-driver { font-size: 12.5px; font-weight: 500; color: var(--text-dim); }
.slot-fill { font-size: 12.5px; font-weight: 600; color: var(--danger); background: none; border: 1px dashed var(--danger); border-radius: 7px; padding: 2px 8px; cursor: pointer; }
.slot-driver.clash, .slot-bus.clash { text-decoration: line-through; opacity: .75; }
.slot-fix { display: block; margin-top: 2px; font-size: 11px; font-weight: 700; color: var(--danger); background: rgba(239,68,68,0.1); border: none; border-radius: 6px; padding: 2px 7px; cursor: pointer; }
.slot-side { display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; gap: 4px; }
/* nowrap: the slot column is narrow, and a two-word tag broken across two lines
   reads as two tags. It wraps onto its own line instead. */
.slot-tag { font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 999px; background: var(--surface); color: var(--text-dim); white-space: nowrap; }
.slot-tag.ok { background: rgba(16,185,129,0.15); color: var(--success); }
/* A bus that's only out today because the district isn't. Brand-coloured rather
   than semantic: it's not a warning, it's an explanation. */
.slot-tag.extra { background: var(--accent-soft); color: var(--accent); }
.disp-slot.extra-day { border-left: 3px solid var(--accent); }
.slot-menu { background: none; border: none; color: var(--text-dim); font-size: 18px; line-height: 1; cursor: pointer; padding: 0 2px; }

.disp-run.trips .trip-list, .disp-group.trips .trip-list { display: flex; flex-direction: column; background: var(--surface); border-top: 1px solid var(--border); overflow: hidden; }
.disp-group.trips .empty.small { padding: 0 13px 13px; }
.trip-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; background: none; border: none; border-bottom: 1px solid var(--border); padding: 10px 12px; cursor: pointer; color: var(--text); }
.trip-row:last-child { border-bottom: none; }
.trip-row.cancelled { opacity: .55; text-decoration: line-through; }
.trip-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.trip-title { font-size: 13.5px; font-weight: 600; }
.trip-sub { font-size: 12px; color: var(--text-dim); }
/* A trip that takes several buses: one card headed by the trip, with a row per
 * bus underneath — each bus still opens its own editor. */
.trip-card { border-bottom: 1px solid var(--border); }
.trip-card:last-child { border-bottom: none; }
.trip-card.cancelled { opacity: .55; }
.trip-card.cancelled .trip-title { text-decoration: line-through; }
.trip-row.static { cursor: default; border-bottom: none; padding-bottom: 4px; }
.trip-buses { display: flex; flex-direction: column; padding: 0 12px 10px 12px; gap: 4px; }
.trip-bus { display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 8px; background: var(--surface-2); cursor: pointer; }
.trip-bus.gap { background: rgba(239,68,68,0.08); box-shadow: inset 0 0 0 1px rgba(239,68,68,0.35); }
.trip-bus.cancelled { opacity: .55; text-decoration: line-through; }
.trip-bus-main { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.trip-bus .slot-fill { font-size: 12px; }
.disp-undo { margin-left: auto; color: var(--accent); font-weight: 700; }

/* ---- Has the driver said yes? ----
   Deliberately the semantic three, not the brand: a confirmation is a status,
   and status green/amber/red is a separate system from the orange chrome. Sized
   below .slot-tag so a row of buses still reads bus-then-driver first — the
   answer is the qualifier on that pairing, not the headline. */
.tconf {
  font-size: 10.5px; font-weight: 700; padding: 1px 7px; border-radius: 999px;
  white-space: nowrap; flex: none;
}
.tconf.ok { background: rgba(16,185,129,0.15); color: var(--success); }
.tconf.wait { background: rgba(245,158,11,0.18); color: var(--warning); }
.trip-sub .tconf { margin-left: 8px; }

/* A decline is a sentence, not a chip: it names somebody who ISN'T on the trip
   any more and quotes why. So it takes a line of its own rather than being
   squeezed into the row — a pill that wraps breaks its own background across
   two lines, which is how this first went wrong. `flex-basis: 100%` is what
   claims the line inside .trip-bus-main's wrapping flex row; the `display:block`
   does it inside .trip-sub, which isn't one. */
.tconf-note {
  display: block; flex: 1 0 100%;
  margin-top: 3px; font-size: 11.5px; font-weight: 700; color: var(--danger);
}

/* The trips group's head, when every bus is filled but not every driver has
   answered. Amber between the red "N gaps" and the green "✓ Covered" it sits
   in place of — the work is done, the agreement isn't. */
.run-unconf {
  margin-left: auto; font-size: 11.5px; font-weight: 700; padding: 2px 8px;
  border-radius: 999px; background: rgba(245,158,11,0.18); color: var(--warning);
}

/* ---- The day laid out by time ----
   Sorted this way the sessions stop being containers, so there are no group
   cards to nest in: every run and every field trip is a card of its own in one
   line down the screen. A trip keeps the surface the trips group gave it (and
   the purple edge that group had), so it still reads as a trip and not a run. */
.disp-timeline { display: flex; flex-direction: column; gap: 10px; }
.disp-tl-head { display: flex; align-items: center; gap: 8px; padding: 0 2px; }
.disp-tl-title { font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); }
.disp-tl-head .btn-xs { margin-left: auto; }
.disp-timeline .trip-list {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid #a855f7;
  border-radius: 10px;
  overflow: hidden;
}

/* Season plans — a LIST of seasons, and one season's own PAGE. Never both at
   once: the list used to expand a season in place and draw its runs under every
   season on the screen, which stopped saying whose runs they were the moment a
   company kept more than one season live (0103). The page borrows the queue
   panel's parts (`.treq` — `.req-group` heads, the blurb) so a season reads like
   the rest of the office's work screens. */
.disp-subhead { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text-dim); }
.plan-list { display: flex; flex-direction: column; gap: 8px; }

/* The list's divisions: each school's live season under its own head, in that
   school's colour, then what's coming, then the drafts, then the archive.
   ONLY A SCHOOL HEAD IS COLOURED. The queue screens' heads are brand orange,
   and four orange headings down one screen made the two that mean something —
   the school running today, in that school's colour — compete with two that are
   only saying where the leftovers are. So the rest are plain: a heading is a
   label until it has a reason to be a colour. */
.plan-group + .plan-group { margin-top: 24px; }
/* Both class names, because the queue's own `.treq .req-group-head` is declared
   further down the sheet and would otherwise win the tie on source order. */
.planq.treq .req-group-head { color: var(--text-dim); }
.planq .plan-group.cat-boys .req-group-head { color: var(--cat-boys); }
.planq .plan-group.cat-girls .req-group-head { color: var(--cat-girls); }
.planq .plan-group.cat-mixed .req-group-head { color: #a855f7; }
/* Everything that isn't running today folds, and arrives shut — a head, a count
   and nothing else until it's asked for. Shut, the head carries no gap under it;
   open, it sits back off its cards. */
.planq .plan-fold .req-group-head { cursor: pointer; user-select: none; margin-bottom: 0; }
.planq .plan-fold.open .req-group-head { margin-bottom: 10px; }
/* `.treq .req-group-head span` is the COUNT badge, pushed to the right by
   `margin-left: auto` and wearing a pill — the chevron is a span in the same
   head and is neither, so it opts out of all of it (the outstanding report's
   `.rep-chev` makes the identical exception). Without this the arrow arrives as
   a bordered pill that shoves its own label into the middle of the row. */
.planq.treq .req-group-head .plan-fold-chev {
  margin-left: 0; padding: 0; background: none; border: 0;
  width: 11px; text-align: center; font-size: 11px; line-height: 1; color: var(--text-dim);
}
/* The archive is the one that's over rather than not started: quieter than the
   others, and set apart from them by the gap above it. */
.planq .plan-arch .req-group-head { color: var(--muted); }
.planq .plan-arch { margin-top: 30px; }

/* One season on the list. A whole-row button — the row IS the door, so there is
   nothing on it to hit or miss. The left rail is WHOSE season it is, in the same
   blue and pink the board and the run cards use: one colour language for boys
   and girls across the whole app, rather than a second one invented here. WHEN
   it runs is the badge beside the name; the two never compete for the rail. */
.plan-card {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 12px 13px; background: var(--surface);
  border: 1px solid var(--border); border-left: 4px solid var(--border);
  border-radius: 12px; color: var(--text); cursor: pointer;
}
.plan-card.cat-boys { border-left-color: var(--cat-boys); }
.plan-card.cat-girls { border-left-color: var(--cat-girls); }
.plan-card.cat-mixed, .plan-card.cat-other { border-left-color: #a855f7; }
.plan-card.cat-standby { border-left-color: var(--muted); }
/* The chips are the run badges from the board, so they read as the same fact. */
.plan-card-top .run-badge { flex: none; }
.plan-card-l { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.plan-card-top { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; }
.plan-name { font-size: 15px; font-weight: 700; }
/* The two figures that say whether a season is ready, so it can be judged from
   the list instead of by opening it. */
.plan-facts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
.plan-fact {
  font-size: 11px; font-weight: 700; line-height: 1.6; padding: 1px 8px; border-radius: 999px;
  color: var(--text-dim); background: var(--bg); border: 1px solid var(--border);
}
/* The year a season is called by — the first thing read on a row when two
   seasons a year apart have nearly the same name. */
.plan-fact.year { color: var(--text); background: var(--bg-2); font-variant-numeric: tabular-nums; }
.plan-fact.ok { color: var(--success); background: rgba(16,185,129,0.13); border-color: transparent; }
.plan-fact.warn { color: var(--warning); background: rgba(234,179,8,0.15); border-color: transparent; }
.plan-go { flex: none; font-size: 20px; color: var(--muted); }
.plan-badge { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; padding: 2px 8px; border-radius: 999px; }
/* Where a season sits against today. A season runs on its dates alone, so these
   are the row's real status — not decoration. */
.plan-badge.now { background: rgba(16,185,129,0.16); color: var(--success); }
.plan-badge.soon { background: rgba(100,116,139,0.16); color: var(--text-dim); }
.plan-badge.past { background: rgba(100,116,139,0.12); color: var(--text-dim); }
.plan-badge.warn { background: rgba(234,179,8,0.18); color: var(--warning); }
.plan-hint { margin: 8px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--text-dim); }
.plan-hint.warn { color: var(--warning); }
.plan-sub { font-size: 12.5px; color: var(--text-dim); }
.plan-sub.warn { color: var(--warning); }
.plan-row-acts { display: flex; flex-wrap: wrap; gap: 6px; border-top: 1px solid var(--border); padding-top: 9px; margin-top: 2px; }
/* Archive on a season that's running today: shown, and visibly refused, with
   the reason in its title — a button that vanishes teaches nothing. */
.plan-row-acts button:disabled { opacity: 0.4; cursor: not-allowed; }

/* One season's page: the way out, then what the season IS, then its runs. */
.plan-back {
  align-self: flex-start; padding: 5px 11px 5px 8px; margin-bottom: 2px;
  font-size: 12.5px; font-weight: 700; color: var(--text-dim);
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px; cursor: pointer;
}
.plan-hero {
  display: flex; flex-direction: column; gap: 10px; padding: 14px;
  background: var(--surface); border: 1px solid var(--border);
  border-top: 4px solid var(--border); border-radius: 12px;
}
/* Same colour as the card it was opened from, so the tap lands somewhere that
   looks like where it came from. */
.plan-hero.cat-boys { border-top-color: var(--cat-boys); }
.plan-hero.cat-girls { border-top-color: var(--cat-girls); }
.plan-hero.cat-mixed, .plan-hero.cat-other { border-top-color: #a855f7; }
.plan-hero.cat-standby { border-top-color: var(--muted); }
.plan-hero-top { display: flex; align-items: center; flex-wrap: wrap; gap: 9px; }
.plan-hero-name { font-size: 19px; font-weight: 800; letter-spacing: -.01em; }
.plan-hero .plan-hint:first-of-type { margin-top: 0; }
.plan-runs { margin-top: 18px; }

/* Still the disclosure caret on Settings → Calendars, which borrows the same
   name/sub pair for its rows. */
.plan-chev { font-size: 12px; color: var(--text-dim); }

/* Plans editor: weekday tabs → a day's runs, split into Boys/Girls cards */
.planday-tabs { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin: 4px 0 2px; }
.planday-pill { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 8px 2px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; color: var(--text); cursor: pointer; transition: background .12s ease; }
.planday-pill.sel { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); box-shadow: none; }
.planday-pill.empty:not(.sel) { opacity: .45; }
.planday-dow { font-size: 11.5px; font-weight: 800; }
.planday-n { font-size: 11px; font-weight: 700; color: var(--text-dim); }
.planday-pill.sel .planday-n { color: var(--accent); }
.planday-title { font-size: 13px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); margin: 12px 2px 2px; }

.plan-cat { border: 1px solid var(--border); border-left: 4px solid var(--border); border-radius: 12px; overflow: hidden; margin-top: 10px; background: var(--surface); }
.plan-cat.cat-boys { border-left-color: var(--cat-boys); }
.plan-cat.cat-girls { border-left-color: var(--cat-girls); }
.plan-cat.cat-mixed, .plan-cat.cat-other { border-left-color: #a855f7; }
.plan-cat.cat-standby { border-left-color: var(--muted); }
.plan-cat-head { display: flex; align-items: center; gap: 8px; padding: 9px 12px; border-bottom: 1px solid var(--border); background: var(--bg); }
.plan-cat.cat-boys .plan-cat-head { background: color-mix(in srgb, var(--cat-boys) 8%, transparent); }
.plan-cat.cat-girls .plan-cat-head { background: color-mix(in srgb, var(--cat-girls) 8%, transparent); }
.plan-cat.cat-mixed .plan-cat-head, .plan-cat.cat-other .plan-cat-head { background: rgba(168,85,247,0.08); }
.plan-cat-count { margin-left: auto; font-size: 12px; font-weight: 700; color: var(--text-dim); }
.plan-cat .run-list { padding: 10px 12px; }

.run-list { display: flex; flex-direction: column; gap: 6px; }
.run-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 10px; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; }
.run-row-info { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
.run-name { font-size: 13px; color: var(--text-dim); }
.run-dur { font-size: 11.5px; font-weight: 700; color: var(--text-dim); background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px; cursor: pointer; }
.run-edit { font-size: 13px; color: var(--text-dim); background: none; border: none; cursor: pointer; padding: 2px 4px; }
.run-days { font-size: 10.5px; font-weight: 700; letter-spacing: 1px; color: var(--muted); background: var(--bg); border-radius: 5px; padding: 1px 6px; }
/* "20 buses" is a lie four days in five when three of them wait for the
   district — this is the +3 that makes the count readable at a glance. */
.run-rule-tag { font-size: 10.5px; font-weight: 700; color: var(--accent); background: var(--accent-soft); border-radius: 5px; padding: 1px 6px; }
.run-asg { font-size: 10.5px; font-weight: 700; letter-spacing: .2px; border-radius: 999px; padding: 1px 8px; white-space: nowrap; }
.run-asg.full { color: var(--success); background: rgba(52, 211, 153, 0.15); }
.run-asg.partial { color: var(--warning); background: rgba(234, 179, 8, 0.16); }
.run-asg.none { color: var(--text-dim); background: var(--bg); border: 1px solid var(--border); }
.run-row.full { border-left: 3px solid var(--success); }
.run-row.partial { border-left: 3px solid var(--warning); }
.wd-pills { display: flex; gap: 6px; flex-wrap: wrap; margin: 4px 0 2px; }
.wd-pill { width: 38px; height: 38px; border-radius: 999px; border: 1.5px solid var(--border); background: var(--surface); color: var(--text); font-size: 14px; font-weight: 700; cursor: pointer; }
.wd-pill.sel { background: var(--accent); border-color: var(--accent); color: #fff; }
.dur-opts { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
.dur-chip { padding: 9px 14px; font-size: 14px; font-weight: 600; color: var(--text); background: var(--surface); border: 1.5px solid var(--border); border-radius: 999px; cursor: pointer; }
.dur-chip.sel { background: var(--accent); border-color: var(--accent); color: #fff; }
.dur-chip.wide { display: block; width: 100%; text-align: center; margin-top: 8px; }
/* A run whose buses aren't all the same length shows a range, not one number. */
.run-dur.varies { color: var(--accent); border-color: var(--accent); }
.run-row-count { display: flex; align-items: center; gap: 6px; }
.run-row-count .step { width: 28px; height: 28px; border-radius: 7px; border: 1px solid var(--border); background: var(--bg); color: var(--text); font-size: 16px; cursor: pointer; }
.run-row-count .step:disabled { opacity: .4; cursor: default; }
.run-row-count .count { min-width: 20px; text-align: center; font-weight: 700; font-size: 15px; }
.disp-hint { font-size: 12px; color: var(--muted); margin: 2px 0 0; }
/* Two rows of two: the dates the season runs between, then the two labels it's
   filed under (its year, and the calendar it takes turns on). */
.plan-dates { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

/* Assign-buses modal */
.runslots { display: flex; flex-direction: column; gap: 8px; max-height: 55vh; overflow-y: auto; margin: 6px 0; }
.runslot { display: flex; align-items: center; gap: 6px; }
.runslot-n { flex: none; width: 20px; text-align: center; font-weight: 700; color: var(--text-dim); }
.runslot-fields { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.runslot-row2 { display: flex; align-items: center; gap: 6px; }
.runslot select { flex: 1; min-width: 0; padding: 7px 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 13px; }
/* Per-bus run length: plain pill when it follows the run, accented when it doesn't. */
.runslot-dur { flex: none; font-size: 12px; font-weight: 700; color: var(--text-dim); background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 6px 10px; cursor: pointer; }
.runslot-dur.custom { color: var(--accent); border-color: var(--accent); }
.runslot-dur:hover { border-color: var(--accent); }
/* Which days this bus goes out. Same shape as the length pill beside it, for the
   same reason: both are "this bus differs from its run", answered per bus. */
.runslot-rule { flex: none; font-size: 12px; font-weight: 700; color: var(--text-dim); background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 6px 10px; cursor: pointer; white-space: nowrap; }
.runslot-rule.custom { color: var(--accent); border-color: var(--accent); }
.runslot-rule:hover { border-color: var(--accent); }
/* The runslot row wraps rather than squeezing the bus select to nothing once a
   third control joined it — a select narrowed to "— driver's" has stopped
   saying which bus it means. The min-width is what forces the wrap: without it
   flex shrinks the select instead of moving the pill down. */
.runslot-row2 { flex-wrap: wrap; }
.runslot-row2 select { min-width: 128px; }

/* A list of whole answers, each with its consequence spelled out underneath —
   used where a radio group would make the office decode a short label. */
.rule-opts { display: flex; flex-direction: column; gap: 8px; margin: 8px 0; }
.rule-opt { display: block; width: 100%; text-align: left; padding: 11px 13px; background: var(--surface); border: 1.5px solid var(--border); border-radius: 10px; cursor: pointer; }
.rule-opt:hover { border-color: var(--accent); }
.rule-opt.sel { border-color: var(--accent); background: var(--accent-soft); }
.rule-opt-label { display: block; font-size: 14px; font-weight: 700; color: var(--text); }
.rule-opt-hint { display: block; margin-top: 3px; font-size: 12px; font-weight: 500; color: var(--text-dim); line-height: 1.4; }

/* Settings → District calendar: one row per marked day. */
/* Each marked day is its own line — without a rule between them, two dates and
   two sub-lines read as one four-line paragraph. */
.district-row { align-items: center; padding: 9px 0; border-bottom: 1px solid var(--border); }
.district-row:last-child { border-bottom: none; }
.district-main { flex: 1; min-width: 0; }
.district-when { font-size: 14px; font-weight: 700; color: var(--text); }
.district-sub { font-size: 12px; color: var(--text-dim); margin-top: 2px; }

/* Fill sheet & menu */
.fill-list { display: flex; flex-direction: column; gap: 4px; max-height: 55vh; overflow-y: auto; margin: 6px 0; }
.fill-row { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; padding: 11px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; color: var(--text); font-size: 14px; font-weight: 600; cursor: pointer; }
.fill-row:hover { border-color: var(--accent); }
.fill-sub { margin-left: auto; font-size: 12px; font-weight: 500; color: var(--text-dim); }
.fill-row.subtle { font-weight: 500; color: var(--text-dim); }
.fill-until-pick { display: flex; gap: 8px; align-items: center; padding: 2px 0; }
.fill-until-pick input { flex: 1; padding: 10px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; color: var(--text); font-size: 14px; }
.menu-list { display: flex; flex-direction: column; gap: 4px; margin: 8px 0; }
.menu-item { text-align: left; padding: 12px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; color: var(--text); font-size: 14px; cursor: pointer; }
.menu-item.danger { color: var(--danger); }
.modal-title { font-size: 16px; font-weight: 700; margin: 0 0 8px; }
.fld { display: flex; flex-direction: column; gap: 3px; margin: 6px 0; font-size: 12.5px; color: var(--text-dim); }
.fld > span { font-weight: 600; }
.fld input, .fld select, .fld textarea { padding: 9px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 14px; }
.fld-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.empty.small { padding: 12px; font-size: 13px; }

/* Calendar */
.cal-head { display: flex; align-items: center; justify-content: center; gap: 14px; }
.cal-month { font-size: 17px; font-weight: 800; min-width: 170px; text-align: center; }
.cal-summary { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.cal-summary span { font-size: 13px; font-weight: 700; padding: 5px 12px; border-radius: 999px; }
.cal-sum-ok { background: rgba(16,185,129,0.15); color: var(--success); }
.cal-sum-bad { background: rgba(239,68,68,0.15); color: var(--danger); }
.cal-sum-trip { background: rgba(168,85,247,0.14); color: #a855f7; }
.cal-sum-none { background: var(--surface); color: var(--text-dim); }

.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.cal-dow { text-align: center; font-size: 11px; font-weight: 700; color: var(--muted); padding: 4px 0 2px; text-transform: uppercase; letter-spacing: .04em; }
.cal-cell { position: relative; min-height: 66px; display: flex; flex-direction: column; align-items: stretch; gap: 3px; padding: 5px 5px 6px; border: 1px solid var(--border); border-left-width: 4px; border-radius: 9px; background: var(--surface); color: var(--text); cursor: pointer; text-align: left; overflow: hidden; }
.cal-cell.blank { background: none; border: none; cursor: default; min-height: 0; }
.cal-cell.st-ok { border-left-color: var(--success); }
.cal-cell.st-gap { border-left-color: var(--danger); background: rgba(239,68,68,0.06); }
.cal-cell.st-none { border-left-color: var(--border); opacity: .7; }
.cal-cell.sel { outline: 2px solid var(--accent); outline-offset: -1px; }
.cal-cell.today .cal-daynum { background: var(--accent); color: #fff; border-radius: 999px; width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; }
.cal-daynum { font-size: 14px; font-weight: 800; line-height: 22px; flex: 0 0 auto; }
.cal-body { display: flex; flex-direction: column; gap: 2px; }
.cal-pill { font-size: 11px; font-weight: 800; padding: 2px 7px; border-radius: 6px; align-self: flex-start; }
.cal-pill.bad { background: var(--danger); color: #fff; }
.cal-pill.ok { background: rgba(16,185,129,0.18); color: var(--success); }
.cal-sub { font-size: 10.5px; color: var(--text-dim); }
/* "✈ 1·4" = one field trip, four buses. It sits in the cell body rather than the
 * old top-right corner: beside the date there's ~14px, which fits neither
 * number once the day is today and the date wears its circle. */
.cal-trip { font-size: 10px; font-weight: 700; color: #a855f7; white-space: nowrap; }

.cal-legend { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; padding: 4px 0; }
.cal-leg { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-dim); }
.cal-leg .dot { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }
.cal-leg .dot.st-ok { background: var(--success); }
.cal-leg .dot.st-gap { background: var(--danger); }
.cal-leg .dot.st-none { background: var(--border); }

/* Field-trip request queue */
.disp-tab-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 17px; height: 17px; margin-left: 5px; padding: 0 4px; font-size: 10.5px; font-weight: 800; color: #fff; background: var(--danger); border-radius: 999px; }
/* A count that is information rather than a demand — same shape, no alarm. */
.disp-tab-badge.soft { background: var(--surface); border: 1px solid var(--border); color: var(--text-dim); }
.req-list { display: flex; flex-direction: column; gap: 8px; }
.req-card { display: flex; flex-direction: column; gap: 5px; width: 100%; text-align: left; padding: 11px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 11px; color: var(--text); cursor: pointer; }
.req-card:hover { border-color: var(--accent); }
.req-card-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.req-ref { font-size: 11.5px; font-weight: 700; color: var(--muted); }
.req-when { margin-left: auto; font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.req-purpose { font-size: 15px; font-weight: 700; }
.req-meta { font-size: 12.5px; color: var(--text-dim); }
/* Trip-request queue spacing. Scoped to .treq because .req-card / .req-meta are
   also used by the (much denser) maintenance-job lists. */
.treq { gap: 14px; padding: 18px; }
.treq .req-list { gap: 12px; }
/* The bottom border is restated because the maintenance list — where cards are
   rows divided by a rule — drops it on the last one (.req-card:last-child), and
   a trip card is always the last child of its own wrapper. Every trip card was
   therefore drawn open-bottomed, which only became obvious once hover started
   coloring the other three sides. A trip card is a card, not a row: four sides. */
.treq .req-card { padding: 15px 18px; gap: 9px; border-radius: 14px; border-bottom: 1px solid var(--border); }
.treq .req-card-top { gap: 10px; }
.treq .req-purpose { font-size: 16px; line-height: 1.35; }
.treq .req-when { font-size: 13px; }
/* Where they're going: one line, clipped. An address that wraps three times
   pushes the trip's own name up into the pills and leaves a ragged card. */
.treq .req-where {
  font-size: 13px; line-height: 1.4; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
/* The card's footer. As plain text the facts ran together with nothing between
   them, and wrapping left the bottom of the card ragged; each fact carries its
   own outline instead, so the row reads the same however it wraps. */
.treq .req-meta {
  display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center;
  gap: 6px; margin-top: 3px; font-size: 12.5px; color: var(--text-dim);
}
/* max-width keeps a long one (a group label listing five grades) from running
   out past the card's edge instead of wrapping with the rest. */
.req-fact {
  padding: 3px 9px; border-radius: 999px;
  background: var(--bg-2); border: 1px solid var(--border);
  font-size: 12px; font-weight: 600; line-height: 1.5; color: var(--text-dim);
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* What the tab you're on is for. One line, under the heading, never shouting. */
.treq-blurb { margin: -6px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--text-dim); }

/* Sections within a tab. Same rule-under-a-label header the detail sheets use,
   so a divided queue reads like the rest of the app rather than a run of pills. */
.treq .req-group + .req-group { margin-top: 22px; }
.treq .req-group-head {
  display: flex; align-items: center; gap: 8px; margin: 0 0 10px;
  padding-bottom: 6px; border-bottom: 1px solid var(--border);
  font-size: 11px; font-weight: 800; letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--accent);
}
.treq .req-group-head span {
  margin-left: auto; font-size: 11px; font-weight: 800; color: var(--text-dim);
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px;
}
/* A section that only says WHEN something happens, rather than asking for a
   decision. Same header, muted — so on the combined tab the eye can tell the
   queue from the calendar without a second row of tabs to do it. */
.treq .req-group.calm .req-group-head { color: var(--muted); }
/* The first quiet section is where the work ends and the diary begins. */
.treq .req-group:not(.calm) + .req-group.calm { margin-top: 30px; }
.treq .req-card.req-stale { opacity: 0.72; }
.treq .req-more { align-self: center; margin-top: 6px; }
.treq .empty { padding: 34px 12px; }

/* A colored rail down the left of a trip card, keyed to where it is in the
   pipeline — the same at-a-glance cue the maintenance lists use for priority.
   The color is set on the wrapper so the bill strip below the card picks up the
   same rail and the two stay one object.

   The rail is painted, not bordered. As a 3px left border against the card's 1px
   sides it tapered into a wedge at both rounded corners and the thick stroke ran
   on past the bottom edge — which is exactly where the card stopped looking like
   a card. Drawn as a bar inside a clipped wrapper it follows the corner radius
   and stops dead where the card does. */
.rail-requested { --rail: var(--warning); }
.rail-approved { --rail: #3b82f6; }
.rail-assigned { --rail: #a855f7; }
.rail-confirmed { --rail: var(--success); }
.rail-denied, .rail-cancelled { --rail: var(--danger); }

/* Hover. The generic .req-card:hover swaps the whole border to brand orange,
   which on a trip card lands right beside a green/blue/purple status rail and
   reads as two outlines arguing about what the card is. Brand orange is chrome;
   the rail is status, and status wins on this card — so the hovered card takes
   its edge and a wash of tint from its OWN rail, and lifts. The lift is on the
   wrapper so a card with a bill strip rises as one object rather than shearing
   in half. */
.treq .req-card { transition: border-color 0.12s ease, background 0.12s ease; }
.treq .req-card-wrap { transition: box-shadow 0.12s ease; }
.treq .req-card-wrap:hover { box-shadow: var(--shadow-control); }
.treq .req-card-wrap:hover .req-card {
  border-color: color-mix(in srgb, var(--rail, var(--accent)) 55%, var(--border));
  background: color-mix(in srgb, var(--rail, var(--accent)) 6%, var(--surface));
}

.ts-pill { font-size: 11px; font-weight: 800; padding: 2px 9px; border-radius: 999px; }
.ts-requested { background: rgba(245,158,11,0.16); color: var(--warning); }
.ts-approved { background: rgba(59,130,246,0.16); color: #3b82f6; }
.ts-assigned { background: rgba(168,85,247,0.16); color: #a855f7; }
.ts-confirmed { background: rgba(16,185,129,0.16); color: var(--success); }
.ts-denied { background: rgba(239,68,68,0.14); color: var(--danger); }
.ts-done { background: var(--surface); color: var(--text-dim); }
.ts-stale { background: rgba(148,163,184,0.18); color: var(--muted); }

.req-detail-head { display: flex; align-items: center; gap: 8px; }
.req-info { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }
.req-info > div { display: flex; gap: 10px; font-size: 14px; }
.req-info > div > span { flex: none; width: 120px; color: var(--text-dim); font-size: 12.5px; padding-top: 1px; }
.req-info > div > b { min-width: 0; word-break: break-word; }
/* Trip-request detail modal: clean, grouped, bordered info cards */
.req-section-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); margin: 14px 2px 6px; }
#req-detail .req-section-label:first-of-type { margin-top: 6px; }
#req-detail .req-info { gap: 0; margin: 0; align-items: stretch; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: var(--surface); }
#req-detail .req-info:empty { display: none; }
#req-detail .req-info > div { gap: 12px; align-items: baseline; padding: 9px 13px; }
#req-detail .req-info > div + div { border-top: 1px solid var(--border); }
#req-detail .req-info > div > span { width: 118px; font-size: 12px; text-transform: uppercase; letter-spacing: .02em; padding-top: 0; }
#req-detail .req-info > div > b { flex: 1; font-weight: 600; text-align: left; }
.assign-list { display: flex; flex-direction: column; gap: 6px; }
.assign-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 10px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; font-size: 13.5px; }
/* Multi-slot "Assign buses" sub-modal */
.asg-row { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.asg-row-num { flex: none; width: 46px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .02em; color: var(--text-dim); }
.asg-row select { flex: 1; min-width: 0; padding: 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 13px; }
.asg-row select option:disabled { color: var(--text-dim); }
.asg-remove { flex: none; width: 30px; height: 30px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--text-dim); cursor: pointer; font-size: 13px; line-height: 1; }
.asg-remove:hover { color: var(--danger); border-color: var(--danger); }

/* Shared searchable driver picker + its trigger field (board, trips, plans, assign) */
.driver-trigger { flex: 1; min-width: 0; text-align: left; padding: 9px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 14px; font-weight: 600; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Inside a modal `.modal label` (higher specificity) beats `.fld`'s flex column,
   so a trigger would sit INLINE after its label and only drop to its own line if
   its text happened to be too long. Pin it to a full-width block so the Driver
   and Bus fields line up regardless of what's selected. */
.fld > .driver-trigger { display: block; width: 100%; }
.driver-trigger.empty { color: var(--text-dim); font-weight: 500; }
.driver-trigger:hover { border-color: var(--accent); }
.dp-search { width: 100%; padding: 10px 12px; margin: 4px 0 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 9px; color: var(--text); font-size: 15px; }
.dp-list { display: flex; flex-direction: column; gap: 4px; max-height: 56vh; overflow-y: auto; }
.dp-row { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; padding: 11px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; color: var(--text); font-size: 14px; font-weight: 600; cursor: pointer; }
.dp-row:hover { border-color: var(--accent); }
.dp-row.sel { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.dp-row.off { opacity: .5; cursor: not-allowed; background: var(--bg); }
.dp-row.off:hover { border-color: var(--border); }
.dp-unassign { color: var(--text-dim); font-weight: 500; }
.dp-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A row that carries a second line (a bus's make/model and shop status) stacks
   its label instead of running it along the row. `.dp-name` keeps its own
   flex:1 for the single-line rows, so it's overridden here rather than dropped. */
.dp-lines { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dp-lines .dp-name { flex: none; }
.dp-sub { font-size: 12px; font-weight: 500; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dp-bus { flex: none; font-size: 12px; font-weight: 600; color: var(--text-dim); }
.dp-reason { flex: none; font-size: 11.5px; font-weight: 700; color: var(--danger); background: rgba(239,68,68,0.12); padding: 2px 8px; border-radius: 999px; }
.dp-reason.muted { color: var(--text-dim); background: var(--bg); font-weight: 600; }
/* Busy / on leave but still tappable: the dispatcher may override and take them
   off what they're on. Amber = "there's a cost", not red = "you can't". */
.dp-reason.warn { color: var(--warning); background: rgba(234,179,8,0.14); }
.dp-row.warn { border-color: rgba(234,179,8,0.45); }
.dp-row.warn:hover { border-color: var(--warning); }
/* What the override would break, listed in the confirm sheet. */
.dp-clash { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
.dp-clash-line { padding: 9px 11px; background: var(--bg); border: 1px solid var(--border); border-left: 3px solid var(--warning); border-radius: 8px; font-size: 14px; font-weight: 600; }

/* ============================================================
 * Driver — "My Schedule"
 *
 * A day is a card, a run/trip is a line inside it with the time in a fixed
 * left column so the whole strip scans as one time column. The month grid
 * reuses the dispatch calendar's .cal-* shell; only the driver-specific
 * "time off" state is new.
 * ============================================================ */
.sched-wrap { display: flex; flex-direction: column; gap: 12px; }

.sday { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px 4px; }
/* Today gets the brand rule — the one card a driver looks for on open. */
.sday.today { border-left: 4px solid var(--accent); }
.sday-head { display: flex; align-items: baseline; gap: 8px; padding-bottom: 6px; }
.sday-date { font-size: 14.5px; font-weight: 800; }
.sday-rel { font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--accent); }
.sday-count { margin-left: auto; font-size: 11.5px; font-weight: 700; color: var(--text-dim); }
.sday-empty { padding: 2px 0 10px; font-size: 13px; color: var(--text-dim); }

.sday-banner { margin: 0 0 8px; padding: 7px 10px; border-radius: 8px; font-size: 12.5px; font-weight: 700; }
.sday-banner.off { background: rgba(16,185,129,0.14); color: var(--success); }
.sday-banner.pending { background: rgba(234,179,8,0.14); color: var(--warning); }
.sday-banner.closed { background: var(--bg-2); color: var(--text-dim); }
/* The district isn't running, so buses that normally stay in are out. Not a
   warning and not a closure — the day is ON, which is the whole point. */
.sday-banner.district { background: var(--accent-soft); color: var(--accent); }
/* A customer shut ITSELF and its runs left the board (0116). The same slate the
   year calendar washes a closed day in — it is the same statement, and the two
   screens are read one after the other. */
.sday-banner.shut {
  background: color-mix(in srgb, var(--hol-fast) 14%, transparent);
  color: var(--hol-fast);
}
/* A regular run that MOVED, and so is off the board without anybody cancelling
   it. Deliberately not the closure's slate: nothing is shut, the same children
   are riding the same route at another hour. It takes the cyan the year calendar
   washes a schedule-change day in — the two screens are read one after the other
   and this is the same fact on both of them. */
.sday-banner.moved {
  background: color-mix(in srgb, var(--ycal-chg) 14%, transparent);
  color: var(--ycal-chg);
}

.sitem { display: flex; gap: 10px; padding: 8px 0; border-top: 1px solid var(--border); }
.sday-head + .sitem, .sday-banner + .sitem { border-top: none; padding-top: 0; }
.sitem-time { flex: 0 0 68px; font-size: 13px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
/* Grows so the chevron on a tappable line lands at the card's right edge rather
   than trailing the text. Nothing else moves: the body is left-aligned either way. */
.sitem-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.sitem-title { font-size: 14px; font-weight: 600; }
.sitem-sub { font-size: 12.5px; color: var(--text-dim); }
.sitem-warn { font-size: 12px; font-weight: 700; color: var(--warning); }

/* ---- A line that opens onto more ----
   A field trip carries a pickup point, a group, a contact and a time they're
   heading back; the line shows three of those and the tap has the rest. The
   chevron is the only thing marking it, deliberately quiet — a day card is a
   list of work, and a row of buttons down the right would read as a row of
   things to do. Runs have no sheet behind them, so they stay plain. */
.sitem.tappable { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.sitem.tappable:active { background: var(--bg-2); }
/* Top-aligned, not centred: on a trip still waiting for an answer the line is
   three times as tall as its title, and a centred chevron floats beside the
   buttons looking like it belongs to them. */
.sitem-more { flex: none; align-self: flex-start; margin-top: 2px; color: var(--text-dim); font-size: 18px; line-height: 1; }

/* ---- A trip the driver hasn't answered ----
   Every other line on this screen is a fact. This one is a question, so it gets
   the only piece of colour and the only buttons on the card: an amber rail down
   its left edge and a tinted panel, so a day with an unanswered trip in it can't
   be scrolled past as "same as yesterday". It sits INSIDE .sitem-body, indented
   under the trip it belongs to — an ask floating at the card's edge would read
   as being about the whole day. */
.sitem-ask {
  display: flex; flex-direction: column; gap: 8px;
  margin: 6px 0 2px; padding: 8px 10px;
  border-left: 3px solid var(--warning); border-radius: 0 8px 8px 0;
  background: rgba(245,158,11,0.10);
}
.sitem-ask .ask-note { font-size: 12px; font-weight: 700; color: var(--warning); }
/* "I'll take it" is green rather than brand orange: it's the same green as the
   ✓ receipt it turns into, so the colour tells one story either side of the tap
   — and brand orange inside an amber panel was two oranges arguing.
   Both buttons on one row and both full-height: "Can't do it" is a real answer,
   not an escape hatch, and burying it as a text link would push a driver who
   can't do the trip into saying yes and sorting it out later. */
.sitem-ask .ask-btns { display: flex; gap: 8px; flex-wrap: wrap; }
.sitem-ask .ask-btns button { flex: 1 1 auto; min-height: 38px; }
/* The receipt. Quiet on purpose — it's there so a driver can check they did
   answer, which is a glance, not a thing to be told twice. */
.sitem-ok { font-size: 12px; font-weight: 700; color: var(--success); }

/* Reasons offered as taps, in the decline sheet. Big enough to hit with a thumb
   in a yard — the whole point is that a typed reason is a reason not given. */
.reason-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 10px; }
.reason-chip {
  font-size: 13px; font-weight: 600; padding: 8px 12px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  cursor: pointer; min-height: 38px;
}
.reason-chip.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

/* Time off — shared by the driver's list and the office's queue. */
.to-list { display: flex; flex-direction: column; gap: 8px; }
.tor { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; }
.tor-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.tor-when { font-size: 14px; font-weight: 700; }
.tor-meta { font-size: 12.5px; color: var(--text-dim); }
.tor-note { font-size: 12.5px; color: var(--text-dim); font-style: italic; }
.tor-side { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex: 0 0 auto; }
.sched-ask { width: 100%; margin-top: 10px; }

/* The office's queue: anything still awaiting a decision is pulled forward. */
.abs-card { margin-bottom: 8px; }
.abs-card.pending { border-left: 4px solid var(--warning); }
.abs-actions { display: flex; gap: 6px; }

/* Driver calendar: a day off is its own state alongside the board's ok/gap.
 * Green already means "you're driving", so time off is deliberately NOT green —
 * it's the absence of work, and it reads as a quiet neutral block against the
 * green run days rather than as a second kind of "good". */
.cal-cell.st-off { border-left-color: var(--text-dim); background: var(--bg-2); }
.cal-pill.off { background: var(--surface-2); color: var(--text-dim); }
.cal-leg .dot.st-off { background: var(--text-dim); }
.sched-daydetail { margin-top: 4px; }

/* ===================== In-app chat =====================
 * Two screens sharing one route: the inbox, and one open conversation.
 *
 * The conversation is the only screen in the app that does NOT scroll the page —
 * the composer has to stay reachable above the tab bar while the history scrolls
 * behind it, which is what everyone expects a chat to do. So .chat-scroll owns
 * its own scrollbar at an explicit height and the composer is fixed.
 * --chat-chrome is everything that ISN'T the message list: the app header (75px,
 * see .topbar-app), main's padding, the thread header, the composer and the tab
 * bar. dvh rather than vh, so a mobile browser retracting its own toolbar doesn't
 * leave the composer stranded off-screen.
 */
:root {
  --chat-nav-h: 72px;
  --chat-chrome: 282px;
}

/* ---------- Inbox ---------- */
.chat-head { margin-bottom: 12px; }
.chat-head .btn-primary { width: 100%; padding: 12px; font-size: 15px; }

.chat-list { display: flex; flex-direction: column; }
.chat-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 4px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.chat-row:last-child { border-bottom: none; }
.chat-row:active { background: var(--surface-2); }
.chat-row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.chat-row-top { display: flex; align-items: baseline; gap: 8px; }
.chat-row-title { font-weight: 600; font-size: 15px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-row-when { font-size: 11.5px; color: var(--text-dim); flex-shrink: 0; }
.chat-row-preview { font-size: 12.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-row-sub { font-size: 11.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* An unread row leans on weight and color, not a background wash: on a busy
   morning the list is mostly unread, and a wall of highlight says nothing. */
.chat-row.unread .chat-row-title { font-weight: 800; }
.chat-row.unread .chat-row-preview { color: var(--text); font-weight: 600; }
.chat-count {
  flex-shrink: 0;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--brand-600);
  color: #fff;
  font-size: 11.5px;
  font-weight: 800;
  line-height: 20px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.chat-mute { font-size: 11px; opacity: 0.7; }

.chat-av {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: 17px;
  font-weight: 700;
}
.chat-av-office { background: var(--brand-grad); color: #fff; }
.chat-av-group { background: var(--bg-3); }

/* ---------- One conversation ---------- */
.chat-thread-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.chat-back {
  background: none;
  border: none;
  color: var(--brand-600);
  font-size: 30px;
  line-height: 1;
  padding: 0 6px 4px 0;
  cursor: pointer;
  font-family: inherit;
}
.chat-thread-title { flex: 1; min-width: 0; }
.chat-thread-name { font-weight: 700; font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-thread-sub { font-size: 12px; color: var(--text-dim); }

.chat-scroll {
  height: calc(100vh - var(--chat-chrome));
  height: calc(100dvh - var(--chat-chrome));
  min-height: 180px;
  overflow-y: auto;
  /* Don't hand the scroll up to the page when the history hits its end — the
     whole screen would bounce around under a fixed composer. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 8px 0 4px;
}
.chat-empty { padding: 28px 20px; text-align: center; color: var(--text-dim); font-size: 13.5px; }

.chat-day { display: flex; align-items: center; justify-content: center; margin: 14px 0 10px; }
.chat-day span {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: var(--text-dim);
  background: var(--surface-2);
  border-radius: 999px;
  padding: 3px 12px;
}
.chat-system {
  text-align: center;
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  margin: 8px 12px;
}

.chat-msg { display: flex; flex-direction: column; align-items: flex-start; margin-top: 2px; }
.chat-msg.run-start { margin-top: 10px; }
.chat-msg.mine { align-items: flex-end; }
.chat-msg-who { font-size: 11.5px; font-weight: 700; color: var(--brand-600); margin: 0 0 3px 10px; }
.chat-bubble {
  max-width: 78%;
  padding: 8px 12px 6px;
  border-radius: 16px;
  background: var(--surface-2);
  color: var(--text);
  font-size: 14.5px;
  line-height: 1.35;
  /* Squared-off inner corner — the classic "this side is mine" cue, and the only
     thing telling the two apart once the bubbles are stacked. */
  border-bottom-left-radius: 5px;
  word-break: break-word;
}
.chat-msg.mine .chat-bubble {
  background: var(--brand-600);
  color: #fff;
  border-bottom-left-radius: 16px;
  border-bottom-right-radius: 5px;
}
/* Mid-run bubbles lose the tail, so a burst of three lines reads as one block. */
.chat-msg:not(.run-start) .chat-bubble { border-bottom-left-radius: 16px; }
.chat-msg.mine:not(.run-start) .chat-bubble { border-bottom-right-radius: 16px; }
.chat-text { white-space: pre-wrap; }
.chat-time {
  display: block;
  text-align: right;
  font-size: 10.5px;
  color: var(--text-dim);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
.chat-msg.mine .chat-time { color: rgba(255, 255, 255, 0.75); }

/* ---------- Composer ---------- */
.chat-composer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--chat-nav-h) + env(safe-area-inset-bottom, 0px));
  z-index: 47;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 8px 12px;
  background: var(--chrome-bg);
  border-top: 1px solid var(--chrome-border);
  /* Same compositing hint as .bottom-nav: iOS drops a fixed element out of the
     layer while a sibling is mid-transform (the swipe-between-tabs gesture). */
  transform: translateZ(0);
}
.chat-composer textarea {
  flex: 1;
  min-width: 0;
  resize: none;
  /* border-box so the auto-grow can set height straight from scrollHeight (+ the
     border) and land exactly on the content — otherwise it sits 2px short and
     the textarea shows a scrollbar on a single empty line. */
  box-sizing: border-box;
  max-height: 130px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 20px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  /* 16px exactly — anything smaller makes iOS Safari zoom the page on focus. */
  font-size: 16px;
  line-height: 1.3;
}
.chat-composer textarea:focus { outline: none; border-color: var(--brand-600); }
.chat-send {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border: none;
  border-radius: 50%;
  background: var(--brand-grad);
  color: #fff;
  font-size: 16px;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.chat-send:active { transform: scale(0.94); }

/* ---------- Pickers (new message / new group / add people) ---------- */
.chat-search {
  width: 100%;
  box-sizing: border-box;
  margin: 10px 0 6px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
}
.chat-picklist { max-height: 46vh; overflow-y: auto; }
.chat-pick {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  box-sizing: border-box;
  padding: 10px 4px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.chat-pick:last-child { border-bottom: none; }
.chat-pick:active { background: var(--surface-2); }
.chat-pick-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.chat-pick-name { font-weight: 600; font-size: 15px; }
.chat-pick-sub { font-size: 12px; color: var(--text-dim); }
.chat-pick-check input { width: 20px; height: 20px; flex-shrink: 0; accent-color: var(--brand-600); }

.chat-members { margin: 10px 0; max-height: 40vh; overflow-y: auto; }
.chat-member {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.chat-member:last-child { border-bottom: none; }

/* Unread count on the More row, for the roles that reach chat through it. */
.more-row-count {
  flex-shrink: 0;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--brand-600);
  color: #fff;
  font-size: 11.5px;
  font-weight: 800;
  line-height: 20px;
  text-align: center;
}

/* ============================================================
   PAYROLL
   Two audiences share these styles: the driver's Pay tab (hero total, period
   cards) and the office's Payroll tab under Dispatch (period table, rate grid).
   The shared unit is .payline — one priced run or trip — so a line reads the
   same on a phone as it does in the office table.
   ============================================================ */

/* ---- Driver: "confirm your trip time" prompt ----
   Sits above the total because the total is incomplete until it's answered. */
.pay-confirm {
  background: rgba(245,158,11,0.10);
  border: 1px solid rgba(245,158,11,0.45);
  border-radius: 12px;
  padding: 12px;
  margin-bottom: 14px;
}
.pay-confirm-h { margin: 0 0 3px; font-size: 14px; font-weight: 800; }
.pay-confirm-sub { margin: 0 0 10px; font-size: 12.5px; color: var(--text-dim); line-height: 1.45; }
.pay-confirm-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  margin-top: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 9px;
  text-align: left;
  color: inherit;
  cursor: pointer;
}
.pay-confirm-row:hover { border-color: var(--accent); }
.pay-confirm-cta { flex-shrink: 0; font-size: 12px; font-weight: 700; color: var(--accent); }
/* A trip whose change the office hasn't answered yet — the driver's own ask is
   still in flight, so the row shouldn't read like a settled number. */
.pay-confirm-row.waiting { border-color: rgba(245,158,11,0.5); background: rgba(245,158,11,0.07); }

/* ---- Swaps & shuttles, on the driver's Pay screen ----
   Same block as the trip times above it, but these rows are records rather than
   prompts: the driver added them and they're already paid, so the row doesn't
   look like a button and the only action on it is taking it back. */
.pay-extra-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 4px; }
.pay-extra-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  margin-top: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 9px;
}
.pay-extra-row .payline-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pay-extra-row .payline-amt { font-weight: 800; font-variant-numeric: tabular-nums; }
/* Its period has been approved: the money is out, so nothing here is editable. */
.pay-extra-row.locked { opacity: 0.72; }

/* ---- Picking the work you drove (Schedule → Up next → Report work) ----
   A day's whole board inside a dialog, so it scrolls on its own rather than
   pushing the Send button off a phone. The rows are the .sitem shape the day
   cards above are built from — same three columns, so a driver recognises the
   run they're looking for — but they're buttons, and one of them is chosen. */
.rw-list {
  max-height: 46vh;
  overflow-y: auto;
  margin: 8px 0 4px;
  -webkit-overflow-scrolling: touch;
}
.rw-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  margin-top: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 9px;
  text-align: left;
  color: inherit;
  cursor: pointer;
}
.rw-item:hover:not(:disabled) { border-color: var(--accent); }
/* The chosen one. An outline and a wash rather than a tick in a corner: the
   list is scrolled past on a phone and the answer has to be visible from the
   edge of the screen. */
.rw-item.on { border-color: var(--accent); background: rgba(249,115,22,0.10); }
/* Already theirs, or already sent. Shown, not hidden — a driver hunting for the
   run they covered has to FIND it and read why it isn't offered. */
.rw-item.dead { opacity: 0.55; cursor: default; }
.rw-item-time {
  flex-shrink: 0;
  width: 58px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.rw-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.rw-item-body b { font-size: 13px; font-weight: 600; }
.rw-item-body small { font-size: 11.5px; color: var(--text-dim); }
.rw-item-why { flex-shrink: 0; font-size: 11px; font-weight: 700; color: var(--muted); }
/* The escape hatch, at the foot of every day including an empty one. Dashed, so
   it reads as "none of the above" rather than as one more run. */
.rw-free-row { border-style: dashed; }

/* "Only this bus" on the office's confirm dialog. */
.tt-scope {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  /* It's a choice, not a field label — the form's shouty label case would read
     as a heading over the buttons underneath it. */
  text-transform: none;
  letter-spacing: normal;
}
.tt-scope input { width: auto; margin: 0; }
.tt-scope .muted { font-weight: 400; }

/* ---- Driver: the headline number ---- */
.pay-hero {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 16px;
  border-radius: 14px;
  background: linear-gradient(150deg, var(--surface-2), var(--surface));
  border: 1px solid var(--border);
}
.pay-hero-label { font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); font-weight: 700; }
.pay-hero-amt { font-size: 34px; font-weight: 800; line-height: 1.15; letter-spacing: -0.5px; }
.pay-hero-sub { font-size: 13px; color: var(--text-dim); }
.pay-hero-when { margin-top: 8px; font-size: 13px; font-weight: 700; color: var(--accent); }
.pay-note { margin: 8px 2px 16px; font-size: 12px; color: var(--muted); line-height: 1.5; }

/* ---- Driver: the period still running ----
   Sits directly under the hero and is deliberately quieter: it's an estimate two
   weeks from a paycheck, and the hero is the money that's actually coming. Flat,
   not gradient; one text size down; no accent colour anywhere on it. */
.pay-sofar {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin-top: 8px;
  padding: 10px 14px;
  border-radius: 11px;
  background: var(--surface);
  border: 1px solid var(--border);
}
.pay-sofar-label { flex: 1 1 100%; font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; color: var(--text-dim); font-weight: 700; }
.pay-sofar-amt { font-size: 19px; font-weight: 800; font-variant-numeric: tabular-nums; }
.pay-sofar-sub { font-size: 12px; color: var(--muted); }

/* ---- Period cards (driver) ---- */
.paycard {
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--surface);
  margin-bottom: 8px;
  overflow: hidden;
}
.paycard-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 11px 12px;
  background: none;
  border: 0;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.paycard-head.static { cursor: default; }
.paycard-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.paycard-when { font-size: 13.5px; font-weight: 700; }
.paycard-sub { font-size: 12px; color: var(--text-dim); }
.paycard-side { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex-shrink: 0; }
.paycard-amt { font-size: 16px; font-weight: 800; }
.paycard-chev { flex-shrink: 0; color: var(--muted); font-size: 15px; }
.paycard-head.static .paycard-chev { display: none; }

/* ---- One priced run or trip — shared by both screens ---- */
.pay-lines { border-top: 1px solid var(--border); padding: 4px 10px 10px; }
.payline {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 2px;
  border-bottom: 1px dashed var(--border);
}
.payline:last-child { border-bottom: none; }
.payline-when { flex-shrink: 0; display: flex; flex-direction: column; width: 58px; }
.payline-date { font-size: 12px; font-weight: 700; }
.payline-time { font-size: 11px; color: var(--muted); }
.payline-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.payline-title { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.payline-basis { font-size: 11.5px; color: var(--text-dim); }
.payline-amt { flex-shrink: 0; font-size: 13.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.payline.edited { background: rgba(249,115,22,0.06); }
/* A sick / not-driven day is a $0 row whose only job is to explain the salary
   balance above it. It reads as an annotation, not as a line that paid. */
.payline.payline-note { opacity: 0.72; }
.payline.payline-note .payline-amt { color: var(--muted); font-weight: 600; }
.pay-warn-txt { color: var(--warning); font-weight: 700; }

/* ---- A driver's fortnight, folded into days (office payroll only) ----
   The head is the same three-column shape as a .payline — date, body, money —
   so a folded day and the lines it opens to line up down the same edges. */
.payday { border-bottom: 1px dashed var(--border); }
.payday:last-child { border-bottom: none; }
.payday-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 2px;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.payday-when { flex-shrink: 0; display: flex; flex-direction: column; width: 58px; }
.payday-date { font-size: 12.5px; font-weight: 800; }
.payday-dow { font-size: 11px; color: var(--muted); }
.payday-sum {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.payday-amt { flex-shrink: 0; font-size: 13.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.payday-chev { flex-shrink: 0; width: 12px; text-align: right; color: var(--muted); }
.payday.open .payday-head { background: var(--surface-2); }
/* The open day's lines are inset under their date so a long list still reads as
   belonging to the day above it rather than to the driver. */
.payday.open .payday-lines {
  margin: 0 0 6px 8px;
  padding-left: 10px;
  border-left: 2px solid var(--border);
}

/* ---- Salaried driver: the makeup of a period, above their lines ---- */
.pay-salstrip {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  padding: 9px 2px 10px;
  border-bottom: 1px dashed var(--border);
  font-size: 12px;
  color: var(--text-dim);
}
.pay-salstrip b { font-variant-numeric: tabular-nums; color: var(--text); }

/* ---- The split: which names the gross is written out to ---- */
.pay-checkstrip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  padding: 9px 2px 10px;
  border-bottom: 1px dashed var(--border);
  font-size: 12px;
  color: var(--text-dim);
}
.pay-checkstrip-h { font-weight: 700; color: var(--text); }
.pay-check b { font-variant-numeric: tabular-nums; color: var(--text); }
/* A check the period couldn't cover in full is a number the office has to act
   on, not a rounding difference — it says so on the line itself. */
.pay-check.short { color: var(--warning); }
.pay-check.short b { color: var(--warning); }

/* ---- Change requests: the driver's copy and the office's queue ---- */
.pay-reqs { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 4px; }
.pay-req {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 10px;
  border: 1px solid rgba(245,158,11,0.45);
  background: rgba(245,158,11,0.08);
  border-radius: 9px;
}
.pay-req-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.pay-req-body b { font-size: 13px; }
.pay-req-body small { font-size: 11.5px; color: var(--text-dim); }
.pay-payee-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }

/* ---- Office: editing who a driver's checks are made out to ---- */
.payee-list { display: flex; flex-direction: column; gap: 1px; margin: 10px 0; }
.payee-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 2px;
  border-bottom: 1px solid var(--border);
}
.payee-row:last-child { border-bottom: none; }
.payee-row.regular { background: var(--surface-2); border-radius: 8px; padding: 9px 10px; }
.payee-name { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.payee-name b { font-size: 13.5px; }
.payee-name small { font-size: 11.5px; }
/* The form under the list — adding a check, editing one, or renaming the
   driver's own. Every control gets the same notched label as the rest of the
   app's dialogs (see `.modal label`), so this reads as one more form and not as
   a row of bare boxes: full-width fields stacked, and only the $-or-% chooser
   paired with the amount it governs, because those two are one decision. */
.payee-form { margin-top: 16px; padding-top: 4px; border-top: 1px solid var(--border); }
/* Closed is how the dialog opens: one button, so a driver paid a single check
   shows their check and not a form. */
.payee-form.closed { padding-top: 12px; }
.payee-form-h {
  margin: 14px 0 2px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}
/* The pair sits on the same baseline as a single field would: the cells carry
   the label notch, so the row itself only supplies the columns. */
.payee-form .form-row-2,
.payee-amt-row { align-items: end; }
.payee-form .form-row-2 > div,
.payee-amt-row > div { display: flex; flex-direction: column; }
.payee-form .small,
.modal .payee-amt-row + .small { margin: 8px 2px 0; font-size: 12px; }
.payee-form-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 14px;
}
/* The row the form below is currently about — without it, "Edit" on the third
   row looks like it did nothing, because the form is off the bottom of a
   scrolled dialog. */
.payee-row.editing {
  background: var(--surface-2);
  border-radius: 8px;
  padding-left: 10px;
  padding-right: 10px;
  box-shadow: inset 2px 0 0 var(--accent);
}

/* ---- Driver: rate reference ---- */
.pay-rates { display: flex; flex-direction: column; gap: 1px; }
.pay-rate {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 9px 2px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.pay-rate:last-child { border-bottom: none; }
.pay-rate span { color: var(--text-dim); }
.pay-rate b { font-variant-numeric: tabular-nums; }

/* ---- Office: the period screen ----
   Payroll is the field-trip queue's shell reused whole: .treq's spacing and
   .req-group sections inside .payq. The office's payroll flow — the money, the
   answers still owed, what goes out, the setup — is four screens of the same
   furniture rather than one screen with a toolbar, and they hang off People's
   subsection bar (its second capsule) rather than a bar of their own. */
.pay-wrap { display: flex; flex-direction: column; gap: 12px; }
/* The rate sheet is the one payroll body that still stacks admin sections. */
.pay-wrap .admin-section { margin: 0; }
/* On the admin page the back link sits in a block; here its parent is a flex
   column, which would stretch it edge to edge and centre the label. */
.pay-wrap .admin-back { align-self: flex-start; text-align: left; }
.pay-topbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.pay-topbar select { width: auto; min-width: 210px; margin: 0; padding: 9px 11px; }
.pay-paydate { font-size: 12.5px; color: var(--text-dim); }
.pay-spacer { flex: 1; }
/* .treq's own gap spaces the body; a margin on top of it opens a hole. */
.payq .pay-banner, .payq .bstats, .payq .pay-topbar { margin: 0; }
/* A report row is a link, not a button — it still has to read as a card. */
.payq a.req-card { text-decoration: none; color: var(--text); }
/* Hover. A payroll card carries no status rail, so the trip card's tinted hover
   (.treq .req-card-wrap:hover) never reaches it and it falls through to the
   generic .req-card:hover, which paints the whole edge brand orange. A fully
   orange outline is how the rest of the app says SELECTED — on a list of three
   downloads it reads as "this one is chosen" rather than "your pointer is here".
   Same idea as the trip card, neutral: a barely-warmed edge, a whisper of tint,
   and the lift doing the actual work. */
.payq .req-card { transition: border-color 0.12s ease, background 0.12s ease, box-shadow 0.12s ease; }
.payq .req-card:hover {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  background: color-mix(in srgb, var(--accent) 5%, var(--surface));
  box-shadow: var(--shadow-control);
}
/* "Approve" stays visible before the period ends so the next step is obvious,
   but it has to LOOK unavailable — a live-looking button that ignores clicks
   reads as a broken app rather than a rule. */
.treq-head-actions button:disabled { opacity: 0.45; cursor: not-allowed; }
/* The table is a section's whole body here, so it keeps its own card edges but
   drops the nested-card look against the queue's surface. */
.payq .req-list > .paytable { background: var(--bg); }

.pay-banner {
  padding: 9px 12px;
  border-radius: 9px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 12.5px;
  color: var(--text-dim);
  line-height: 1.5;
}
.pay-banner.ok { background: rgba(16,185,129,0.10); border-color: rgba(16,185,129,0.4); color: var(--text); }
.pay-banner.warn { background: rgba(245,158,11,0.10); border-color: rgba(245,158,11,0.45); color: var(--text); }
.pay-banner.warn button { margin-left: 8px; }

/* The figures are .bstat tiles, exactly as on the billing home. The period still
   in progress gets the dashed, dimmed variant so it can never be read as the
   payable figure sitting next to it — it's a fortnight from a paycheck. */
.bstat-soft { background: none; border-style: dashed; }
.bstat-soft .bstat-n { font-size: 18px; color: var(--text-dim); }

/* Driver table. The grid is declared once and shared by the header and every
   row so the columns line up without a real <table>. */
.paytable { border: 1px solid var(--border); border-radius: 11px; overflow: hidden; background: var(--surface); }
.paytable-head,
.payrow-head {
  display: grid;
  grid-template-columns: 1fr 46px 46px 62px 84px 22px;
  align-items: center;
  gap: 6px;
}
.paytable-head {
  padding: 8px 12px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-dim);
  font-weight: 700;
}
.paytable-head span:not(:first-child) { text-align: right; }
.payrow { border-bottom: 1px solid var(--border); }
.payrow:last-child { border-bottom: none; }
.payrow-head {
  width: 100%;
  padding: 10px 12px;
  background: none;
  border: 0;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.payrow.open .payrow-head { background: var(--surface-2); }
/* A driver signed off on their own, inside a period that's still running. Drawn
   as a green edge rather than a tint: the office scans this table down the name
   column looking for who is left, and an edge answers that at a glance without
   making the approved rows look disabled. */
.payrow.signed { box-shadow: inset 3px 0 0 var(--success); }
.payrow.signed .payrow-name .cpill { vertical-align: middle; }
.payrow-name { font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.payrow-stat { font-size: 12.5px; color: var(--text-dim); text-align: right; font-variant-numeric: tabular-nums; }
.payrow-amt { font-size: 14px; font-weight: 800; text-align: right; font-variant-numeric: tabular-nums; }
.payrow-chev { color: var(--muted); text-align: right; }
.pay-warn { font-size: 12px; }
/* The buttons under an expanded driver: correcting the period, and the stub
   that driver is handed. Wraps on a phone rather than squashing the labels. */
.pay-row-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }

/* A .check-row (see the modal kit) whose explanation sits UNDER the label
   instead of in a tooltip: "email the drivers" is one click away from thirty
   people's inboxes, so what it will do has to be readable, not hoverable.
   Written as `.modal label.pay-check-opt` to outrank the generic
   `.modal label` caption styling, which would otherwise stack the tick above
   the text and shout the description in uppercase. */
.modal label.pay-check-opt {
  align-items: flex-start;
  gap: 10px;
  margin: 10px 0 0;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.modal label.pay-check-opt input[type="checkbox"] { margin-top: 2px; }
.pay-check-opt > span { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pay-check-opt small { font-size: 12px; line-height: 1.45; text-transform: none; letter-spacing: 0; }
.modal label.pay-check-opt:has(input:disabled) { opacity: 0.55; cursor: not-allowed; }

/* The driver's own "download my stub" link, under a period card. */
.pay-stub-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 10px 0 2px;
  font-size: 13px;
  font-weight: 600;
  color: var(--brand-600);
  text-decoration: none;
}

/* ---- Office: rate editor ----
   A driver picker, then one driver's whole deal on a form — the same list→detail
   as the admin settings screen, and deliberately built out of the same parts
   (.admin-menu-item, .admin-section, .org-field) so it doesn't read as a
   different app. What was here before was an eleven-column grid: it needed
   800px, it clipped rather than scrolled, and it had nowhere to put the sentence
   that says what each box means. */

/* Salary is a term of the deal, not a schedule fact — a quiet chip, not a status. */
.cpill-soft { background: var(--surface-2); color: var(--text-dim); border: 1px solid var(--border); }

/* .search-input is built for a flex toolbar (flex: 1, min-width: 200px); here it
   is the only thing on its line, so it has to be told to fill it. */
#rate-search { display: block; width: 100%; margin-top: 12px; }

/* The picker. A plain divided stack rather than a grid of cards: it's a list you
   run your eye down looking for one name. */
.rate-picker { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.rate-picker .rate-item { background: var(--bg); }   /* inset on the section surface */
.rate-item.inactive { opacity: 0.6; }
.rate-item-title { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
/* Initials, not an emoji: thirty rows of the same glyph is noise, and the
   initials are the thing you're actually scanning for. */
.rate-ico {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text-dim);
  border-radius: 999px;
  /* .admin-menu-ico's own background is var(--bg), which is what the row itself
     sits on here — it would vanish. */
  background: var(--surface-2);
}
.rate-ico-lg {
  width: 46px;
  height: 46px;
  font-size: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  flex-shrink: 0;
}
.rate-group-h {
  margin: 18px 0 0;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-dim);
}
.rate-picker + .rate-group-h { margin-top: 20px; }

/* Whose rates these are, above the form. It carries the name because the
   sections below are all "Salary per period" and "Per swap" — nothing in the
   form itself says who it belongs to. */
.rate-head { display: flex; align-items: center; gap: 12px; margin: 14px 0 0; }
.rate-head-txt { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.rate-head-name { font-size: 19px; font-weight: 700; }
.rate-head-sub { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }

/* The form fields. .org-field gives the notched label; these add the money
   prefix and the caption under each box. */
.rate-form .org-settings-grid { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.rate-field { min-width: 0; }
.rate-input { position: relative; display: flex; min-width: 0; }
.rate-input .pay-rate-in { width: 100%; min-width: 0; }
/* The currency sits INSIDE the box rather than in the label, so an empty box
   still reads as money rather than as a bare number. */
.rate-input-money::before {
  content: "$";
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: 14px;
  pointer-events: none;
}
/* Same box as every other form field in the app. .pay-rate-in is the save hook
   (see savePayRate) rather than a look, so the appearance is set here. */
.rate-form .pay-rate-in {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 10px;
  font-size: 14px;
  text-align: left;
  font-variant-numeric: tabular-nums;
  outline: none;
}
.rate-form .pay-rate-in:focus { border-color: var(--accent); }
.rate-form select.pay-rate-in { width: 100%; font-family: inherit; }
/* After the shared box, not before it — the shorthand `padding` above would
   otherwise reset this and the number would sit on top of the "$". */
.rate-form .rate-input-money .pay-rate-in { padding-left: 24px; }
.pay-rate-in:disabled { opacity: 0.45; cursor: not-allowed; }
/* Captions are rare here on purpose — the general explanation is behind the "?"
   on the section heading, so what's left is only ever about THIS driver. */
.rate-hint {
  margin: 6px 2px 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-dim);
}
/* The "use 6" offer sits in the caption, so it can't wear the caption's colour. */
.rate-hint .btn-xs { margin-left: 4px; vertical-align: baseline; }
/* One line under the whole rate grid, saying what an EMPTY box pays. It belongs
   to the section rather than to any one field, which is why it isn't a
   .rate-hint repeated five times. */
.rate-note {
  margin: 10px 2px 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-dim);
}
.rate-note .btn-xs { margin-left: 6px; vertical-align: baseline; }

/* Live "that's 2h 20m" readout under the trip-time inputs. */
.tt-calc { margin: 4px 0 10px; font-size: 12.5px; }

@media (max-width: 560px) {
  .paytable-head,
  .payrow-head { grid-template-columns: 1fr 34px 34px 52px 74px 18px; }
  .pay-hero-amt { font-size: 30px; }
}

/* ==========================================================================
   BILLING & THE SCHOOL PORTAL
   ==========================================================================
   Shared by the school's own screens (trips / book / billing) and the office's
   Billing admin. One set of components, because the two sides look at the same
   invoices — a status the school reads as "Overdue" must not read as something
   else on the office's list.

   Every colour comes from the theme tokens, so all of this follows light/dark
   without a second definition. Status colours stay SEMANTIC (green/amber/red);
   brand orange is reserved for the balance due and primary actions.
   ========================================================================== */

/* ---- the surface every billing block sits on ----
   `.card` in the markup is a grouping hook, not a style — this app has no
   generic .card rule, each component carries its own surface. One declaration
   here keeps every billing block identical instead of six near-copies. */
.btrip, .bempty, .binv, .binv-doc, .brow-card, .bform {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow);
  margin-bottom: 8px;
}
.btrip:last-child, .binv:last-child { margin-bottom: 0; }

/* ---- status chips ---- */
.bpill {
  display: inline-block; flex: none;
  font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  padding: 4px 10px; border-radius: 999px; white-space: nowrap;
}
.bp-ok     { background: rgba(59,130,246,.14);  color: #2563eb; }
.bp-wait   { background: rgba(161,98,7,.16);    color: var(--warning); }
.bp-done   { background: rgba(21,128,61,.14);   color: var(--success); }
.bp-bad    { background: rgba(220,38,38,.14);   color: var(--danger); }
.bp-credit { background: rgba(147,51,234,.14);  color: #9333ea; }
.bp-draft  { background: var(--bg-3);           color: var(--text-dim); }
.bp-void   { background: var(--bg-3);           color: var(--muted); text-decoration: line-through; }

/* ---- section headings inside the portal ---- */
/* THE HEAD OF A GROUP OF CARDS — accent, over a rule.
   It was grey on grey, which on a phone is a caption you skim past and on a desk
   is the reason the page reads as one undifferentiated pale field. The same
   treatment the outstanding-paperwork report gives its group heads (see
   .req-group): small, uppercase, brand-coloured, with a hairline carrying the
   eye across to the next section. Orange here is a LABEL, not a status — the
   distinction the tokens at the top of this sheet draw. */
.bsection-h {
  display: flex; align-items: center; gap: 10px;
  font-size: 11px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase;
  color: var(--accent); margin: 22px 4px 10px;
}
.bsection-h::after {
  content: ""; flex: 1; height: 1px;
  background: linear-gradient(to right,
    color-mix(in srgb, var(--accent) 42%, transparent), transparent);
}
.bsection-h.small { margin: 16px 0 6px; }

/* The school's header block. It was two things — the account strip and the
   "we're still setting you up" note — and the strip is the app's own header
   now (see .tacct, up with the top bar), where chrome that is the same on all
   four of their screens belongs. What's left is the note: a state with a next
   step, which is content and goes on the page. Rendered as nothing at all when
   there's nothing to say, rather than as an empty box with a margin. */
.school-head { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }

/* ---- explanatory notes (not errors — states with a next step) ---- */
.bnote { padding: 14px 16px; border-radius: 13px; margin-bottom: 12px;
         background: var(--surface-2); border: 1px solid var(--border); }
.bnote-warn { background: rgba(161,98,7,.10); border-color: rgba(161,98,7,.3); }
.bnote-t { font-size: 14px; font-weight: 700; }
.bnote-s { font-size: 13px; color: var(--text-dim); line-height: 1.5; margin-top: 3px; }

/* ---- empty states: say what to do next, not just "none" ---- */
.bempty { text-align: center; padding: 30px 20px; }
.bempty-t { font-size: 15px; font-weight: 700; }
.bempty-s { font-size: 13.5px; color: var(--text-dim); margin: 5px 0 16px; line-height: 1.5; }

/* THE ONE THING TO DO ON THE SCREEN, sized like it. Every other button in this
   app is 12px in a 7px pad because it sits in a row of its peers competing for a
   glance; this one has a row to itself above the list and is the whole reason the
   screen is worth opening, so it takes the full width and a real height. It also
   carries a longer name than a button usually can — "Book a trip or schedule
   change" — which is the other half of why it needed the room.
   FULL WIDTH, and that is a correction. It was capped at 380px and left-aligned,
   which on a phone left it stopping 48px short of the right edge of every card
   below it and on a desk made it 380 of an 844px column — a button that looks
   full width, isn't, and lines up with nothing. Whatever a lone centred button
   would have read as, a lone SHORT one read as lopsided, which is worse. Now it
   is the width of the column it heads, so its edges are the cards' edges.

   SOFT, not the solid brand gradient. It is the same accent-tinted, accent-
   bordered, accent-lettered shape as the controls inside the form it opens —
   the kind tiles, the pickup pills, the grade pills — and the screen reads as
   one family rather than as a dark slab over a pale list. The weight it loses
   from the fill it keeps in its size: it is still the only 54px control on the
   screen. Deliberately NOT the same as "Send request" at the foot of the form,
   which stays solid: that one commits the booking, and the two should not look
   equally final. */
.book-cta-row { margin: 10px 0 4px; }
.book-cta {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; min-height: 54px;
  padding: 14px 20px; border-radius: 14px;
  /* The label is a sentence, so it is sized against the viewport and allowed to
     wrap — balanced, and the button grows to hold it. Every phone from an SE up
     fits it on one line; below that two centred lines read as one button, where
     a fixed 15px would have pushed the words past the edge. */
  font-size: clamp(13.5px, 3.9vw, 15px); font-weight: 700; letter-spacing: -.01em;
  line-height: 1.25; text-wrap: balance;
  /* Beats `.btn-primary` by coming later at equal specificity — see the note in
     the modal-field block about how easily that goes the other way. */
  background: var(--accent-soft); color: var(--accent);
  border: 1.5px solid var(--accent);
  /* A tinted button casts no orange glow — the drop shadow belonged to the solid
     fill, and under a soft one it reads as a smudge. */
  box-shadow: none;
  transition: transform .16s ease, background .16s ease, border-color .16s ease;
}
.book-cta-i { font-size: 18px; line-height: 1; }
.book-cta:hover {
  transform: translateY(-2px);
  background: color-mix(in srgb, var(--accent) 20%, var(--surface));
}
/* The generic `.btn-primary:active` scale still applies; this only cancels the
   lift, so a press lands the button back on the card rather than shrinking it
   two pixels above it. */
.book-cta:active { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .book-cta { transition: none; }
  .book-cta:hover { transform: none; }
}

/* ---- a trip card in the school's list ---- */
/* A BOOKING CARD ANSWERS THE POINTER. With a mouse, a list of cards that does
   nothing under the cursor reads as a printout; the row lifts, its edge takes
   the accent and its shadow deepens — the same gesture every other openable row
   in the app makes. Cheap on a phone (there is no hover) and the whole
   difference between a live screen and a pale one on a desk. */
.btrip {
  padding: 15px 16px;
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}
.btrip:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: 0 12px 26px -16px color-mix(in srgb, var(--accent) 80%, transparent),
              var(--shadow);
}
@media (prefers-reduced-motion: reduce) {
  .btrip { transition: none; }
  .btrip:hover { transform: none; }
}
.btrip-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.btrip-title { font-size: 15.5px; font-weight: 800; letter-spacing: -.01em; }
.btrip-when { font-size: 13px; color: var(--text-dim); margin-top: 2px; }
.btrip-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px;
              font-size: 12.5px; color: var(--text-dim); }
.btrip-price { font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
/* What kind of job it is, on the cards where it isn't the usual field trip.
   Accented for the same reason the office's queue accents it: it changes how
   every other figure on the card reads. */
.btrip-kind { font-weight: 700; color: var(--accent); }
.btrip-note { margin-top: 10px; padding: 9px 11px; border-radius: 10px;
              background: var(--surface-2); font-size: 13px; color: var(--text-dim); line-height: 1.45; }
.btrip-note-bad { background: rgba(220,38,38,.10); color: var(--danger); }
.btrip-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.btrip-actions:empty { display: none; }

/* ---- the booking form ----
   Fields are styled here rather than inherited: this app has no global input
   rule (its other forms are inside .modal, which styles its own), so without
   these the booking screen falls back to raw browser chrome. */
.bform { padding: 18px 16px 20px; }
.bform input, .bform textarea, .bform select {
  width: 100%; padding: 12px 13px; font: inherit; font-size: 16px;
  border: 1px solid var(--border); border-radius: 11px;
  background: var(--surface); color: var(--text);
  transition: border-color .12s, box-shadow .12s;
}
.bform input:focus, .bform textarea:focus, .bform select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
.bform textarea { resize: vertical; min-height: 74px; }
.bform .gpill input { width: auto; padding: 0; box-shadow: none; }
/* The "Other" pickup address, revealed only while Other is the chosen pill.
   Hidden at the autosuggest wrapper so the suggestion dropdown goes with it;
   the explicit display:none is defensive, since [hidden] is only a UA default
   and .addr-autosuggest sets its own display. */
.bform .addr-autosuggest[hidden] { display: none; }
.bform .bloc-other { margin-top: 8px; }
/* THE END OF THE FORM. Every other button in the app is 12px in a 7px pad
   because it shares a row with its peers; this one is the whole point of the
   screen and had inherited the size of a toolbar control, so a form you have
   just spent four taps filling in finished on something smaller than its own
   captions. Sized like the button that opened it (see .book-cta). */
.bform .btn-primary {
  margin-top: 22px; min-height: 54px; padding: 15px 20px; border-radius: 14px;
  font-size: 15.5px; font-weight: 700; letter-spacing: -.01em;
  box-shadow: 0 8px 20px -10px color-mix(in srgb, var(--accent) 85%, transparent);
  transition: transform .16s ease, box-shadow .16s ease, filter .16s ease;
}
.bform .btn-primary:hover:not(:disabled) {
  transform: translateY(-2px); filter: brightness(1.06);
  box-shadow: 0 12px 26px -10px color-mix(in srgb, var(--accent) 95%, transparent);
}
.bform .btn-primary:active { transform: none; }
/* Sending: it says so, and it stops looking pressable while it does. */
.bform .btn-primary:disabled { opacity: .75; box-shadow: none; cursor: default; }
@media (prefers-reduced-motion: reduce) {
  .bform .btn-primary { transition: none; }
  .bform .btn-primary:hover:not(:disabled) { transform: none; }
}
.blbl { display: block; font-size: 12.5px; font-weight: 700; color: var(--text-dim); margin: 15px 0 5px; }
.bform > .blbl:first-child { margin-top: 0; }
.brow { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* `display: grid` outranks the `hidden` attribute, and a whole row goes away on
   a schedule change — it isn't asked how many children or how many buses. */
.brow[hidden] { display: none; }
/* The same row with one of its two cells hidden — a run has no heading-back
   time, and the remaining field shouldn't sit in half a row with a gap beside
   it where a question used to be. */
.brow.brow-1 { grid-template-columns: 1fr; }
.gpills { display: flex; flex-wrap: wrap; gap: 7px; }
.gpill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 12px; border-radius: 999px; cursor: pointer;
  font-size: 13px; border: 1.5px solid var(--border); background: var(--surface);
  transition: border-color .12s, background .12s, color .12s;
}
.gpill input { margin: 0; }
/* "+ Add a grade" — the last pill in the row, and deliberately shaped like one:
   it is the same act as ticking, for a word that isn't offered yet (0114).
   Dashed and unfilled so it reads as an opening in the row rather than as a
   grade somebody has already chosen — the one thing it must never look like. */
.gpill-add {
  border-style: dashed; background: none; color: var(--muted);
  font: inherit; font-size: 13px; font-weight: 600;
}
.gpill-add:hover { border-color: var(--accent); color: var(--accent); }
/* A pill is a tap target too, and on a form where the usual answer is already
   ticked, the hover is most of what says the others can be changed. */
.gpill:hover { border-color: color-mix(in srgb, var(--accent) 65%, var(--border)); color: var(--text); }
.gpill.sel { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 700; }
.gpill.sel:hover { background: color-mix(in srgb, var(--accent) 24%, transparent); }
.gpill:has(input:focus-visible) { box-shadow: 0 0 0 3px var(--accent-soft); }
/* A pill is inline-flex, which outranks the `hidden` attribute — the grades this
   department doesn't book for sit behind "More grades" and need saying twice. */
.gpill[hidden] { display: none; }

/* A grade pill is a <label>, and inside a modal that means `.modal label` had
   been styling it as a FIELD CAPTION — uppercase, letter-spaced, dimmed, and
   with 18px of top margin on every one of them, which is what made the rows of
   pills sit at ragged heights. Invisible on the boys' ladder, because Hebrew has
   no case; "Pre School" gave it away by shouting. The booking form needed the
   same exemption for the same reason (`.bform .gpill`), so this is the second
   place a pill has had to say it isn't a caption. */
.modal label.gpill {
  /* `display` belongs on this list for the same reason the rest of it does, and
     it was the one property the exemption missed: `.modal label { display:
     block }` is a class AND an element, so it outranks `.gpill`'s own
     `inline-flex` and the pill lost the `align-items: center` and the 6px `gap`
     that put air between its tick and its name. Inside `.gpills` that stayed
     hidden — a flex container blockifies its children anyway, so the ROW still
     laid out correctly and only the inside of each pill was wrong, which reads
     as a tick glued to its label rather than as a broken rule. */
  display: inline-flex;
  margin-top: 0; margin-bottom: 0; text-transform: none;
  letter-spacing: 0; color: var(--text);
}
.modal label.gpill.sel { color: var(--accent); }
.modal label.gpill input { width: auto; flex: none; margin: 0; padding: 0; box-shadow: none; }
/* The pills are one control, not a stack of them: the row of them needs the
   caption's gap above it and nothing between. */
.modal .gpills { margin-top: 2px; }

/* ---- the roll: how many children are in each grade (0117) ----
   A grid and not a stack of labelled fields: it is one question asked once per
   ticked grade, the grades are short words and the answers are two digits, so
   two or three to a row on a phone is the whole list in a glance instead of a
   scroll. Each row is name-then-box on one line, which is how somebody reads a
   class list off a sheet of paper.

   Scoped through `.modal` for the same trap the pills hit above: `.modal label`
   is a class AND an element, so it beats any single-class rule here — the
   caption styling (uppercase, dimmed, 18px of top margin) would otherwise land
   on every one of these and stand the rows at ragged heights. */
.gsizes {
  display: grid; gap: 8px; margin-top: 2px;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
}
.modal label.gsize {
  display: flex; align-items: center; gap: 8px;
  margin: 0; padding: 6px 10px; border-radius: 10px;
  border: 1.5px solid var(--border); background: var(--surface);
  text-transform: none; letter-spacing: 0; color: var(--text);
  font-size: 13px; font-weight: 600;
}
/* The name gives way, never the box: a grade with a long name ellipsises and a
   two-digit answer stays readable, which is the wrong way round only if you
   have forgotten which of the two the person is here to type. */
.modal label.gsize > span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.modal label.gsize input {
  width: 62px; flex: none; margin: 0; padding: 6px 8px;
  text-align: center; font-weight: 700;
}
/* A box still waiting for an answer is the one thing that will stop the save, so
   it says so — dashed, like the "+ Add a grade" pill, rather than in red: an
   opening still to be filled and not a mistake, because nobody has done anything
   wrong yet. The inputs carry `required`, which is what `:invalid` reads; it is
   true of them and no native submit is listening in a modal. */
.modal label.gsize:has(input:invalid) { border-style: dashed; }
.modal label.gsize:focus-within { border-color: var(--accent); }

/* "More grades": the way to the questions that are usually already answered.
   Deliberately NOT a button-looking button — it is an aside on the control above
   it, and filled buttons stacked down a form is what the tiles at the top are
   for. ("+ Add another day" was the other one; the days are a calendar now and
   there is nothing to add another of.) */
.bk-more {
  display: inline-block; margin: 8px 0 0; padding: 4px 2px;
  background: none; border: 0; font: inherit; font-size: 12.5px; font-weight: 700;
  color: var(--accent); cursor: pointer;
}
.bk-more:hover { text-decoration: underline; }
/* Same trap as .gpill and .brow: an explicit `display` outranks the `hidden`
   attribute. */
.bk-more[hidden] { display: none; }

/* ---- one tick, one fact ----
   "They won't need the regular bus that day." It used to be two sentences to
   choose between, because the answer decided the money as well as the dispatch;
   it decides no money now (a schedule change is free), so what's left is one
   thing worth saying and a tick is the smallest control that says it. Left
   alone it says NOTHING, which is why it isn't a pair of radios with a default:
   the office reads "Not said" rather than an answer the form invented. */
.bk-tick {
  display: flex; align-items: center; gap: 10px;
  margin: 15px 0 0; padding: 12px 13px; border-radius: 11px;
  border: 1.5px solid var(--border); background: var(--surface);
  font-size: 13.5px; color: var(--text-dim); cursor: pointer;
  transition: border-color .16s ease, background .16s ease, color .16s ease;
}
/* The form's own input rule dresses every box on the screen — 100% wide, padded,
   bordered, filled — which is exactly wrong for a checkbox, so this undoes it
   the same way `.bform .gpill input` does for the pills.

   ROUND, and therefore DRAWN. `accent-color` paints the platform's own checkbox,
   which is a square on every platform and can't be told otherwise — so the box
   is given up (`appearance: none`) and this draws its own: a circle, and the same
   inline-SVG check the notification ticks use, which is centred on the box rather
   than on a text baseline no font metric agrees about. Round is a softer thing to
   meet at the top of a form than a hard-cornered box, and it is the one control
   in the block now that anybody is asked to touch. */
.bk-tick input {
  appearance: none; -webkit-appearance: none;
  width: 19px; height: 19px; flex: none; margin: 0; padding: 0;
  border: 1.5px solid var(--border); border-radius: 50%;
  background: var(--surface); box-shadow: none; cursor: pointer;
  background-repeat: no-repeat; background-position: center;
  transition: background-color .16s ease, border-color .16s ease;
}
.bk-tick input:checked {
  background-color: var(--accent); border-color: var(--accent);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M3.6 8.4l2.9 2.9 5.9-6.4' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-size: 13px 13px;
}
.bk-tick:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
/* Ticked, it is an instruction to a dispatcher — the one answer on this form
   that changes what goes out of the yard — so it lights rather than merely
   showing a checked box in a grey row. */
.bk-tick:has(input:checked) {
  border-color: var(--accent); background: var(--accent-soft); color: var(--text);
  font-weight: 600;
}
.bk-tick:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/* ---- THE REGULAR-BUS BLOCK IS GONE ----
   It was a bordered card holding a big checkbox — "They won't need the regular
   bus that day" — lit in accent-soft when ticked, because it was the one answer
   on the form that decided whether a bus left the yard. It isn't an answer any
   more: whether the usual bus goes out is worked out from the grades on the
   request against the grades on the run (see the booking form), so what is left
   here is the footnote naming that run, and a footnote gets no card. The block's
   rules went with the tick rather than being left to style an element nothing
   renders. */
/* The fold under it now holds only the note, so the textarea's own caption and
   the fold's summary would ask the same question twice, eight millimetres
   apart. The summary is the one that's visible when it's shut, so it keeps the
   words and the caption inside stands down. */
#bk-extra-body .blbl { display: none; }

/* ---- WHICH RUN THIS REPLACES ----
   A FOOTNOTE, not a control. It was two accent-bordered chips and a "Change"
   button, which is the size of something being asked — and which run our bus
   is being held off is the office's own bookkeeping, not the secretary's
   decision. At chip size a school reads it as one more thing to get right,
   gets it wrong, and the app believes them; at this size it is the form
   showing its working, and there is nothing to answer.

   So: no fill, no border, no brand colour, a small size, and the way it belongs
   to said in words rather than in a tag — one plain line per way, which is what
   a fact you may glance at and never touch should look like. It stands on its
   own now that the tick above it has gone, so it keeps a line's worth of air
   above it instead of hugging a sentence that is no longer there. */
.bk-instead { display: flex; flex-direction: column; gap: 1px; margin-top: 14px; }
/* Empty until a way is picked — the margin has to go with it, or the form has a
   14px hole where a footnote isn't. */
.bk-instead:empty { display: none; }
.bk-runline {
  font-size: 12px; font-weight: 500; line-height: 1.4;
  color: var(--text-dim);
}
/* The pickers themselves never come out. They are still the FIELD — suggestRun
   writes into them and the submit reads `schedule_id` off them — so they stay in
   the markup, and this says once and in the sheet that they are not a control on
   this form. Spelled out as well as carrying `hidden`, since a stray `display`
   anywhere would outrank the attribute (same trap as `.bk-more[hidden]`). */
#bk-run-picks { display: none; }

/* ---- everything else, folded away ----
   The run it replaces, who else it might be, a note. Each is worth having and
   none is worth a row of the form: the run is guessed right nearly every time,
   the group is usually a grade already ticked, and most requests have no note.
   Closed, the whole lot is one line. */
.bk-extra { margin-top: 15px; }
.bk-extra > summary {
  display: flex; align-items: center; gap: 8px;
  padding: 11px 13px; border-radius: 11px; cursor: pointer;
  border: 1px dashed var(--border); background: transparent;
  font-size: 12.5px; font-weight: 700; color: var(--text-dim);
  list-style: none;
  transition: border-color .16s ease, color .16s ease, background .16s ease;
}
.bk-extra > summary::-webkit-details-marker { display: none; }
/* The chevron leans toward the door it's about to open — the same gesture the
   report screens' section heads make. */
.bk-extra > summary::before {
  content: "›"; font-size: 17px; line-height: 1; color: var(--muted);
  transition: transform .18s ease, color .16s ease;
}
.bk-extra[open] > summary::before { transform: rotate(90deg); }
.bk-extra > summary:hover {
  border-color: var(--accent); color: var(--accent); background: var(--accent-soft);
}
.bk-extra > summary:hover::before { color: var(--accent); }
.bk-extra[open] > summary { color: var(--text); border-style: solid; }
/* Opened, the body slides down rather than appearing — a form that grows by
   three fields under the finger should show where they came from. */
.bk-extra-body { animation: bkExtraIn .2s ease both; }
@keyframes bkExtraIn {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: none; }
}
/* A fold's own first caption doesn't need the gap that separates two fields. */
.bk-extra-body > .blbl:first-child { margin-top: 12px; }
.bk-extra-body > div[hidden] { display: none; }

/* ---- the live estimate ---- */
.best {
  margin-top: 20px; padding: 16px 17px; border-radius: 14px;
  background: var(--accent-soft); border: 1px solid var(--accent);
}
.best-l { font-size: 10.5px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; color: var(--accent); }
.best-n { font-size: 30px; font-weight: 800; letter-spacing: -.02em; margin: 4px 0 2px;
          font-variant-numeric: tabular-nums; }
.best-s { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; }
/* Separated by a rule so it reads as a condition on the price, not as more
   small print about the arithmetic above it. */
.best-note { margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--accent);
             font-size: 12.5px; color: var(--text-dim); line-height: 1.5; }
.best-note strong { color: var(--text); }
.best.best-idle { background: var(--surface-2); border-color: var(--border); }
.best.best-idle .best-l { color: var(--muted); }
.best.best-idle .best-n { font-size: 16px; font-weight: 700; color: var(--text-dim); }

/* THE QUIET ANSWER TO "WILL THIS COST US?"
   A schedule change has no price box; it has this line, under the last field and
   above the send button. Grey, one size down from the form's own captions, and
   phrased as what the run IS rather than as what it costs — see the markup for
   why a "No charge" headline was the wrong thing to put on this form. */
.bk-fine {
  margin: 16px 2px 0; font-size: 12.5px; line-height: 1.5;
  color: var(--text-dim); text-align: center;
}
.bk-fine[hidden] { display: none; }

/* ---- balance hero on the school's Billing tab ---- */
.bbal { text-align: center; padding: 24px 16px 20px; }
.bbal-l { font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.bbal-n { font-size: 40px; font-weight: 800; letter-spacing: -.03em; margin-top: 4px;
          font-variant-numeric: tabular-nums; }
.bbal-due .bbal-n { color: var(--accent); }

/* ---- an invoice row (both sides use it) ---- */
.binv {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; text-align: left; padding: 14px 16px; cursor: pointer;
}
.binv-main { min-width: 0; flex: 1; }
.binv-no { font-size: 14.5px; font-weight: 800; font-variant-numeric: tabular-nums; }
.binv-sub { font-size: 12.5px; color: var(--text-dim); margin-top: 2px;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.binv-right { flex: none; text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
.binv-amt { font-size: 16px; font-weight: 800; font-variant-numeric: tabular-nums; }
.binv-list { display: flex; flex-direction: column; gap: 8px; }

/* ---- the invoice document (portal detail + admin modal) ---- */
.binv-doc { padding: 18px 16px 20px; }
.binv-doc-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px;
                 padding-bottom: 14px; border-bottom: 2px solid var(--accent); }
.binv-doc-no { font-size: 21px; font-weight: 800; letter-spacing: -.02em; }
.binv-doc-meta { display: flex; flex-wrap: wrap; gap: 6px 18px; padding: 12px 0 4px;
                 font-size: 12.5px; color: var(--text-dim); }
.blines { margin-top: 8px; }
.bline { display: flex; align-items: baseline; gap: 12px; padding: 11px 0;
         border-bottom: 1px solid var(--border); font-size: 14px; }
.bline-d { flex: 1; min-width: 0; }
.bline-d .muted { display: block; margin-top: 2px; }
.bline-a { flex: none; font-weight: 600; font-variant-numeric: tabular-nums; }
.bline-x { flex: none; background: none; border: none; color: var(--muted);
           font-size: 19px; line-height: 1; cursor: pointer; padding: 0 2px; }
.bline-x:hover { color: var(--danger); }
.btotal { border-bottom: none; font-weight: 800; font-size: 15.5px; padding-top: 13px;
          border-top: 1px solid var(--border); }
.bbalance { border-top: 2px solid var(--accent); color: var(--accent); font-size: 18px; }

/* ---- admin: the dashboard tiles ---- */
.bstats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 16px; }
.bstat {
  display: flex; flex-direction: column; gap: 3px; align-items: flex-start;
  padding: 14px 15px; border-radius: 13px; cursor: pointer; text-align: left;
  background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow);
  transition: border-color .12s;
}
.bstat:hover { border-color: var(--accent); }
.bstat-n { font-size: 22px; font-weight: 800; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.bstat-l { font-size: 11.5px; color: var(--text-dim); font-weight: 600; }
.bstat-bad .bstat-n { color: var(--danger); }
.badmin-links { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
/* The CSV export is an <a> so it downloads; it must still read as a menu row. */
.badmin-links a.admin-menu-item { text-decoration: none; color: inherit; }

/* ---- admin: a generic list row with an action on the right ---- */
.brow-card { display: flex; align-items: center; justify-content: space-between; gap: 14px;
             width: 100%; text-align: left; padding: 13px 15px; margin-bottom: 8px; }
.brow-t { font-size: 14.5px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
.brow-s { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
.brow-actions { flex: none; display: flex; align-items: center; gap: 8px; }
/* A button dropped straight into the row (no .brow-actions wrapper) is still a
   flex item — without this a two-word label like "Set rates" wraps mid-button. */
.brow-card > button { flex: none; white-space: nowrap; }
.brow-bal { font-weight: 800; font-variant-numeric: tabular-nums; color: var(--accent); }

/* Four buttons don't fit a phone's modal footer on one line. */
.modal-actions-wrap { flex-wrap: wrap; }

/* ---- admin: one school's statement, department by department ----
   The grouping IS the document: a total for the school, then a section per
   department carrying its own balance. Everything here is the billing screen's
   own parts — .bstat tiles, .bsection-h heads, .brow-card rows — so a statement
   reads as part of Billing and not as a report bolted beside it. */
.stmt-dept { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.stmt-dept-bal { font-size: 14px; font-weight: 800; letter-spacing: 0; color: var(--text-dim);
                 font-variant-numeric: tabular-nums; }
.stmt-dept-bal.stmt-due { color: var(--accent); }
.stmt-dept-sub { margin: -4px 4px 8px; }
/* A grade group inside a division. The rule down the left is what says "these
   belong to the heading above" without a second, competing section header —
   the division's head is already an uppercase rule, and two of those stacked
   would read as two divisions. */
.stmt-sub { margin: 2px 0 10px 12px; padding-left: 12px; border-left: 2px solid var(--border); }
.stmt-subhead { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
                margin: 10px 2px 6px; font-size: 12.5px; font-weight: 700; color: var(--text-dim); }
/* A run of text with its own direction — Hebrew grade names inside an English
   line. Without the isolate the browser pulls whatever follows into the RTL run,
   so "כתה ט · 0 logins" renders as "· 0 כתה ט logins". */
.bidi { unicode-bidi: isolate; }
/* A sub-department in the customer sheet: same row, one step in, so it reads as
   an account you can bill — because it is — and not as a caption. */
.cd-sub { margin-left: 16px; }
.cd-addsub { margin: 0 0 14px 16px; }
/* A department with nothing on it is a REMARK, not an empty screen — .empty's
   60px centred block would give the one department that owes nothing more of the
   page than the three that do. */
.stmt-empty { text-align: left; padding: 4px 4px 10px; background: none;
              font-size: 12.5px; color: var(--muted); }
/* The statement's tiles are figures, not links: .bstat is a button everywhere
   else on this screen and must not offer a pointer it can't honour here. */
.bstat:not(button) { cursor: default; }
.bstat:not(button):hover { border-color: var(--border); }

/* ---- admin: search + status chips ---- */
.binput { width: 100%; padding: 11px 13px; font-size: 15px; margin-bottom: 10px;
          border: 1px solid var(--border); border-radius: 11px;
          background: var(--surface); color: var(--text); }
.bchips { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 14px; }
.bchip { padding: 7px 13px; border-radius: 999px; cursor: pointer; font-size: 12.5px; font-weight: 600;
         border: 1px solid var(--border); background: var(--surface); color: var(--text-dim); }
.bchip.sel { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 700; }

/* ---- admin: the invoice modal ---- */
.binv-modal { max-height: 76vh; overflow-y: auto; }
.binv-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.binv-acts .btn-ghost, .binv-acts .btn-primary { flex: 1 1 auto; text-align: center; text-decoration: none; }
.bhistory { margin-top: 18px; font-size: 12.5px; color: var(--text-dim); }
.bhistory summary { cursor: pointer; font-weight: 700; }
.bhistory ul { margin: 8px 0 0; padding-left: 18px; line-height: 1.6; }

/* ---- admin: the aging table ---- */
.btable-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.btable { width: 100%; border-collapse: collapse; font-size: 13.5px; min-width: 560px; }
.btable th { text-align: right; font-size: 10.5px; font-weight: 800; letter-spacing: .08em;
             text-transform: uppercase; color: var(--muted);
             padding: 0 0 9px 12px; border-bottom: 1px solid var(--border); }
.btable th:first-child, .btable td:first-child { text-align: left; padding-left: 0; }
.btable td { text-align: right; padding: 11px 0 11px 12px; border-bottom: 1px solid var(--border);
             font-variant-numeric: tabular-nums; }
.btable tfoot td { border-bottom: none; border-top: 2px solid var(--border); }
.bcell-late { color: var(--danger); font-weight: 600; }

@media (max-width: 560px) {
  .bstats { grid-template-columns: 1fr 1fr; }
  .bbal-n { font-size: 34px; }
  .best-n { font-size: 26px; }
  .brow { grid-template-columns: 1fr; }
}

/* ---- the billing line on the office's trip detail ---- */
.req-bill { display: flex; align-items: center; gap: 12px; margin-top: 14px; padding: 12px 14px;
            border-radius: 12px; background: var(--surface-2); border: 1px solid var(--border); }
.req-bill-l { font-size: 10.5px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase;
              color: var(--muted); flex: none; }
.req-bill-v { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- Field Trips: the "it ran, bill it" strip under a card ---- */
/* The card is a button, so the strip is its sibling and the two are drawn as one
   object — a button inside a button is invalid HTML and eats the tap. */
/* The wrapper owns the rounded shape and clips to it, so the rail below can be a
   plain bar and still come out with the corners curved. */
.treq .req-card-wrap {
  display: flex; flex-direction: column; position: relative;
  border-radius: 14px; overflow: hidden;
}
.treq .req-card-wrap::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--rail, var(--border)); pointer-events: none; z-index: 1;
}
/* Clipping costs the focus ring its 2px of breathing room outside the card, so
   the ring is drawn just inside the edge instead of being cut away entirely. */
.treq .req-card-wrap :focus-visible { outline-offset: -3px; }
/* Only a card that actually carries a strip gives up its bottom edge — without
   .has-bill every card on the other tabs was drawn open-bottomed. */
.treq .req-card-wrap.has-bill .req-card { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: none; }
.req-bill-strip {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 18px 10px 20px; background: var(--surface-2);
  border: 1px solid var(--border); border-top: none;
  border-radius: 0 0 14px 14px;
}
.req-bill-hint { flex: 1; min-width: 0; font-size: 12.5px; color: var(--text-dim); }
.req-bill-warn { flex: 1; min-width: 0; font-size: 12.5px; font-weight: 600; color: var(--warning); }
.ts-billed { background: rgba(16,185,129,0.16); color: var(--success); }
.ts-own { background: rgba(168,85,247,0.16); color: #a855f7; }

/* ---- the confirm-and-bill sheet ---- */
.wrap-buses { display: flex; flex-direction: column; gap: 6px; margin: 10px 0 14px;
              padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface-2); }
.wrap-bus { display: flex; align-items: center; gap: 10px; font-size: 13.5px; }
.wrap-bus-no { font-weight: 700; flex: none; }
.wrap-bus-drv { flex: 1; min-width: 0; color: var(--text-dim);
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The running total. It is the thing being decided, so it is the loudest thing
   on the sheet after the times themselves. */
.wrap-quote { margin: 12px 0 4px; padding: 12px 14px; border-radius: 12px;
              background: var(--surface-2); border: 1px solid var(--border); font-size: 13.5px; }
.wrap-quote-line { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.wrap-quote-line + .wrap-quote-line { margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--border); }
.wrap-quote-line em { font-style: normal; color: var(--muted); font-size: 12px; }
.wrap-quote-total { font-size: 20px; font-weight: 800; }
.wrap-quote-warn { color: var(--warning); font-weight: 600; }
/* The two ticks on the sheet wrap onto a second line more often than the app's
   other check-rows (they carry an email address), so they align to the top. */
#wrap-body label.check-row { align-items: flex-start; margin-top: 12px; line-height: 1.4; }
#wrap-body label.check-row input[type="checkbox"] { margin-top: 2px; }

/* On a flat booking there is no length to confirm, so the time boxes fold away
   behind a summary: they're a record, not an input, and leaving them open would
   say the price is waiting on them. */
.wrap-times-opt { margin-top: 12px; }
.wrap-times-opt > summary {
  cursor: pointer; list-style: none; padding: 8px 0;
  font-size: 13px; font-weight: 600; color: var(--text-dim);
}
.wrap-times-opt > summary::-webkit-details-marker { display: none; }
.wrap-times-opt > summary::before { content: "▸ "; color: var(--muted); }
.wrap-times-opt[open] > summary::before { content: "▾ "; }

/* ============================================================
   Add a booking — what kind of job is it?

   The first question on the sheet, and the one every field under it depends on,
   so it is a row of real targets rather than a dropdown: the office is picking
   a pricing rule, and a rule you can see all four of at once is one you can be
   sure you picked right.
   ============================================================ */
.kind-picker {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin: 4px 0 8px;
}
/* The school's own booking form offers four as well: every pricing rule there is
   — but not the swap, which has no customer and isn't a booking — plus the
   schedule change, which is a run asked as the question a school actually has
   (migration 0106). So it uses the default four across and this modifier is left
   for the edit sheet below. */
.kind-picker-3 { grid-template-columns: repeat(3, 1fr); }
/* Which way a schedule change goes: two answers, and they are the whole
   request, so they get the same big targets as the kinds above rather than a
   pair of radios. */
.kind-picker-2 { grid-template-columns: repeat(2, 1fr); }
/* Editing a booking offers three: a trip can become a run and a run a shuttle,
   but nothing already booked can turn into a bus swap — that has no customer
   and nothing to invoice. */
.kind-picker-3 { grid-template-columns: repeat(3, 1fr); }
.kind-opt {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 10px 4px; border-radius: 12px;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-dim); font: inherit; cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.kind-opt-i { font-size: 20px; line-height: 1; transition: transform .18s ease; }
.kind-opt-l { font-size: 11.5px; font-weight: 700; text-align: center; line-height: 1.2; }
/* A tile is a target you press, so it answers the pointer: it lifts, its edge
   takes the accent, and the icon grows a little. The lift is what tells a
   chosen tile from a hovered one — the chosen tile is the one that stays. */
.kind-opt:hover {
  border-color: var(--accent); color: var(--text);
  transform: translateY(-2px);
  box-shadow: 0 6px 14px -8px color-mix(in srgb, var(--accent) 70%, transparent);
}
.kind-opt:hover .kind-opt-i { transform: scale(1.12); }
.kind-opt:active { transform: translateY(0); }
/* CHOSEN IS TINTED, not filled — and the tint is the app's ONE tint.
   Three attempts before this: a pale wash that had to be compared with its
   neighbours to be read, then a solid brand gradient that went too far the other
   way, then a 30%/20% mix of the two brand oranges over --surface, which is what
   was still here. That last one read as a heavy peach block — a colour that
   appears nowhere else in the app, sitting next to controls (the grade pills,
   "Both ways", the regular-bus block) that all light in --accent-soft. Two
   different oranges answering two questions on one form is the form looking
   assembled from parts.

   So it lights in --accent-soft like everything else, and what makes it
   unmistakable is the EDGE, not the fill: the accent border doubled by an inset
   ring, which is the same trick at a tenth of the weight. The label goes to the
   full text colour, which on a tinted tile is most of the difference from the
   dimmed one beside it. */
.kind-opt.active {
  background: var(--accent-soft);
  border-color: var(--accent); color: var(--text);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.kind-opt.active .kind-opt-l { color: var(--text); }
.kind-opt.active .kind-opt-i { filter: none; }
/* The chosen one is already lit, so hovering it must not promise a change. */
.kind-opt.active:hover { transform: none; }

.kind-opt:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/* WHICH WAY THE BUS GOES — a LIST, not a row of tiles.
   Deliberately NOT the .kind-picker above it, two questions up the same form,
   and the difference is what each is asking. "What do you need?" is four words
   that fit a tile and are compared with each other; this is three SENTENCES a
   school would say out loud ("both ways — in and home"), and three of those
   across a phone are three truncated labels nobody can compare. One per line,
   read top to bottom, tapped once. It also stops the two questions looking like
   one two-row control, which is half of why the old shape had to be worked out.
   The rows carry the same recipe as a tile — surface, border, accent-soft when
   chosen, the edge doing the work — so it still reads as this form's family. */
.bk-ways { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 8px; }
.bk-way {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 12px 14px; border-radius: 12px;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-dim); font: inherit; cursor: pointer;
  transition: background .12s, border-color .12s, color .12s, transform .12s;
}
.bk-way-i { font-size: 20px; line-height: 1; transition: transform .18s ease; }
.bk-way-l { font-size: 13.5px; font-weight: 700; line-height: 1.25; }
/* The first row is the commonest answer and the one the thumb lands on, so it is
   the one that gets a hair more presence — nothing louder than that, because it
   is an answer and not a recommendation. */
.bk-way[data-bkway="both"] .bk-way-l { font-size: 14px; }
.bk-way:hover {
  border-color: var(--accent); color: var(--text);
  box-shadow: 0 6px 14px -8px color-mix(in srgb, var(--accent) 70%, transparent);
}
.bk-way:hover .bk-way-i { transform: scale(1.12); }
/* Chosen is the tile recipe: accent-soft tint, accent edge doubled by an inset
   ring, label at full strength. The same answer looks the same everywhere on
   this form. */
.bk-way.active {
  background: var(--accent-soft);
  border-color: var(--accent); color: var(--text);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.bk-way.active:hover { transform: none; }
.bk-way:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
@media (prefers-reduced-motion: reduce) {
  .bk-way, .bk-way-i { transition: none; }
  .bk-way:hover .bk-way-i { transform: none; }
}

/* Everything above still says what it means without moving: the colours and the
   edges carry the state, and the motion is only ever the flourish on top. */
@media (prefers-reduced-motion: reduce) {
  .kind-opt, .kind-opt-i, .bk-day, .bk-tick, .bk-extra > summary,
  .bk-extra > summary::before { transition: none; }
  .kind-opt:hover, .kind-opt:hover .kind-opt-i, .bk-day:hover { transform: none; }
  .bk-extra-body { animation: none; }
}
/* One line saying what the chosen rule means in money. It changes with the
   picker above it, which is the whole reason it's here and not in a tooltip. */
.kind-blurb { margin: 0 0 10px; line-height: 1.45; min-height: 2.6em; }

/* ============================================================
   THE DAYS A SCHEDULE CHANGE IS ASKED FOR — a month, not a list
   ============================================================
   It was a date box that turned each answer into a removable chip. A day is a
   square on a calendar, and a chip reading "Aug 27 ✕" asks somebody to check a
   date they can't see in its week — while the case the list exists for is a
   STRETCH ("all next week"), which is a shape and not a list of names.

   So the picked days stay LIT IN THE GRID. The lit square is the answer and
   tapping it again is the way back, which is what a calendar day already means
   everywhere else — there is nothing here to press that removes rather than
   answers, and no second control to bring the first one back.

   The grid is deliberately QUIET: no card, no fill, a hairline round it and a
   hairline under its head. It sits inside a form between two captions, and a
   panel there would read as a different screen dropped into the middle of one. */
.bk-cal {
  margin: 8px 0 4px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface); overflow: hidden;
}
.bk-cal-head {
  display: flex; align-items: center; gap: 4px;
  padding: 8px 8px; border-bottom: 1px solid var(--border);
}
/* The month's name takes the room and the two steppers sit at the ends, so the
   thing you READ is centred and the things you PRESS are where a thumb is. */
.bk-cal-title {
  flex: 1; text-align: center; font-size: 13.5px; font-weight: 700; color: var(--text);
}
.bk-cal-nav {
  flex: none; width: 32px; height: 32px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 8px; background: transparent;
  font: inherit; font-size: 18px; line-height: 1; color: var(--text-dim);
  cursor: pointer; transition: background .14s ease, color .14s ease;
}
.bk-cal-nav:hover { background: var(--bg-2); color: var(--text); }

/* Seven columns, and the weekday heads are cells of the same grid rather than a
   row of their own — two grids can disagree about a column's width, and a head
   sitting half a letter off the days under it is the one thing a calendar must
   never do. */
.bk-cal-grid {
  display: grid; grid-template-columns: repeat(7, 1fr);
  gap: 2px; padding: 6px;
}
.bk-cal-dow {
  display: flex; align-items: center; justify-content: center;
  padding: 2px 0 4px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  color: var(--text-dim); text-transform: uppercase;
}
.bk-cal-cell {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 1; min-height: 34px; padding: 0;
  border: 1px solid transparent; border-radius: 9px;
  background: transparent; font: inherit; font-size: 13px; font-weight: 600;
  color: var(--text); cursor: pointer;
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
.bk-cal-cell.blank { cursor: default; }
.bk-cal-day:hover { background: var(--bg-2); }
/* Saturday is greyed on both rows — the head and the days under it — because
   the bus doesn't run and a day picked there is nearly always a mis-tap. Still
   tappable: a carrier's Sunday-running customer is not this app's business to
   refuse, and see the past below for why none of this is a refusal. */
.bk-cal-dow.sat, .bk-cal-cell.sat { color: var(--text-dim); }
/* THE PAST IS DIMMED AND STILL TAPPABLE. The grid puts thirty past days one
   finger away where a date box hid them behind typing, so a request for last
   Tuesday wants discouraging — but a day already picked has to stay
   un-pickable-back, and the odd request really is filed after the fact. A hint,
   never a refusal. */
.bk-cal-cell.past { color: var(--text-dim); opacity: .55; }
/* Today is a ring, not a fill: it says where you are, and a filled today beside
   a filled answer would be two different statements in one colour. */
.bk-cal-cell.today { border-color: var(--text-dim); }
/* THE ANSWER — TINTED, NOT FILLED, like every other answer on this form.
   It was a solid brand square with white text, and beside the grade pills eight
   millimetres above it — which say "chosen" with a tint, an accent edge and
   accent letters — it read as a different kind of statement altogether: a block
   of full-strength brand in the middle of a pale grid, sharp enough that the eye
   went to it before the question above it. One tap is not a louder decision than
   ticking a grade. So it takes the form's own recipe: the accent lives in the
   EDGE and the letters, doubled by an inset ring because a 34px square has less
   room to say it than a pill does.

   It still outranks everything above it — a day in the past that somebody has
   deliberately picked reads as picked, at full strength, or the form is arguing
   with a decision already made. */
.bk-cal-cell.on,
.bk-cal-cell.on.past,
.bk-cal-cell.on.sat {
  background: var(--accent-soft); border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
  color: var(--accent); font-weight: 800; opacity: 1;
}
.bk-cal-cell.on:hover { background: var(--accent-soft); }

/* The whole answer, in words, because the grid is ONE month and a stretch can
   cross two — a day picked in September is off screen the moment somebody steps
   back to August, and this is the only place it still shows. Drawn only when
   there is something to say. */
.bk-cal-sum {
  margin: 0; padding: 8px 11px 10px;
  font-size: 12.5px; font-weight: 600; color: var(--text-dim);
}

/* ============================================================
   "WE'VE HELD THE RUN'S OWN DRIVERS"
   ============================================================
   Sits above the assign rows on a schedule change and explains why they arrived
   already filled in (migration 0109). It has to be a NOTE and not a warning: the
   pre-fill is the right answer nearly every time, and dressing it in amber would
   train the office to check something that is usually correct. Accent-tinted,
   the same treatment the queue's kind chip gets, because it is context for the
   rows under it rather than a thing to act on. */
.asg-held-note {
  margin: 0 0 10px; padding: 9px 11px; border-radius: 10px;
  background: var(--accent-soft, var(--bg-2));
  border: 1px solid var(--accent);
  font-size: 12.5px; line-height: 1.45; color: var(--text-dim);
}
.asg-held-note strong { color: var(--text); }
/* The one case that IS a warning: the school named a run that puts no buses on
   the board that day, so nobody could be held and the office is about to crew it
   from scratch without being told why. Neutral rather than red — nothing is
   broken, the answer just isn't what the note above promises. */
.asg-held-none {
  background: var(--bg-2); border-color: var(--border);
}

/* ============================================================
   WHO RIDES THIS RUN — the picker (migration 0110)
   ============================================================
   The driver picker's own furniture (.dp-search / .dp-list / .dp-row), because
   it is the same act: search a list of names and choose. The only difference is
   that a tap toggles instead of closing, which the shared .dp-row.sel state
   already draws.

   The grades box belongs to the row above it and says so by sitting inside its
   selected outline, indented and quiet. It exists only while that row is chosen
   — blank is the normal answer, and an empty field under every unchosen name
   would read as a list of things left undone. */
/* The trigger sits as a SIBLING of its `.fld` label, the way the weekday pills
   above it do — see runDeptsFieldHtml(). Outside the label it is no longer a
   `.fld` child, so `.fld > .driver-trigger` doesn't reach it and it would fall
   back to an inline-block button hugging its own text.

   `display` and `width` are ALSO set inline on the element itself, and that is
   deliberate: a stylesheet is cached under a `?v=` that only changes when the
   server restarts, so a control whose whole shape depends on a brand-new rule
   renders wrong for anybody holding yesterday's CSS. The two properties that
   decide whether it looks like a field or like a stray button don't get to
   depend on that. Everything below is presentation and can wait for the cache. */
.run-depts-trigger {
  display: block; width: 100%;
  margin: 0 0 6px;
  min-height: 40px;
}

/* ============================================================
   A TWO-UP FIELD ROW INSIDE A MODAL
   ============================================================
   `.modal label:first-of-type { margin-top: 4px }` is scoped to a label's own
   PARENT, so in a `.fld-row` the left field matched it and the right one didn't
   — every two-up row in every modal has been sitting with its second column 14px
   lower than its first (Group / Time on the run sheet is the visible one).

   Fixed on the ROW rather than by unsetting `:first-of-type`: the row still
   needs a normal field's gap above it, so the 18px moves onto the row and both
   labels inside it start level. */
.modal .fld-row { margin-top: 14px; }
.modal .fld-row > label { margin-top: 4px; }

/* The grades under a chosen school are `.gpill`s — the department sheet's own
   pills, because it is the same act on the same vocabulary and a second grade
   control that looked different would read as a different kind of answer.
   This block is only the indent and the smaller size: they sit UNDER a row and
   belong to it, where the sheet's pills are a field of their own.

   Scoped through `.modal` for a reason worth keeping: `.modal input` is a class
   AND an element (0,1,1), so a bare `.rd-grades` (0,1,0) loses to it on every
   property the two share. That is what broke the typed box this replaced — it
   took `width: 100%` while keeping its 22px indent, hung 22px past the list, and
   `.dp-list`'s `overflow-y: auto` computes the other axis to `auto` as well, so
   the overhang surfaced as a horizontal scrollbar under the departments. */
/* The indent moved onto the block that holds BOTH halves — the department tabs
   and the pills under them — so the two line up under the school's row without
   each carrying its own copy of the same 22px. */
.modal .rd-pick { margin: 6px 0 10px 22px; }
.modal .rd-grades {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 8px 0 0;
}
.modal .rd-grades .gpill { padding: 6px 10px; font-size: 12.5px; border-width: 1px; }

/* ---- which department's ladder is on screen ----
   Small uppercase tabs on a rule, in the app's own section-label voice — and
   deliberately NOT `.gpill` or `.bchip`, which are what every pill UNDER them
   is. A control that changes which answers are showing must not look like one
   of the answers; two rows of near-identical accent pills is exactly what made
   "sixteen girls' grades wrapped into ten boys'" hard to read in the first
   place. The underline is the active state, the same way a folder tab's cut
   edge is: it says the pills below belong to this word. */
.modal .rd-cats {
  display: flex; align-items: flex-end; gap: 16px;
  border-bottom: 1px solid var(--border);
}
.rd-cat {
  position: relative; padding: 2px 0 7px;
  border: 0; background: none; font: inherit;
  font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); cursor: pointer;
}
.rd-cat.sel { color: var(--accent); }
.rd-cat.sel::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px; border-radius: 2px; background: var(--accent);
}
/* How many are ticked on the side you AREN'T looking at — without it, half of
   what a run carries is behind a word, and a run described entirely on the
   other ladder reads as a run with nothing on it. */
.rd-cat-n {
  display: inline-block; margin-left: 5px; padding: 1px 5px;
  border-radius: 999px; font-size: 10px; font-weight: 800; letter-spacing: 0;
  background: var(--surface-2); color: var(--text-dim);
}
.rd-cat.sel .rd-cat-n { background: var(--accent-soft); color: var(--accent); }
/* WHO THE TICKED GRADES WORK OUT TO (migration 0113). The office ticks grades
   and the accounts are derived, so this line is the answer to the question it is
   really being asked — and it has to sit UNDER the pills rather than beside
   them, or it reads as one more pill somebody disabled. `flex-basis: 100%` is
   what takes a full row inside a wrapping flex line; a `<br>` or a block child
   would both be swallowed by the flex context. */
.modal .rd-grades .rd-riders {
  flex: 0 0 100%; margin-top: 2px; line-height: 1.45;
}

/* The trips-screen header now carries two buttons, and "+ Add" has to stay
   reachable next to the share link on a narrow phone. */
.treq-head-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }

/* What kind of job a queued booking is, when it isn't the usual field trip.
   Accented because it changes how every other chip on the card reads. */
.req-fact-kind {
  background: var(--accent-soft, var(--bg-2));
  border-color: var(--accent);
  color: var(--text);
}

/* A note under a rate-card field, for the ones whose meaning isn't obvious from
   the label alone ("per run" needs to say that an evening job is two of them). */
.org-field-hint {
  margin: 4px 2px 0; font-size: 12px; line-height: 1.4; color: var(--text-dim);
}

/* ============================================================
   Info popover — the "?" beside a heading

   Long explanations don't sit on the screen; they wait behind one of these.
   The button is deliberately quiet — it's an offer, not a warning — and grows
   to a 28px tap target without pushing the heading around.
   ============================================================ */
.info-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 18px;
  height: 18px;
  margin: 0 0 0 6px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-dim);
  font-family: inherit;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  vertical-align: middle;
  position: relative;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
/* The visible hit area stays 18px; the touchable one is 28px. */
.info-btn::after { content: ""; position: absolute; inset: -5px; }
.info-btn:hover,
.info-btn[aria-expanded="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
.info-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Labelled variant ("ⓘ Learn more") for a screen whose subject needs naming. */
.info-btn.info-btn-text {
  width: auto;
  height: auto;
  padding: 3px 9px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
}

.info-scrim { position: fixed; inset: 0; z-index: 8400; }
.info-pop {
  position: fixed;
  z-index: 8500;
  width: max-content;
  max-width: min(340px, calc(100vw - 20px));
  max-height: min(60vh, 460px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--modal-bg);
  box-shadow: var(--shadow);
  color: var(--text);
  font-size: 13px;
  line-height: 1.5;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.14s ease, transform 0.14s ease;
}
.info-pop.is-open { opacity: 1; transform: none; }
.info-pop-body > :first-child { margin-top: 0; }
.info-pop-body > :last-child { margin-bottom: 0; }
.info-pop-body b { color: var(--text); }
.info-pop-body code {
  background: var(--surface-2);
  border-radius: 4px;
  padding: 1px 4px;
  font-size: 12px;
}
/* "Got it" is the only way out of the phone sheet; on a pointer device the
   popover already closes on the next click anywhere, so it stays hidden. */
.info-pop-ok { display: none; }

/* Phones get a bottom sheet — an anchored popover next to a 320px-wide heading
   has nowhere to go, and a sheet is what a phone user expects here. */
@media (max-width: 560px) {
  :root.info-sheet .info-scrim { background: rgba(0, 0, 0, 0.45); }
  :root.info-sheet .info-pop {
    left: 0;
    right: 0;
    bottom: 0;
    top: auto !important;
    width: auto;
    max-width: none;
    max-height: 72vh;
    padding: 18px 18px calc(18px + env(safe-area-inset-bottom));
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-bottom: 0;
    font-size: 14px;
    transform: translateY(12px);
  }
  :root.info-sheet .info-pop.is-open { transform: none; }
  :root.info-sheet .info-pop-ok {
    display: block;
    width: 100%;
    margin-top: 14px;
    padding: 11px;
    border: 0;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text);
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
  }
}

/* ============================================================
   Attendance — the office's once-a-day "did the day run as planned"

   Everything here is sized for a quick scan down a list and a single tap.
   The ROW is the unit: a day that ran normally should read as a column of
   quiet grey lines, so the one line that isn't grey is impossible to miss.
   ============================================================ */

.att-wrap { gap: 12px; }

.more-row-badge {
  min-width: 20px; height: 20px; padding: 0 6px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800; color: #fff;
  background: var(--danger); border-radius: 999px; flex-shrink: 0;
}

/* ---- day navigation ---- */
.att-daynav {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.att-daynav-mid { flex: 1; display: flex; flex-direction: column; gap: 1px; line-height: 1.25; }
.att-daynav-mid b { font-size: 15px; }
.att-daynav-mid small { font-size: 11.5px; }
.att-daynav button { flex: 0 0 auto; }

/* ---- "you're behind" strip ---- */
.att-behind {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: 8px 10px;
  font-size: 12.5px; color: var(--text);
  background: rgba(245,158,11,0.10);
  border: 1px solid rgba(245,158,11,0.45);
  border-radius: 9px;
}
.att-behind-day {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 3px 9px;
  font-size: 12px; font-weight: 600;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
}
.att-behind-day:active { background: var(--surface-2); }
.att-behind-day small { font-size: 10.5px; color: var(--text-dim); font-weight: 500; }

/* ---- the one tap ---- */
.att-head { display: flex; flex-direction: column; gap: 8px; }
.att-confirm-all {
  width: 100%;
  min-height: 48px;
  font-size: 15.5px;
  font-weight: 700;
}
.att-done {
  padding: 10px 12px;
  font-size: 13.5px; font-weight: 600;
  color: var(--text);
  background: rgba(16,185,129,0.12);
  border: 1px solid rgba(16,185,129,0.4);
  border-radius: 10px;
  text-align: center;
}
.att-progress { display: flex; align-items: center; gap: 8px; }
.att-bar {
  flex: 1; height: 5px;
  background: var(--bg-3);
  border-radius: 999px;
  overflow: hidden;
}
.att-bar i { display: block; height: 100%; background: var(--success); transition: width 0.2s ease; }

/* ---- sessions → runs → buses ----

   The office holds the day as Boys / Girls / the trips, and inside each of those
   as runs ("Boys 7 AM", ten buses). So does this screen: the RUN is what gets
   confirmed, and its buses are chips underneath it — the whole run visible at a
   glance, any one bus one tap away. Ninety-two rows was a list nobody read. */
/* Sorted by time the sessions come off and the run cards stand on their own —
   same list, same gap, one less lid to open. */
.att-sortbar { display: flex; justify-content: flex-end; }
.att-sortbar .sort-select { height: 32px; padding: 0 10px; font-size: 12.5px; }
.att-sections { display: flex; flex-direction: column; gap: 10px; }
.att-sections.by-time .att-sched-name { flex: 1; min-width: 0; }

.att-sec {
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: 11px;
  background: var(--surface);
  overflow: hidden;
}
.att-sec.cat-boys { border-left-color: var(--cat-boys); }
.att-sec.cat-girls { border-left-color: var(--cat-girls); }

.att-sec-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px;
  background: var(--surface-2);
}
.att-sec-toggle {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  padding: 0; background: none; border: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.att-sec-chev { font-size: 11px; color: var(--text-dim); width: 10px; }
.att-sec-count { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; }
.att-sec-left { font-size: 11.5px; font-weight: 700; color: var(--warning); white-space: nowrap; }
.att-sec-ok { font-size: 11.5px; font-weight: 600; color: var(--success); white-space: nowrap; }
.att-sec-body { display: flex; flex-direction: column; gap: 6px; padding: 8px; }
.att-sec-note {
  margin: 0 2px 2px;
  font-size: 11.5px; line-height: 1.4; color: var(--text-dim);
}

/* "Everything left here ran as planned", for a whole session or one run. This
   is the button the office actually presses — the per-bus answers are the
   exceptions, and the exceptions are the minority of a normal day. */
.att-scope-confirm {
  flex: 0 0 auto;
  padding: 6px 11px;
  font-size: 12px; font-weight: 700;
  color: var(--success);
  background: rgba(16,185,129,0.12);
  border: 1px solid rgba(16,185,129,0.4);
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
}
.att-scope-confirm:active { background: rgba(16,185,129,0.26); }

/* ---- one run ---- */
.att-sched {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  overflow: hidden;
}
.att-sched.needs { border-left: 3px solid var(--accent); }
.att-sched-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px;
  min-height: 44px;
}
.att-sched-toggle {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  padding: 0; background: none; border: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.att-sched-chev { font-size: 11px; color: var(--text-dim); width: 10px; flex: 0 0 auto; }
.att-sched-time {
  flex: 0 0 auto;
  font-size: 12.5px; font-weight: 700; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.att-sched-name {
  font-size: 14px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.att-sched-count { flex: 0 0 auto; font-size: 11.5px; color: var(--text-dim); white-space: nowrap; }
.att-sched-state { flex: 0 0 auto; font-size: 11.5px; font-weight: 600; white-space: nowrap; }
.att-sched-state.ok { color: var(--success); }
.att-sched-state.warn { color: var(--warning); }
.att-sched-state.left { color: var(--accent); font-weight: 700; }

/* ---- one bus, as a driver chip ----

   The driver's name leads and the bus is the second line: attendance is a
   question about people, and a grid of bus numbers is a grid you can't read a
   name out of. The initials disc gives each chip a shape the eye can find again
   after it has looked away. */
.att-chips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(184px, 1fr));
  gap: 7px;
  padding: 0 10px 10px;
}
.att-cchip {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 10px;
  min-height: 54px;
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 11px;
  cursor: pointer;
  overflow: hidden;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.att-cchip:active { transform: scale(0.985); }
.att-cchip.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
/* Waiting on an answer is the only state that reaches for attention. Everything
   already dealt with recedes, so the run visibly empties as it's worked. */
.att-cchip.needs { background: var(--surface); border-color: var(--accent-soft); }
.att-cchip.att-ok { background: rgba(16,185,129,0.09); border-color: rgba(16,185,129,0.30); }
.att-cchip.att-sub { background: rgba(245,158,11,0.11); border-color: rgba(245,158,11,0.42); }
.att-cchip.att-miss { background: rgba(239,68,68,0.10); border-color: rgba(239,68,68,0.40); }
.att-cchip.att-cancel,
.att-cchip.att-off,
.att-cchip.att-none { opacity: 0.62; }

.acb-av {
  flex: 0 0 auto;
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11.5px; font-weight: 800; letter-spacing: 0.3px;
  color: var(--text-dim);
  background: var(--bg-3);
  border-radius: 50%;
}
.acb-av.sm { width: 26px; height: 26px; font-size: 10.5px; }
.acb-av.lg { width: 42px; height: 42px; font-size: 14px; }
.att-cchip.att-ok .acb-av { background: rgba(16,185,129,0.20); color: var(--success); }
.att-cchip.att-sub .acb-av { background: rgba(245,158,11,0.22); color: var(--warning); }
.att-cchip.att-miss .acb-av { background: rgba(239,68,68,0.20); color: var(--danger); }
.att-cchip.needs .acb-av { background: var(--accent-soft); color: var(--accent); }

.acb-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.acb-name {
  font-size: 13px; font-weight: 700; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.acb-sub {
  font-size: 11px; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.acb-mark {
  flex: 0 0 auto;
  width: 18px;
  font-size: 13px; font-weight: 800; text-align: center;
  color: var(--text-dim);
}
.att-cchip.att-ok .acb-mark { color: var(--success); }
.att-cchip.att-sub .acb-mark { color: var(--warning); }
.att-cchip.att-miss .acb-mark { color: var(--danger); }

/* ---- the change dialog ----

   One bus at a time, named at the top, with everything the answer might need in
   it — including the roster search, so "somebody else covered it" is one gesture
   and not a second dialog stacked on this one. */
.att-modal { display: flex; flex-direction: column; gap: 12px; }
.att-modal-head { display: flex; align-items: center; gap: 10px; }
.att-modal-who { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.att-modal-who b { font-size: 15px; }
.att-modal-who small {
  font-size: 11.5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.att-modal-close {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; color: var(--text-dim);
  background: none; border: 1px solid var(--border); border-radius: 8px;
  cursor: pointer;
}
/* What's on the record right now, when it isn't the plan. */
.att-modal-now {
  padding: 7px 10px;
  font-size: 12px; font-weight: 600;
  border-radius: 8px;
}
.att-modal-now.sub { background: rgba(245,158,11,0.13); color: var(--warning); }
.att-choice.big { flex: 1 1 100%; min-height: 44px; font-size: 13.5px; }
.att-dlist { max-height: 190px; overflow-y: auto; }
.att-dlist .dp-row { display: flex; align-items: center; gap: 9px; }
.att-choice-row { display: flex; flex-wrap: wrap; gap: 6px; }
.att-choice {
  flex: 1 1 auto;
  min-height: 38px;
  padding: 8px 12px;
  font-size: 12.5px; font-weight: 600;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 9px;
  cursor: pointer;
  white-space: nowrap;
}
.att-choice:active { background: var(--bg-3); }
.att-choice.on { border-color: var(--accent); background: var(--accent-soft); color: var(--text); }
.att-choice.clear { flex: 0 0 auto; color: var(--text-dim); }

.att-choice-line { display: flex; flex-direction: column; gap: 5px; }
/* A span, not a <label> — `.modal label` upper-cases every label in a dialog and
   turns one followed by an input into a floating field label, and neither is
   what these two headings are. */
.att-flabel { font-size: 11.5px; font-weight: 600; color: var(--text-dim); }
.att-choice-line select { margin: 0; width: 100%; }
.att-choice-line small { font-size: 11px; }

.att-reasons { display: flex; flex-wrap: wrap; gap: 6px; }
.att-reason {
  padding: 6px 11px;
  font-size: 12px; font-weight: 600;
  color: var(--text-dim);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
}
.att-reason.on { border-color: var(--accent); background: var(--accent-soft); color: var(--text); }
.att-reason.clear { color: var(--muted); }
/* "Don't use a paid day" is not another reason — it's the money decision sitting
   under them, so it reads as a deduction rather than as a fifth chip. */
.att-reason.unpaid.on {
  border-color: rgba(239,68,68,0.45); background: rgba(239,68,68,0.14); color: var(--danger);
}

/* On a phone the run head runs out of room first. The bus count is the part
   that can go — the chips underneath are literally the count. */
@media (max-width: 560px) {
  .att-choice { flex: 1 1 100%; }
  .att-sched-count, .att-sec-count { display: none; }
  /* Two chips to a row on a phone. Narrower than this and the name — the whole
     point of the chip — is the first thing to get an ellipsis. */
  .att-chips { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

/* ============================================================
   STUDENTS — families, roster, import
   Reuses the dispatch shell (.disp-wrap / .disp-tabs / .disp-body) so the
   roster reads as another office screen rather than a new place. Everything
   below is the list row those screens are built from, plus the two things
   unique to this section: the "not on the map" strip and the import mapper.
   ============================================================ */

.stu-list { display: flex; flex-direction: column; gap: 6px; }

.stu-row {
  display: flex; align-items: center; gap: 9px;
  width: 100%; padding: 10px 12px; text-align: left;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  color: var(--text); cursor: pointer;
}
.stu-row:hover { border-color: var(--accent); }
.stu-row.static { cursor: default; }
.stu-row.static:hover { border-color: var(--border); }
.stu-row-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.stu-name { font-size: 14.5px; font-weight: 700; }
.stu-sub { font-size: 12.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stu-chev { color: var(--muted); font-size: 17px; flex: none; }
.stu-row-acts { display: flex; gap: 4px; flex: none; }

/* Which way this child rides. Both chips always show — "no PM" is a fact worth
   seeing, and a missing chip reads as missing data rather than as an answer. */
.stu-dirs { display: flex; gap: 3px; flex: none; }
.stu-dir {
  font-size: 10.5px; font-weight: 800; letter-spacing: .03em;
  padding: 2px 6px; border-radius: 6px;
  background: var(--bg-3); color: var(--muted);
}
.stu-dir.on { background: var(--accent-soft); color: var(--accent); }

.stu-flag {
  flex: none; font-size: 11px; font-weight: 700;
  padding: 2px 7px; border-radius: 999px;
  background: var(--bg-3); color: var(--text-dim);
}
.stu-flag.warn { background: rgba(245,158,11,0.16); color: var(--warning); }
.stu-flag.ok { background: rgba(16,185,129,0.15); color: var(--success); }
.stu-flag.off { background: var(--bg-3); color: var(--muted); }

/* The map gap. Loud on purpose: a child whose address isn't placed cannot be
   put on any route, and nothing else on the screen would say so. */
.stu-geo {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px;
  padding: 9px 12px; border-radius: 12px; font-size: 13px; line-height: 1.45;
}
.stu-geo.ok { background: rgba(16,185,129,0.12); color: var(--success); }
.stu-geo.warn { background: rgba(245,158,11,0.14); color: var(--text); border: 1px solid rgba(245,158,11,0.35); }
.stu-geo.bad { background: rgba(239,68,68,0.13); color: var(--text); border: 1px solid rgba(239,68,68,0.35); }
.stu-geo-acts { display: flex; gap: 6px; flex: none; }

.stu-seasonbar .fld { margin: 0; }
.stu-detail-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.stu-check { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--text-dim); margin: 6px 0; }
.stu-check input { width: 16px; height: 16px; }
.stu-dir-picks { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 18px; }
.stu-addr-override { border-top: 1px solid var(--border); padding-top: 4px; margin-top: 4px; }

/* ---- import ---- */
.stu-import-opts { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-items: end; }
.stu-map { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px; }
.stu-map-col { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.stu-map-head { font-size: 12px; font-weight: 700; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stu-map-sample { font-size: 11.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stu-map-col select { width: 100%; }

.stu-result { padding: 10px 12px; border-radius: 12px; font-size: 13px; background: var(--bg-2); border: 1px solid var(--border); }
.stu-result.ok { background: rgba(16,185,129,0.12); border-color: transparent; }
.stu-result.bad { background: rgba(239,68,68,0.13); border-color: transparent; }
.stu-result ul { margin: 6px 0 0; padding-left: 18px; }
.stu-result li { margin: 2px 0; }
.stu-skipped { margin-top: 8px; font-size: 12.5px; color: var(--text-dim); }

.stu-progress {
  position: relative; height: 22px; border-radius: 999px;
  background: var(--bg-3); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.stu-progress-bar { position: absolute; inset: 0 auto 0 0; background: var(--accent-soft); transition: width .18s ease; }
.stu-progress span { position: relative; font-size: 12px; font-weight: 700; color: var(--text); }

@media (max-width: 560px) {
  .stu-import-opts { grid-template-columns: 1fr; }
}

/* ============================================================
   Bus cleaning — the janitor's queue + trip schedule
   Same furniture as the driver's "My Bus" cards, so the two screens read as
   one app: a surface card, a bus number that scans down the left, and the
   actions on their own row underneath.
   ============================================================ */
.clean-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }

.clean-card, .clean-trip {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
}
/* Someone has picked this one up — the accent says "in hand", not "urgent". */
.clean-card-doing { border-left: 4px solid var(--accent); }

.clean-card-head, .clean-trip-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.clean-bus { font-weight: 700; font-size: 15px; }
.clean-chip {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px;
  color: var(--accent); background: var(--accent-soft);
  padding: 3px 8px; border-radius: 999px;
}
.clean-note { margin: 6px 0 4px; font-size: 14px; line-height: 1.45; }
.clean-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

.clean-trip-title { font-size: 14px; font-weight: 600; margin-top: 2px; }
.clean-trip .btn-xs { margin-top: 8px; }
.clean-trip-flag { margin-top: 8px; font-size: 12.5px; color: var(--accent); font-weight: 600; }

/* On "My Bus": the wash button's other state — already reported, nothing to do. */
.mybus-clean-filed {
  margin: -6px 0 18px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--bg-2);
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
}

/* ============================================================
   Bus swaps — the shop's board and the swap driver's copy of it
   (migration 0088)

   Same card furniture as the janitor's queue above, because it is the same kind
   of object: a short job somebody is about to pick up. What differs is the state
   it can be in, and each state gets exactly one visual cue on the left edge:

     going spare  → nothing. It's the default, and a board of four highlighted
                    cards highlights nothing.
     yours        → accent bar. "This one is on you."
     taken        → dimmed. Present so a driver who opens the app late can see
                    it's covered; not competing for attention.
   ============================================================ */
.swap-board { margin-top: 18px; }

/* "Request a swap" borrows the mechanic card whole (.mech-row) rather than
   copying its recipe: same surface, same 14px corner, same avatar-name-chevron
   shape, so the board reads as a continuation of the column above it instead of
   a form control parked underneath. The colour is what separates it: every
   other card on this screen opens work that already exists, this one starts
   something, and a full-strength brand row says so before it's read. */
.swap-board .swap-new {
  margin-bottom: 10px;
  background: var(--accent-soft);
  border-color: var(--accent);
  box-shadow: none;
}
.swap-board .swap-new:active { background: color-mix(in srgb, var(--accent) 26%, transparent); }
.swap-new .mech-row-name,
.swap-new .mech-row-arrow { color: var(--accent); }
.swap-new-avatar {
  background: var(--accent);
  /* .mech-row-avatar paints its initial white — which is what an emoji drawn as
     a text glyph (rather than in colour) needs to be, on solid orange. */
  color: #fff;
  font-size: 20px;
  line-height: 1;
}
/* The board itself — one card onto every outstanding swap. Slate, like the
   Unassigned row it sits under: this one is a queue, not an action. */
.swap-board-avatar { background: #64748b; font-size: 20px; line-height: 1; }
/* On "My Bus" the board is the first thing on the screen and the bus is under
   it, so it carries its own bottom gap rather than borrowing the hero's. */
.swap-board-driver { margin: 0 0 18px; }

/* On the DRIVER's screen the row is the only swap furniture there is — the
   cards live inside it — so a row with a job of theirs or work going spare has
   to carry the whole signal on its own. Same brand treatment as "Request a
   swap" on the shop's side, for the same reason: it's the row you open after a
   text, and grey-on-grey is the row nobody opens. A quiet board (nothing
   waiting, nothing yours) keeps the plain slate row — it's a log then. */
.swap-board-live {
  background: var(--accent-soft);
  border-color: var(--accent);
  box-shadow: none;
}
.swap-board-live:active { background: color-mix(in srgb, var(--accent) 26%, transparent); }
.swap-board-live .mech-row-name,
.swap-board-live .mech-row-arrow { color: var(--accent); }
.swap-board-live .swap-board-avatar { background: var(--accent); color: #fff; }

.swap-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 10px;
  transition: border-color 0.15s;
}
/* The shop's swaps aren't on the page anymore — they're inside the board's
   dialog, where a flat card is right. Only the driver's board on "My Bus" still
   shows them inline, and there they match the cleaning queue beside them. */
.swap-card-mine { border-left: 4px solid var(--accent); }
/* On the SHOP's board: somebody has taken it and a bus is on its way. Green,
   because it's a resolved state — the one thing a mechanic wants to see. */
.swap-card-claimed { border-left: 4px solid var(--success); }
/* On the DRIVER's board: somebody else took it. Nothing to do, so it recedes. */
.swap-card-taken { opacity: .62; }
/* Driven and closed — history, in the team board's Done tab. It recedes like a
   taken one (there's nothing to do to it either) but keeps the green edge a
   finished thing has earned, so the tab reads as a run of receipts. */
.swap-card-done { opacity: .72; border-left: 4px solid var(--success); }

.swap-card-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
/* The errand itself — "Take #12 to the shop, bring #27 back". It is the whole
   point of the card, so it gets the weight a bus number gets on a clean-card. */
.swap-card-title { font-weight: 700; font-size: 15px; line-height: 1.35; }
.swap-card-detail { margin: 6px 0 4px; font-size: 14px; line-height: 1.45; }
.swap-card-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

/* "Pay it by the hour" on the ask and the change form. A toggle row dropped
   between two labelled fields needs the breathing room a <label> gets, or it
   reads as part of the box above it. */
.swap-basis-row { margin: 14px 0 6px; }
.swap-basis-row .toggle-label { font-weight: 600; }
.swap-basis-row .toggle-label .muted { font-weight: 400; }

/* Swaps this driver has already done and been paid for — a receipt, not work.
   Quiet, and small enough that three of them don't crowd out a live job. */
.swap-done-list {
  margin-top: 4px; padding-top: 8px;
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 6px;
}
.swap-done-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: 13px; color: var(--text-dim);
}

/* ============================================================================
   THE PAPERWORK REPORT — Drivers → Reports

   THERE IS ALMOST NOTHING HERE, AND THAT IS THE POINT. The screen is the office
   shell every other one uses — `.disp-body.treq`, the panel the payroll run and
   the trip queue are drawn in — with `.bstats` counters, `.bchip` cut pills,
   `.req-group` sections and `.cpill` status pills inside it. Two earlier
   versions built their own summary card, their own tile grid, their own toolbar
   and their own group cards floating on the page background, and the result
   read as a report bolted onto the side of the app. Everything below is either a
   TONE (the four status colours) or a small adjustment to a part that already
   existed. If a rule here is doing more than that, it is probably re-inventing
   something with a class name already in this file.

   ONE COLOUR PER GROUP, SET ONCE. `--rep-c` is declared by a `.rep-tone-*` class
   and everything downstream reads it: the counter's figure, the section's rule,
   the row's dot. So a group is the same colour everywhere by construction, and
   a new place inherits it for free. The ramp is red → amber → faded amber →
   grey: severity you can read down the page in colours the app already speaks.
   Brand orange is NOT in it — orange is the brand and never a status (see the
   theme note at the top of this file); the only orange here is on the section
   labels and the controls you press, exactly as on the payroll screen.
   ============================================================================ */

/* `--rep-fade` is how "due within 30 days" stays the same amber as "due this
   week" without reading as equally urgent — one meaning, two strengths, and no
   fifth colour to explain. It is applied to DECORATION only, never to a number
   or a word: a count at 45% opacity is a legibility problem dressed up as a
   design. */
.rep-tone-expired { --rep-c: var(--danger);  --rep-fade: 1; }
.rep-tone-week    { --rep-c: var(--warning); --rep-fade: 1; }
.rep-tone-month   { --rep-c: var(--warning); --rep-fade: 0.45; }
.rep-tone-pending { --rep-c: var(--muted);   --rep-fade: 0.7; }

/* ---- The counters: .bstat, wearing its group's colour ---- */
.repq .repstats { margin-bottom: 2px; }
.rep-stat .bstat-n { color: var(--rep-c); }
/* A cleared group keeps its place and goes grey — the nought is what the office
   worked for, and dropping the tile would hide it. */
.rep-stat.rep-zero { box-shadow: none; background: var(--bg-2); cursor: default; }
.rep-stat.rep-zero .bstat-n { color: var(--muted); }

/* ---- The sections: .req-group, with a head you can shut ----
   `.treq .req-group-head` already draws the orange uppercase label over a rule
   — this only has to make it a button and hang a chevron off it. */
.repq .rep-ghead {
  width: 100%; background: none; border-width: 0 0 1px; border-style: solid;
  cursor: pointer; font-family: inherit;
}
/* `.treq .req-group-head span` is the count badge; the chevron is the exception. */
.repq .rep-ghead .rep-chev {
  margin-left: 0; padding: 0; background: none; border: 0;
  width: 13px; text-align: center;
  font-size: 15px; line-height: 1; color: var(--text-dim);
  transform: rotate(90deg); transform-origin: 50% 50%;
}
.repq .rep-group.open .rep-ghead .rep-chev { transform: rotate(-90deg); }
/* Shown only while the group is open — collapsed, a section is a label and a
   count, which is what lets eight of them read as a contents page. */
.rep-gnote { margin: -4px 0 8px; font-size: 12px; color: var(--text-dim); }

/* ---- The rows: the .paytable recipe ---- */
.rep-table {
  border: 1px solid var(--border); border-radius: 11px;
  overflow: hidden; background: var(--surface);
}
.rep-row {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 10px 12px;
  background: none; border: 0; border-bottom: 1px solid var(--border);
  text-align: left; cursor: pointer; color: inherit;
}
.rep-row:last-child { border-bottom: none; }
.rep-row:active { background: var(--surface-2); }
/* The row's own bucket, which is not always the section's: "by driver" puts a
   lapsed medical and a licence due in a fortnight under the same name. */
.rep-dot {
  flex: none; width: 7px; height: 7px; border-radius: 50%;
  background: var(--rep-c); opacity: var(--rep-fade);
}
.rep-row-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.rep-row-lead {
  font-size: 13.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rep-row-sub {
  font-size: 12px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rep-row .cpill { flex: none; }
.rep-more {
  display: block; width: 100%; padding: 10px 12px;
  background: none; border: 0; border-top: 1px solid var(--border);
  font-size: 12.5px; font-weight: 700; color: var(--accent);
  text-align: center; cursor: pointer; font-family: inherit;
}

/* Nothing on this screen moves to say anything — the growing dot and the
   chevron's nudge are emphasis on a colour change that carries the meaning by
   itself. So they are the first things to go, and nothing is lost with them. */
@media (prefers-reduced-motion: reduce) {
  .rep-stat:hover { transform: none; }
  .rep-row:hover .rep-dot { transform: none; }
  .rep-row:hover .gap-go { transform: none; }
  .repq .rep-ghead:hover .rep-chev { transform: rotate(90deg); }
  .repq .rep-group.open .rep-ghead .rep-chev,
  .repq .rep-group.open .rep-ghead:hover .rep-chev { transform: rotate(-90deg); }
}

/* ============================================================================
   HOVER LAYER — everything a pointer can touch answers back

   Gated on (hover: hover) and (pointer: fine) for the reason spelled out over
   the My Bus block: half this app is used one-handed on a phone all day, and an
   ungated :hover sticks to whatever was last tapped until something else is —
   a chip left wearing an orange edge reads as "selected", which is a lie.
   Desktop is where hover means anything, so desktop is where it lives.

   Rules are grouped by screen and stay inside the vocabulary the rest of the
   sheet already speaks, so nothing here invents a new way to say "you can press
   this":
     row in a list      → its surface lifts one step (--surface-2 / --bg-3)
     card / chip / pill → --accent edge, and --accent-soft fill if it's a picker
     filled button      → brightness(1.08) + the toolbar lift
     ghost / outline    → fills toward its own colour, red stays red
     collapsible head   → the caret turns brand orange and nudges over
   A control that already carries meaning on its border (a priority rail, an
   attendance verdict) lifts with a shadow instead, so hovering never argues
   with status.

   The transition block comes first: a transition has to sit on the base
   selector to run both ways, and inside this media query it costs a phone
   nothing.
   ============================================================================ */
@media (hover: hover) and (pointer: fine) {
  .tab, .dlg-tab, .mst-btn, .user-tab, .filter-select,
  .btn-primary, .btn-success, .btn-ghost, .btn-outline, .btn-danger, .btn-danger-ghost,
  .fab, .mech-modal-x, .att-modal-close, .chat-send, .chat-back,
  .more-row, .more-row-chevron, .mech-row, .mech-row-arrow, .cbucket-head,
  .job-card, .job-cta, .chat-row, .chat-pick, .trip-row, .trip-bus, .binv,
  .paycard-head, .payday-head, .payrow-head, .menu-item, .tp-board-name,
  .plan-name, .plan-card, .plan-go, .plan-back,
  .sec-caret, .gap-head, .gap-caret, .disp-group-chev, .disp-run-toggle,
  .disp-run-time-btn, .att-sec-chev, .att-sched-chev,
  .wd-pill, .dur-chip, .gpill, .bchip, .rd-cat, .planday-pill, .att-choice, .att-reason,
  .att-behind-day, .att-scope-confirm, .att-cchip,
  .run-dur, .run-edit, .run-row-count .step, .slot-menu, .slot-fill, .slot-fix,
  .cal-cell, .insp-group-ok, .insp-btn, .insp-vbtn,
  .dd-photo-click, .switch .slider, .tt-scope, .cr-disclose-sum,
  .rep-stat, .rep-stat .bstat-l, .rep-ghead, .rep-ghead span, .rep-chev,
  .rep-row, .rep-row-lead, .rep-row .gap-go, .rep-dot, .rep-more {
    transition: background-color 0.14s ease, border-color 0.14s ease, color 0.14s ease,
      box-shadow 0.14s ease, filter 0.14s ease, transform 0.14s ease,
      opacity 0.14s ease;
  }

  /* ---- The button system. Nothing else in the app is pressed as often. ---- */
  .btn-primary:not(:disabled):hover,
  .btn-success:not(:disabled):hover,
  .btn-danger:not(:disabled):hover { filter: brightness(1.08); box-shadow: var(--shadow-control); }
  .btn-ghost:not(:disabled):hover { background: var(--bg-3); border-color: var(--muted); }
  .btn-outline:not(:disabled):hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
  /* The destructive pair fill toward red rather than toward the brand — a
     delete that warms up orange on the way in is the wrong promise. */
  .btn-danger-ghost:not(:disabled):hover { background: rgba(239, 68, 68, 0.12); }

  /* ---- Round / icon-only buttons ---- */
  .fab:hover { filter: brightness(1.08); box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45); }
  .chat-send:not(:disabled):hover { filter: brightness(1.08); }
  .chat-back:hover { color: var(--brand-400); }
  .mech-modal-x:hover,
  .att-modal-close:hover { background: var(--bg-3); border-color: var(--muted); color: var(--text); }
  .dd-photo-click:hover { filter: brightness(1.08); }

  /* ---- Tabs and segmented pickers. The chosen one is already loud; only the
     others have anywhere to go. ---- */
  .tab:not(.active):hover { background: var(--surface-2); border-color: var(--muted); color: var(--text); }
  .dlg-tab:not(.active):hover { background: var(--bg-3); color: var(--text); }
  /* Keeps to --brand-soft, the softened orange these in-dialog pills run on. */
  .mst-btn:not(.active):hover { border-color: var(--brand-soft); }
  .user-tab:not(.active):hover { border-color: var(--accent); color: var(--text); }
  /* :not(:focus) so hovering an open dropdown doesn't pull the focus ring off it. */
  .filter-select:hover:not(:focus) { border-color: var(--muted); }
  .switch input:not(:disabled) + .slider:hover { background: var(--muted); }
  .switch input:checked:not(:disabled) + .slider:hover { background: var(--accent); filter: brightness(1.1); }

  /* ---- Rows: the More menu, the mechanic list, the shop's job cards ---- */
  .more-row:hover { background: var(--surface-2); }
  .more-row:hover .more-row-chevron { color: var(--accent); transform: translateX(2px); }
  .more-row-danger:hover { background: rgba(239, 68, 68, 0.08); }
  .mech-row:hover { background: var(--surface-2); border-color: var(--accent); }
  .mech-row:hover .mech-row-arrow { color: var(--accent); transform: translateX(2px); }
  .cbucket-head:hover { background: var(--bg-3); }
  /* The card's left edge is its priority — swapping the border would erase the
     one thing the mechanic scans this column for, so the whole card lifts. */
  .job-card:has(.job-card-tap:hover) { box-shadow: var(--shadow-control); }
  .job-cta-go:hover, .job-cta-done:hover { filter: brightness(1.08); }
  .job-cta-ghost:hover { border-color: var(--accent); color: var(--accent); }

  /* ---- Collapsible heads. These buttons are only part of their row, so a
     background wash would highlight half a line — the caret answers instead. ---- */
  .sec-collapsible:hover .sec-caret { color: var(--accent); }
  .gap-head:hover { background: var(--bg-3); }
  .gap-head:hover .gap-caret { color: var(--accent); }
  .disp-group-toggle:not(.static):hover .disp-group-chev { color: var(--accent); }
  .att-sec-toggle:hover .att-sec-chev,
  .att-sched-toggle:hover .att-sched-chev { color: var(--accent); }
  /* These two own their whole row, so they can take a wash. --bg-2 rather than
     the --surface their :active uses: the run card sits on --bg, and in light
     theme --surface is white, which is the colour of the panel behind it — the
     hovered row would dissolve into its own container. */
  .disp-run-toggle:hover { background: var(--bg-2); }
  .disp-run-time-btn:hover { background: var(--bg-2); color: var(--accent); }
  .course-cabinet > summary:hover,
  .due-details > summary:hover,
  .bhistory summary:hover,
  .wrap-times-opt > summary:hover { color: var(--text); }
  .cr-disclose-sum:hover { background: var(--surface-2); }

  /* ---- Dispatch board ---- */
  .trip-row:hover { background: var(--surface-2); }
  .trip-bus:hover { background: var(--bg-3); }
  /* A season's row opens something, so it behaves like every other row that
     does: surface lifts, the chevron leans toward the door. */
  .plan-card:hover { background: var(--surface-2); border-color: var(--accent); }
  .plan-card:hover .plan-name { color: var(--accent); }
  .plan-card:hover .plan-go { color: var(--accent); transform: translateX(2px); }
  .plan-back:hover { border-color: var(--accent); color: var(--accent); }
  .planq .plan-fold .req-group-head:hover { color: var(--text); }
  .planq .plan-arch .req-group-head:hover { color: var(--text-dim); }
  .tp-board-row:hover .tp-board-name { color: var(--accent); }
  .planday-pill:not(.sel):hover,
  .wd-pill:not(.sel):hover,
  .dur-chip:not(.sel):hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
  .run-dur:hover,
  .run-row-count .step:not(:disabled):hover { border-color: var(--accent); color: var(--accent); }
  .run-edit:hover, .slot-menu:hover { color: var(--accent); }
  /* An empty seat and its clash fix are red because the gap is red. */
  .slot-fill:hover { background: rgba(239, 68, 68, 0.12); }
  .slot-fix:hover { background: rgba(239, 68, 68, 0.22); }
  .menu-item:hover { background: var(--surface-2); border-color: var(--accent); }
  .menu-item.danger:hover { background: rgba(239, 68, 68, 0.1); border-color: var(--danger); }
  .cal-cell:not(.blank):hover { background: var(--surface-2); box-shadow: var(--shadow-control); }

  /* ---- The paperwork report ----
     The parts this screen borrows already answer a pointer — `.bstat` takes an
     accent edge, `.bchip` fills toward the brand, `.btn-ghost` darkens — so
     these are the two exceptions and the polish on top.

     The exception: a counter and a row already carry their group's colour, and
     hovering them toward BRAND orange would erase the one thing they exist to
     say. They answer in their own colour instead. */

  /* The counter lifts the way .cal-cell does, takes its group's colour on the
     edge (overriding .bstat's accent), and brings its label up to full text.
     A shadow rather than a repaint, because the number below is already red. */
  .rep-stat:not(.rep-zero):hover {
    border-color: var(--rep-c);
    box-shadow: var(--shadow-control);
    transform: translateY(-2px);
  }
  .rep-stat:not(.rep-zero):hover .bstat-l { color: var(--text); }
  .rep-stat.rep-zero:hover { border-color: var(--border); }

  /* The section head is already orange, so the RULE under it takes the group's
     colour and the count badge warms with it — the label can't get louder, and
     the underline is the part that's about this section. The chevron slides the
     way it is about to turn, the nudge .more-row and .mech-row use. */
  .repq .rep-ghead:hover { border-bottom-color: var(--rep-c); }
  .repq .rep-ghead:hover span { border-color: var(--rep-c); color: var(--rep-c); }
  .repq .rep-ghead:hover .rep-chev { color: var(--accent); transform: rotate(90deg) translateX(2px); }
  .repq .rep-group.open .rep-ghead:hover .rep-chev { transform: rotate(-90deg) translateX(-2px); }

  /* A row is a way into a driver's file, so it behaves like every other row in
     the app that opens something: the surface lifts a step, the chevron turns
     orange and leans toward the door. The dot grows into a proper bullet — the
     row's own status answering the pointer without moving the layout. */
  .rep-row:hover { background: var(--surface-2); }
  .rep-row:hover .rep-row-lead { color: var(--text); }
  .rep-row:hover .gap-go { color: var(--accent); transform: translateX(2px); }
  .rep-row:hover .rep-dot { transform: scale(1.55); opacity: 1; }

  .rep-more:hover { background: var(--accent-soft); }

  /* ---- Chat ---- */
  .chat-row:hover, .chat-pick:hover { background: var(--surface-2); }

  /* ---- Payroll ---- */
  .paycard-head:not(.static):hover,
  .payday-head:hover,
  .payrow-head:hover { background: var(--surface-2); }
  .tt-scope:hover { color: var(--accent); }

  /* ---- Billing ---- */
  .binv:hover { border-color: var(--accent); }
  .gpill:not(.sel):hover,
  .bchip:not(.sel):hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
  /* The department tabs have no fill to warm — they're words on a rule — so the
     hover is the word itself coming up to full strength. */
  .rd-cat:not(.sel):hover { color: var(--text); }

  /* ---- Driver checklist. The two answers hover toward the verdict they file,
     not toward the brand — this is the one screen where the colour IS the
     meaning of the button. ---- */
  .insp-group-ok:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
  .insp-btn-ok:hover { border-color: var(--success); color: var(--success); }
  .insp-btn-bad:hover { border-color: var(--danger); color: var(--danger); }
  .insp-vbtn:not(.insp-vbtn-bad):not(.active):hover { border-color: var(--success); color: var(--success); }
  .insp-vbtn-bad:not(.active):hover { border-color: var(--danger); color: var(--danger); }

  /* ---- Attendance. Every chip already wears its answer on its border, so the
     hover lifts rather than repaints; pressing beats hovering. ---- */
  .att-cchip:hover { transform: translateY(-1px); box-shadow: var(--shadow-control); }
  .att-cchip:hover:active { transform: scale(0.985); box-shadow: none; }
  .att-behind-day:hover { background: var(--surface-2); border-color: var(--muted); }
  .att-scope-confirm:hover { background: rgba(16, 185, 129, 0.22); }
  .att-choice:not(.on):hover,
  .att-reason:not(.on):hover { border-color: var(--accent); background: var(--accent-soft); color: var(--text); }

  /* ---- The info sheet's dismiss button (phone-width layout only) ---- */
  :root.info-sheet .info-pop-ok:hover { background: var(--bg-3); }
}


/* ============================================================================
   DRIVERS → APPLICATIONS

   Almost nothing, on purpose. The screen is `.disp-body.treq.repq` — the same
   panel, group heads and row recipe the outstanding-paperwork report is drawn
   from, because it is the same kind of thing: a list of work waiting on the
   office. `.appq` adds only what the report can't supply, which is the tones,
   since these are STATES and not deadlines: what's waiting is amber, what
   happened is green or grey, and brand orange stays off the ramp exactly as it
   does everywhere else.
   ============================================================================ */
.app-tone-new      { --rep-c: var(--warning); --rep-fade: 1; }
.app-tone-hired    { --rep-c: var(--success); --rep-fade: 1; }
.app-tone-declined { --rep-c: var(--muted);   --rep-fade: 0.7; }

/* "Not taken on" — an answer that has been given, not a status to act on. Grey
   rather than red: declining somebody is a finished piece of work, and the red
   pills on this app all mean "do something". */
.cpill-was { background: var(--bg-3); color: var(--text-dim); }

/* The link itself, spelled out under the counters. An office that can only
   press "Copy" has no way to check what it copied, or to read it down a phone. */
.app-link {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 9px 12px;
  border: 1px dashed var(--border); border-radius: 10px; background: var(--bg-2);
}
.app-link-l {
  font-size: 10.5px; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--accent);
}
.app-link-u {
  font-size: 12.5px; color: var(--text-dim); word-break: break-all;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ---- Inside the detail dialog ---- */
/* Auto-fit rather than a fixed two- or three-up: the same class carries a row
   of three short boxes (city / state / ZIP) and a row of two long ones, and
   drops to one column on a phone without a second class to remember. */
.app-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; }
.app-row > div { min-width: 0; }
/* The answers with no box to sit in — what they've driven, when they can work.
   Read-only by nature: they are what the applicant wrote, and the office's
   opinion of them belongs in the decision note. */
.app-block { margin-top: 12px; }
.app-block-l {
  display: block; font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-dim); margin-bottom: 2px;
}
.app-block-v { display: block; font-size: 14px; color: var(--text); white-space: pre-wrap; }
.app-note {
  margin: 10px 0 0; padding: 9px 12px; border-radius: 10px;
  background: var(--bg-2); font-size: 13px; color: var(--text-dim);
}
/* Four controls don't fit one line on a phone, and the important one must not
   be the one that wraps off the bottom. */
.app-actions { flex-wrap: wrap; }


/* ============================================================================
   MESSAGES → SENT FILES  (migration 0115)

   The same borrowing again, and for the third time it is the right answer: the
   screen is `.disp-body.treq.repq`, so the panel, the counters, the cut pills
   and the row recipe all come from the outstanding-paperwork report. `.sentq`
   adds the three tones and one row for the search box.

   THE TONES ARE OUTCOMES, not deadlines and not states of work: the mail
   landed, it didn't, or we never tried. Green / red / grey, and brand orange
   stays off the ramp exactly as it does on the other two screens.
   ============================================================================ */
.sent-tone-ok   { --rep-c: var(--success); --rep-fade: 1; }
.sent-tone-fail { --rep-c: var(--danger);  --rep-fade: 1; }
/* "Not sent" is the no-mail-key case. Grey rather than red: nothing went wrong,
   nothing was attempted, and there is nobody to chase about it. */
.sent-tone-none { --rep-c: var(--muted);   --rep-fade: 0.7; }

/* The search box sits between the counters and the cut pills — it narrows the
   same list they cut, so it belongs in the same block rather than up in the
   toolbar the fleet screens pin under the header. */
.sentq .sent-find { margin: 0 0 10px; }
.sentq .sent-find .search-input { width: 100%; }

/* A row's second line carries a full email address, which on a phone is longer
   than the line it's on. The report's rows elide their sub-line; here it wraps
   instead, because a truncated address is exactly the fact somebody opened this
   screen to read. */
.sentq .rep-row-sub { white-space: normal; overflow-wrap: anywhere; }

/* "Open the file" is an <a> so the browser can open the download in a tab, and
   an <a> inside a dialog still takes the link underline. Scoped through `.modal`
   because a bare `.btn-primary` (0,1,0) loses to `.modal a` (0,1,1). */
.modal .modal-actions a.btn-primary { text-decoration: none; }

/* ============================================================================
   FLEET → FLUIDS (migration 0118)

   Almost nothing, for the third time and on purpose. The screen is
   `.disp-body.treq.repq`, so the panel, the counters, the cut pills and the row
   recipe all come from the outstanding-paperwork report — this is another list
   of work waiting on somebody, and inventing a second visual language for it is
   how a screen ends up looking bolted on. `.fluidq` adds the tones and the one
   thing the report has no part for: a row with a BUTTON on the end of it.

   The tones are the report's own ramp, unchanged: red for a deadline that has
   already passed, amber for one about to, grey for something never on file,
   green for done. Amber says "deal with this" and red says "it has already
   happened" — the same line the compliance chips draw, and a fluid check is a
   deadline of exactly that kind. Brand orange stays out of it, as everywhere.

   The DOT and the PILL read the same rung by construction: the dot is --rep-c
   and the pill is chosen next to it in FLUID_GROUPS (src/fluid-checks.ts). A
   red pill beside an amber dot is a row disagreeing with itself, which is what
   the first cut of this screen did.
   ============================================================================ */
.fl-tone-overdue { --rep-c: var(--danger);  --rep-fade: 1; }
.fl-tone-due     { --rep-c: var(--warning); --rep-fade: 1; }
/* "Never checked" is grey and not red for the reason the report gives: both are
   true and only one of them is a thing to do this morning. */
.fl-tone-never   { --rep-c: var(--muted);   --rep-fade: 0.7; }
.fl-tone-ok      { --rep-c: var(--success); --rep-fade: 1; }

/* The row carries its own "Done", because the whole screen is thirty of these
   walked in one go and making every one of them a dialog is thirty dialogs. The
   report's row is a <button>; this one is a container holding two, so the inner
   one takes over the layout the row would otherwise have. */
.fluidq .fl-row { cursor: default; }
.fl-open {
  display: flex; align-items: center; gap: 10px;
  flex: 1; min-width: 0; padding: 0;
  background: none; border: 0; font: inherit; color: inherit;
  text-align: left; cursor: pointer;
}
.fl-done { flex: none; white-space: nowrap; }
.fl-done:disabled { opacity: .55; }

/* The morning reminder switched off, said on the screen it was switched off
   for. A switch nobody can see is a switch that stays where it was put — the
   same reason the office's paperwork digest announces its own off switch. Its
   own line rather than another clause in the blurb, because it is a STATE of
   the feature and not a description of it; muted, since nothing is wrong. */
.fl-off { display: block; margin-top: 4px; color: var(--muted); }

/* What's already been done to this bus, under the log form in its dialog. A
   list, not a table: it is read to answer "when was it last done and did
   anybody say anything", and both of those are one line. */
.fl-history { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px; }
.fl-hist-h {
  font-size: 11px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase;
  color: var(--accent); margin-bottom: 6px;
}
.fl-hist {
  display: flex; align-items: baseline; gap: 8px;
  padding: 5px 0; border-bottom: 1px solid var(--border); font-size: 12.5px;
}
.fl-hist:last-child { border-bottom: none; }
.fl-hist-d { flex: none; font-weight: 600; color: var(--text); }
.fl-hist-t { flex: 1; min-width: 0; color: var(--text-dim); overflow-wrap: anywhere; }
/* Removing a mis-logged check is a correction, not a routine action — it stays
   quiet until the pointer is on the line it belongs to. */
.fl-hist-x { flex: none; color: var(--text-dim); }

/* ============================================================
 * THE CLOCK (migration 0100)
 *
 * Two screens: the driver's punch button and the office's timesheet.
 *
 * The punch button is the largest control in the app, deliberately. It is
 * pressed with a gloved thumb, in the dark, standing beside a running bus, by
 * somebody who is already late — and it is the only screen in the driver's app
 * where a mis-tap costs money. Everything else on the screen is smaller than it
 * by a wide margin, so there is never a moment of looking for it.
 *
 * The colour is the STATE, not the action: green when you are off the clock and
 * about to go on, amber when you are on it. A driver glancing at the phone in
 * their pocket has to be able to answer "am I punched in" without reading, and
 * a button that only ever said "Punch" in one colour could not do that.
 * ============================================================ */
.clock-wrap { display: flex; flex-direction: column; gap: 14px; }

/* The forgot-to-punch-out warning. Loud, because a clock left running overnight
   is a day that pays nothing, and the driver is the one who can fix it fastest. */
.clock-stale {
  padding: 12px 14px; border-radius: 12px;
  background: color-mix(in srgb, var(--warning) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warning) 40%, transparent);
  color: var(--text); font-size: 13px; line-height: 1.45;
}

.clock-btn {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; width: 100%; min-height: 132px; padding: 22px 18px;
  border: none; border-radius: 20px; cursor: pointer;
  color: #fff; text-align: center;
  /* The press is the whole of the feedback on a phone that may never show the
     toast — the screen is back in a pocket by the time it lands. */
  transition: transform .12s ease, filter .12s ease;
}
.clock-btn:active { transform: scale(.985); filter: brightness(.94); }
.clock-btn:disabled { opacity: .6; cursor: default; }
.clock-btn-in  { background: linear-gradient(160deg, var(--success) 0%, color-mix(in srgb, var(--success) 72%, #000) 100%); }
.clock-btn-out { background: linear-gradient(160deg, var(--warning) 0%, color-mix(in srgb, var(--warning) 72%, #000) 100%); }
.clock-btn-verb { font-size: 27px; font-weight: 800; letter-spacing: -.01em; }
.clock-btn-sub { font-size: 13px; opacity: .92; }

/* Today and the period: the same card, because they are the same question asked
   at two zoom levels. */
.clock-today, .clock-period {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 12px 14px;
}
.clock-today-head, .clock-period-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; font-size: 14px; margin-bottom: 6px;
}
.clock-today-head b, .clock-period-head b { font-size: 16px; font-variant-numeric: tabular-nums; }

/* ONE BLOCK, with its clock reading under its paid figure. The pair is the
   whole reason a round-up rule doesn't generate a phone call every fortnight. */
.clock-block {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 10px;
  padding: 8px 0; border-top: 1px solid var(--border);
}
.clock-block:first-of-type { border-top: none; }
.clock-block-span { font-size: 14px; color: var(--text); font-variant-numeric: tabular-nums; }
.clock-block-num { text-align: right; font-variant-numeric: tabular-nums; }
.clock-block-num b { font-size: 15px; }
.clock-block-num small { display: block; font-size: 11px; }
.clock-block-gap, .clock-block-warn {
  grid-column: 1 / -1; font-size: 12px; color: var(--text-dim);
}
.clock-block-warn { color: var(--warning); }
.clock-block-open .clock-block-span { color: var(--warning); font-weight: 600; }

.clock-days { display: flex; flex-direction: column; margin-top: 8px; }
.clock-day {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 0; border-top: 1px solid var(--border); font-size: 13px;
}
.clock-day b { margin-left: auto; font-variant-numeric: tabular-nums; }
.clock-day-open b { color: var(--warning); }
.clock-day-flag { font-size: 11px; color: var(--warning); font-style: normal; }

/* The rules, in the driver's own words, at the bottom of their own screen. They
   are in the contract too; a contract is not something anybody re-reads on a
   Tuesday, and this is the one place the question actually comes up. */
.clock-rules {
  margin: 0; padding: 11px 13px; border-radius: 12px;
  background: var(--bg-2); color: var(--text-dim);
  font-size: 12.5px; line-height: 1.5;
}

/* ---- the office's timesheet ---- */
.ts-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px;
}
.ts-head-name { font-weight: 700; font-size: 15px; }
.ts-row { cursor: pointer; align-items: center; }
.ts-row-name { font-weight: 600; }
/* The spans are the scannable column: an office checking a morning reads the
   times, not the totals. Clipped rather than wrapped so one long day can't make
   its row three lines tall and break the rhythm of the list. */
.ts-row-blocks {
  color: var(--text-dim); font-size: 12.5px; font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ts-row-hours, .ts-row-amt { font-variant-numeric: tabular-nums; text-align: right; }
.ts-detail {
  padding: 8px 12px 12px; background: var(--bg-2);
  border-radius: 0 0 12px 12px; margin-bottom: 8px;
}
.ts-punches { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; }

/* A raw punch. Smaller and quieter than a block, because a block is the
   conclusion and this is the evidence underneath it — reached for in a dispute,
   not read every morning. */
.ts-punch {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12px; color: var(--text-dim);
}
.ts-punch-kind { font-weight: 700; color: var(--text); min-width: 44px; }
.ts-punch-at { font-variant-numeric: tabular-nums; color: var(--text); }
/* Struck out, never deleted — see migration 0074. It stays legible: a punch
   nobody can read is a punch that can't settle the argument it exists for. */
.ts-punch-void { opacity: .6; }
.ts-punch-void .ts-punch-at, .ts-punch-void .ts-punch-kind { text-decoration: line-through; }

/* ===================== The Hebrew year calendar =====================
 * Dispatch → School year, and a school's own Calendar. One stylesheet block
 * because it is one screen (see the header over viewYearCalendar in
 * src/client.ts) — the office's copy differs only by a filter across the top.
 *
 * THE GRID RUNS RIGHT TO LEFT, and it is the markup that says so (`dir="rtl"`
 * on .hcal-grid), not a pile of rules here. A CSS grid in an RTL box flows its
 * columns and its leading blanks from the right on its own, which is why the
 * week reads א׳ ב׳ ג׳ … from the right with no per-column arithmetic. The
 * ENGLISH inside a cell — run names, times, a civil date — is turned back to
 * LTR at .hcal-lines, so the two languages each read the way they are written
 * and the Hebrew date still sits at the head of the cell.
 *
 * ONE COLOUR PER HOLIDAY KIND, declared once as --hol-c on the chip, exactly
 * the way the outstanding-paperwork report handles --rep-c: everything
 * downstream reads it, so a Yom Tov is the same colour wherever it appears.
 * Brand orange is NOT in that ramp — orange is the brand, never a category —
 * and the only orange on this screen is today's date and the controls you press.
 * ==================================================================== */
/* ON :root AND NOT ON .hcal, which is where these started and was wrong: the
   day sheet is a dialog on document.body, outside the calendar entirely, and a
   custom property scoped to .hcal simply doesn't reach it — every Yom Tov in
   the sheet fell back to grey while the same chip in the grid behind it was
   violet. A palette a second reader can't see is a palette with one reader. */
:root {
  /* Categories, not statuses. Yom Tov and Chol Hamoed share a hue because they
     are the same festival at two intensities; a fast is deliberately quiet
     (it is a fact about the day, rarely about the buses); Erev is the one an
     office actually acts on, so it gets the brightest cool colour. */
  --hol-yomtov: #a855f7;
  --hol-cholhamoed: #c084fc;
  --hol-erev: #22d3ee;
  --hol-fast: #94a3b8;
  --hol-minor: #34d399;
  --hol-roshchodesh: var(--muted);
  /* A booking on the day. Trips keep the violet the dispatch calendar already
     uses for them (.cal-trip); a schedule change is the thing this screen exists
     for, so it takes the cooler, louder one. */
  --ycal-trip: #a855f7;
  --ycal-chg: #22d3ee;
  /* The ordinary morning. It needs a colour of its own now that a cell says how
     many runs are on it and nothing else about them — grey would have read as
     "nothing here" on the day the fleet is busiest — and it is deliberately the
     calmest of the four, because it is the one that is true of most days. */
  --ycal-run: #34d399;
}
:root[data-theme="light"] {
  --hol-yomtov: #7e22ce;
  --hol-cholhamoed: #9333ea;
  --hol-erev: #0e7490;
  --hol-fast: #64748b;
  --hol-minor: #15803d;
  --ycal-trip: #7e22ce;
  --ycal-chg: #0e7490;
  --ycal-run: #047857;
}

/* ---- the head ----
   Two groups, and each is one question: WHICH MONTH (the arrows and the title,
   which is also the search) and WHICH YEAR, with Today beside it. It used to be
   five loose controls including a browser `select`, which is the one piece of
   furniture on the page the app has no say over — see viewYearCalendar. */
.hcal-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 16px; flex-wrap: wrap; padding: 2px 0 10px;
}
.hcal-monthnav { display: flex; align-items: center; gap: 4px; min-width: 0; }
.hcal-nav {
  flex: none; width: 34px; height: 34px; display: grid; place-items: center;
  font-size: 22px; line-height: 1; padding: 0 0 3px;
  border: 1px solid transparent; border-radius: 10px;
  background: none; color: var(--text-dim); cursor: pointer;
  transition: background .14s ease, color .14s ease, border-color .14s ease;
}
.hcal-nav:hover { background: var(--surface-2); border-color: var(--border); color: var(--accent); }
.hcal-nav:active { background: var(--bg-3); }

/* The title is a button, so it has to look pressable without becoming a slab:
   no fill until it is hovered, and a caret that leans when it is. */
.hcal-titlebtn {
  display: block; min-width: 0; text-align: left;
  padding: 4px 12px; border: 1px solid transparent; border-radius: 12px;
  background: none; color: var(--text); cursor: pointer;
  transition: background .14s ease, border-color .14s ease;
}
.hcal-titlebtn:hover { background: var(--surface); border-color: var(--border); }
/* The month's own name, in Hebrew, is the heading of the page — everything else
   here is an annotation on it. */
.hcal-month { display: block; font-size: 24px; font-weight: 800; line-height: 1.2; }
.hcal-caret {
  font-size: 15px; line-height: 1; font-style: normal; color: var(--muted);
  vertical-align: 3px; margin-left: 3px; display: inline-block;
  transition: transform .14s ease, color .14s ease;
}
.hcal-titlebtn:hover .hcal-caret { color: var(--accent); transform: translateY(2px); }
.hcal-span { display: block; font-size: 12px; color: var(--text-dim); margin-top: 2px; }

.hcal-jump { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* One control, not three: − year + reads as a stepper and cannot be mistaken for
   three unrelated buttons that happen to be next to each other. */
.hcal-step {
  display: inline-flex; align-items: stretch;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); overflow: hidden;
}
.hcal-step button {
  border: none; background: none; color: var(--text-dim); cursor: pointer;
  font-size: 17px; font-weight: 700; line-height: 1; padding: 0 12px;
  transition: background .14s ease, color .14s ease;
}
.hcal-step button:hover { background: var(--accent-soft); color: var(--accent); }
.hcal-year {
  display: grid; place-items: center; min-width: 62px; padding: 6px 2px;
  font-weight: 700; font-size: 14px; text-align: center;
  border-inline: 1px solid var(--border);
}
.hcal-today {
  padding: 7px 14px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--text-dim);
  font-size: 12.5px; font-weight: 700; cursor: pointer;
  transition: background .14s ease, color .14s ease, border-color .14s ease;
}
.hcal-today:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* The office's department filter. "All departments" is a real chip that lights
   up rather than the absence of any other — see ycalDeptFilter. */
.hcal-depts { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 0 6px; }
.hcal-dept {
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: 1px;
  padding: 5px 11px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--text-dim);
  font-size: 12.5px; font-weight: 600; cursor: pointer; line-height: 1.25;
  transition: transform .14s ease, border-color .14s ease, box-shadow .14s ease, background .14s ease;
}
.hcal-dept:hover { border-color: var(--accent); color: var(--text); transform: translateY(-1px); box-shadow: var(--shadow-control); }
.hcal-dept.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.hcal-dept-c { font-size: 10px; font-weight: 500; color: var(--muted); }
.hcal-dept.on .hcal-dept-c { color: var(--accent); opacity: .8; }

/* The month in numbers, each one wearing the colour its days wear in the grid
   below — the strip and the cells are the same fact at two sizes. */
.hcal-sum { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; font-size: 12px; color: var(--text-dim); padding: 2px 0 10px; }
.hcal-sum-n { font-weight: 700; color: var(--text); padding-right: 4px; }
.hcal-s {
  --s-c: var(--muted);
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; line-height: 1.35;
  color: var(--s-c); background: color-mix(in srgb, var(--s-c) 12%, transparent);
}
.hcal-s i { width: 6px; height: 6px; border-radius: 999px; background: var(--s-c); flex: none; }
.hcal-s.s-off { --s-c: var(--hol-fast); }
.hcal-s.s-gap { --s-c: var(--danger); }
.hcal-s.s-chg { --s-c: var(--ycal-chg); }
.hcal-s.s-trip { --s-c: var(--ycal-trip); }

.hcal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.hcal-dow {
  text-align: center; font-size: 14px; font-weight: 700; color: var(--text-dim);
  padding: 4px 0 2px;
}
.hcal-dow.shabbos { color: var(--accent); }

/* A CELL IS A DAY'S HEADLINE, NOT ITS CONTENTS — see ycalTally in client.ts for
   why. `overflow: visible` is what lets the hover peek out of it, and is only
   safe because nothing long is drawn INSIDE any more: the two things that can
   run past the edge (a holiday chip, a run name in the peek) clip themselves. */
.hcal-cell {
  position: relative; min-height: 92px;
  display: flex; flex-direction: column; align-items: stretch; gap: 3px;
  padding: 5px 7px 6px;
  border: 1px solid var(--border); border-radius: 10px;
  --cell-bg: var(--surface);
  background: var(--cell-bg); color: var(--text);
  cursor: pointer; overflow: visible; text-align: right;
  transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}
/* The day lifts toward the finger and its edge takes the brand, the way every
   other openable row in the app behaves. The GROUND is left alone on purpose —
   Shabbos and Yom Tov are told by the surface colour, and warming it on hover
   would say the day had changed kind. */
.hcal-cell:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  box-shadow: var(--shadow-control);
  z-index: 3;
}
.hcal-cell:active { transform: translateY(0); box-shadow: none; }
.hcal-cell:focus-visible { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }
.hcal-cell.blank {
  background: none; border: none; cursor: default; min-height: 0;
  transform: none; box-shadow: none;
}
/* Shabbos and Yom Tov are the ground the month is read against, so they are a
   change of SURFACE rather than another chip in a cell already full of them.
   They set --cell-bg rather than `background` directly, because what is ON a day
   washes OVER what kind of day it is — see .hcal-cell.mark. */
.hcal-cell.shabbos { --cell-bg: var(--bg-2); }
.hcal-cell.yomtov { --cell-bg: color-mix(in srgb, var(--hol-yomtov) 12%, var(--surface)); }
.hcal-cell.closed { border-style: dashed; }
/* A day with nothing on it recedes. It is still legible — it is where the month
   is going next — but it should not compete with the days that need reading. */
.hcal-cell.bare { --cell-bg: color-mix(in srgb, var(--bg-2) 55%, var(--surface)); }
/* Dimmer, but not on hover and never on today: both of those rules are written
   later in the daytop block below and win on order, which is what keeps an empty
   today from painting orange on its own orange pill. */
.hcal-cell.bare .hcal-heb { color: var(--text-dim); }

.hcal-daytop { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.hcal-heb { font-size: 17px; font-weight: 800; line-height: 22px; transition: color .16s ease; }
.hcal-civ { font-size: 10.5px; color: var(--muted); direction: ltr; white-space: nowrap; transition: color .16s ease; }
/* The day's own colour where it has one, the brand where it doesn't — which is
   one declaration rather than a rule per tone, because --day-c is only set on a
   cell that is marked. */
.hcal-cell:hover .hcal-heb { color: var(--day-c, var(--accent)); }
.hcal-cell:hover .hcal-civ { color: var(--text-dim); }
.hcal-cell.today .hcal-heb {
  background: var(--accent); color: #fff; border-radius: 999px;
  padding: 0 8px; line-height: 22px;
}
/* Today is already wearing the brand, so hovering it must not paint the date
   the same colour as the pill it sits in. */
.hcal-cell.today:hover .hcal-heb { color: #fff; }

.hcal-hols { display: flex; flex-wrap: wrap; gap: 3px; }
/* Tinted, not outlined. An outline at 10.5px is a box with a colour somewhere on
   its edge; the fill is what makes the kind readable in a glance across thirty
   cells, and it is 14% so the Hebrew on it stays the thing you read. */
.hcal-hol {
  --hol-c: var(--muted);
  font-size: 10.5px; font-weight: 700; line-height: 1.55;
  padding: 0 5px; border-radius: 5px;
  color: var(--hol-c); border: 1px solid transparent;
  background: color-mix(in srgb, var(--hol-c) 14%, transparent);
  white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
/* "+2" — the holidays this cell isn't printing. It says there are more, in the
   quietest way available; which ones is what the sheet is for. */
.hcal-hol.more { background: none; color: var(--muted); padding: 0 2px; }
.hcal-hol.k-yomtov { --hol-c: var(--hol-yomtov); }
.hcal-hol.k-cholhamoed { --hol-c: var(--hol-cholhamoed); }
.hcal-hol.k-erev { --hol-c: var(--hol-erev); }
.hcal-hol.k-fast { --hol-c: var(--hol-fast); }
.hcal-hol.k-minor { --hol-c: var(--hol-minor); }
.hcal-hol.k-roshchodesh { --hol-c: var(--hol-roshchodesh); }

/* Why a day has no buses, in the office's own words — never inferred from a
   Yom Tov. A tinted chip rather than bare coloured text: at 10px a colour alone
   is barely a colour, and this is the one line on the cell that explains an
   empty day. */
.hcal-note {
  align-self: flex-start; max-width: 100%;
  font-size: 10px; font-weight: 700; line-height: 1.5;
  direction: ltr; text-align: left; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  padding: 0 5px; border-radius: 5px;
  color: var(--note-c); background: color-mix(in srgb, var(--note-c) 15%, transparent);
}
.hcal-note.dist { --note-c: var(--warning); }
.hcal-note.shut { --note-c: var(--hol-fast); }

/* ---- the counts along the foot of a cell ----
   The whole of what a closed cell says about its runs (see ycalTally). Each
   count carries its own colour on --t-c and everything in it reads that one
   property, the way the outstanding-paperwork report handles --rep-c: a change
   is the same cyan in the count, in the wash behind the day, in the peek and in
   the legend, by construction rather than by four rules agreeing. */
.hcal-tally {
  display: flex; flex-wrap: wrap; align-items: center; gap: 3px 7px;
  margin-top: auto; padding-top: 3px; direction: ltr;
}
.hcal-t {
  --t-c: var(--text-dim);
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 11px; font-weight: 700; line-height: 1.4; color: var(--t-c);
  white-space: nowrap; min-width: 0;
}
.hcal-t i {
  width: 6px; height: 6px; flex: none; border-radius: 999px; background: var(--t-c);
  transition: transform .16s ease;
}
.hcal-t-w { font-weight: 600; opacity: .85; overflow: hidden; text-overflow: ellipsis; }
.hcal-t.runs { --t-c: var(--ycal-run); }
.hcal-t.gap { --t-c: var(--danger); }
.hcal-t.chg { --t-c: var(--ycal-chg); }
.hcal-t.trip { --t-c: var(--ycal-trip); }
/* The school's own diary. Brand orange, because it is the one thing on the cell
   that is THEIRS rather than ours — every other count on the row is a fact about
   our buses. It is also why a note deliberately does not wash the cell: writing
   on your own calendar must not repaint a month. */
.hcal-t.note { --t-c: var(--accent); }
/* Earlier years is a MARK, not an event: no dot in the ramp, and it sits at the
   far end of the row so the day's own colours read as one group. */
.hcal-t.hist { --t-c: var(--muted); margin-left: auto; font-weight: 600; }
.hcal-cell:hover .hcal-t i { transform: scale(1.4); }

/* ---- the day's ground ----
   Layered so each layer answers one question: what KIND of day it is (Shabbos,
   Yom Tov) sets --cell-bg, and what is ON it washes over that. A wash is 7% of
   the event's own colour — enough to find across a month at arm's length, not
   enough to fight the text on it — and it deepens on hover, which is the cell
   answering in its own colour rather than turning brand orange. */
.hcal-cell.mark { background: color-mix(in srgb, var(--day-c) 7%, var(--cell-bg)); }
/* A day already wearing a colour deepens THAT one and takes it on its edge, the
   way a group's row does on the outstanding-paperwork report: warming it toward
   the brand instead would say the cyan day and the plain day beside it were the
   same kind of thing under the cursor. */
.hcal-cell.mark:hover {
  background: color-mix(in srgb, var(--day-c) 14%, var(--cell-bg));
  border-color: var(--day-c);
}
.hcal-cell.m-chg { --day-c: var(--ycal-chg); }
.hcal-cell.m-trip { --day-c: var(--ycal-trip); }
.hcal-cell.m-off { --day-c: var(--hol-fast); }
/* The same colour again as a strip along the top edge, because a 7% wash is
   invisible on a phone in daylight and a 3px line is not. Inset from the corners
   so it reads as part of the cell rather than as a second border. */
.hcal-cell.mark::before {
  content: ""; position: absolute; top: 0; inset-inline: 9px; height: 3px;
  border-radius: 0 0 4px 4px; background: var(--day-c);
  transition: opacity .16s ease;
  opacity: .75;
}
.hcal-cell.mark:hover::before { opacity: 1; }

/* ---- the lines, on hover ----
   English, so it turns back around. These are the run and booking lines the cell
   used to print permanently; they live in the peek now (see ycalPeek). */
.hcal-lines { display: flex; flex-direction: column; gap: 2px; direction: ltr; text-align: left; min-width: 0; }
.hcal-run { display: flex; align-items: baseline; gap: 5px; font-size: 11.5px; color: var(--text-dim); min-width: 0; }
.hcal-run b { color: var(--text); font-weight: 700; flex: none; }
/* Short-staffed marks the BUS COUNT, which is the number that's short — not the
   time, which is the same time it always was. */
.hcal-run.gap .hcal-run-b { color: var(--danger); }
.hcal-run-n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hcal-run-b { margin-left: auto; flex: none; font-weight: 700; color: var(--muted); }
.hcal-more { font-size: 11px; color: var(--muted); }
.hcal-chg { font-size: 11.5px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hcal-chg.dir { color: var(--ycal-chg); }
.hcal-chg.trip { color: var(--ycal-trip); }
/* The school's own line, first in the peek and in the brand's own colour — the
   same reason as .hcal-t.note above. */
.hcal-evt {
  font-size: 11.5px; font-weight: 700; color: var(--accent);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The peek itself: hidden at every width, and switched on only by the hover
   query near the foot of this block — a pointer that can hover, on a screen wide
   enough to land it. A phone taps straight through to the day sheet. */
.hcal-peek {
  position: absolute; top: calc(100% + 7px); left: 50%;
  z-index: 6; width: max-content; min-width: 190px; max-width: 260px;
  padding: 9px 11px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); box-shadow: var(--shadow);
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translate(-50%, -4px);
}
/* A day in the last row would push the page taller, so its peek goes up. */
.hcal-cell:nth-last-child(-n + 7) .hcal-peek { top: auto; bottom: calc(100% + 7px); }

.hcal-legend {
  display: flex; flex-wrap: wrap; gap: 12px; justify-content: center;
  padding: 12px 0 4px; font-size: 11.5px; color: var(--text-dim);
}
/* ROUND, because they are round in the cell — a square in the key for a circle
   on the day is the legend describing a screen that isn't there. */
.hcal-swatch { display: inline-block; width: 8px; height: 8px; border-radius: 999px; vertical-align: -1px; margin-right: 4px; background: currentColor; }
.hcal-swatch.d-runs { color: var(--ycal-run); }
.hcal-swatch.d-gap { color: var(--danger); }
.hcal-swatch.d-chg { color: var(--ycal-chg); }
.hcal-swatch.d-trip { color: var(--ycal-trip); }
.hcal-swatch.d-note { color: var(--accent); }

/* ---- go to a month ----
   The search behind the title. A list of months is a list of PLACES, so it is
   drawn as rows you land on and not as chips you toggle — and the row carries
   all three of a month's names at once, because the whole point is that somebody
   arrives knowing only one of them. */
.ycf-h { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ycf-h h3 { margin: 0; font-size: 17px; font-weight: 800; }
.ycf-list {
  margin-top: 12px; max-height: 46vh; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 12px; background: var(--bg-2);
}
.ycf-year {
  position: sticky; top: 0; z-index: 1;
  padding: 7px 12px 5px; background: var(--bg-2);
  font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em;
  color: var(--accent); border-bottom: 1px solid var(--border);
}
.ycf-year span { color: var(--muted); font-weight: 600; letter-spacing: 0; }
.ycf-row {
  display: flex; align-items: baseline; gap: 10px; width: 100%;
  padding: 9px 12px; border: none; border-bottom: 1px solid var(--border);
  background: none; color: var(--text); text-align: left; cursor: pointer;
  transition: background .12s ease, padding .12s ease;
}
.ycf-row:last-child { border-bottom: none; }
.ycf-row:hover { background: var(--surface); padding-left: 16px; }
.ycf-row.on { background: var(--accent-soft); }
.ycf-he { font-size: 15px; font-weight: 700; flex: none; }
.ycf-en { font-size: 12.5px; color: var(--text-dim); flex: 1 1 auto; min-width: 0; }
.ycf-civ { font-size: 11.5px; color: var(--muted); flex: none; }
.ycf-row.on .ycf-he, .ycf-row.on .ycf-en { color: var(--accent); }
.ycf-none { padding: 16px 12px; font-size: 12.5px; color: var(--text-dim); }
@media (prefers-reduced-motion: reduce) { .ycf-row { transition: none; } .ycf-row:hover { padding-left: 12px; } }

/* ---- one day, opened ---- */
/* Wider than the app's 420px sheet: this one holds a day's schedule AND six
   years behind it, and at 420px the years become one word per line. */
.modal:has(#ycal-day) { max-width: 620px; }
/* THE BAND ACROSS THE HEAD wears the day's own colour — the same --day-c the
   cell was wearing, so the sheet reads as that day opened rather than as a
   dialog that happened. It bleeds to the modal's edges (the modal's own 18px of
   padding, undone) because a tinted card floating inside a white one is two
   boxes saying one thing. */
.ycal-sheet { --day-c: var(--muted); }
.ycal-sheet.m-chg { --day-c: var(--ycal-chg); }
.ycal-sheet.m-trip { --day-c: var(--ycal-trip); }
.ycal-sheet.m-off { --day-c: var(--hol-fast); }
.ycal-hero {
  margin: -18px -18px 0; padding: 16px 18px 13px;
  border-radius: 13px 13px 0 0;
  border-bottom: 2px solid var(--day-c);
  background: color-mix(in srgb, var(--day-c) 9%, var(--modal-bg));
}
/* A plain day gets the band without the colour — the shape of the sheet must not
   change depending on whether anything happened. */
.ycal-sheet.m-plain .ycal-hero { background: var(--bg-2); border-bottom-color: var(--border); }
.ycal-hero-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.ycal-hero-when { min-width: 0; }
.ycal-sheet-heb { margin: 0; font-size: 22px; font-weight: 800; line-height: 1.2; }
.ycal-sheet-civ { font-size: 12.5px; color: var(--text-dim); margin-top: 3px; }
.ycal-sheet-hols { display: flex; flex-wrap: wrap; gap: 5px; margin: 10px 0 0; }

/* The counts, spelled out. The cell had 45px and said "●5"; the sheet has the
   room to say what the 5 is, and it is the first thing read after the date. */
.ycal-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.ycal-chip {
  --c-c: var(--muted);
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 700; line-height: 1.35;
  color: var(--c-c); background: color-mix(in srgb, var(--c-c) 13%, transparent);
}
.ycal-chip i { width: 6px; height: 6px; border-radius: 999px; background: var(--c-c); flex: none; }
.ycal-chip.c-runs { --c-c: var(--ycal-run); }
.ycal-chip.c-gap { --c-c: var(--danger); }
.ycal-chip.c-chg { --c-c: var(--ycal-chg); }
.ycal-chip.c-trip { --c-c: var(--ycal-trip); }
.ycal-chip.c-none { --c-c: var(--muted); }
/* A day the school has shut itself. The chip REPLACES the run count rather than
   sitting beside it: "0 runs" and "closed" are the same fact and only one of
   them says why. */
.ycal-chip.c-shut { --c-c: var(--hol-fast); }

/* ---- WHAT YOU CAN DO ABOUT THIS DAY ----
   Directly under the band, above everything the day merely reports, because the
   sheet is now a place you act from and not a report you read. A wrapping row of
   equal buttons rather than a toolbar: there are three or four of them, they are
   peers, and on a phone they should stack into two columns without anyone
   writing a second layout for it. */
.ycal-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.ycal-act {
  --act-c: var(--text);
  flex: 1 1 140px;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--bg-2);
  font-size: 13px; font-weight: 700; color: var(--act-c);
  cursor: pointer; transition: background .12s ease, border-color .12s ease, transform .12s ease;
}
.ycal-act span { font-size: 14px; line-height: 1; }
.ycal-act:hover { background: var(--surface); border-color: var(--act-c); transform: translateY(-1px); }
/* The one button that changes what happens on the road wears the colour that
   says so — everywhere else on this screen a status colour means a status, and
   this is the one control whose status IS the thing it does. */
.ycal-act.danger { --act-c: var(--danger); }
.ycal-act.undo { --act-c: var(--success); }
@media (prefers-reduced-motion: reduce) {
  .ycal-act { transition: none; }
  .ycal-act:hover { transform: none; }
}

/* Why there are no buses, in the office's own words. Tinted like every other
   statement on this screen rather than left as bare coloured text. */
.ycal-note {
  --note-c: var(--muted);
  font-size: 12.5px; font-weight: 600; margin-top: 10px;
  padding: 7px 11px; border-radius: 9px;
  color: var(--note-c); background: color-mix(in srgb, var(--note-c) 12%, transparent);
}
.ycal-note.dist { --note-c: var(--warning); }
.ycal-note.shut { --note-c: var(--hol-fast); }
/* A school's own closure. Same slate as a season marked off — it is the same
   piece of news — but it carries an Undo, so the line becomes a row. */
.ycal-note.off {
  --note-c: var(--hol-fast);
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.ycal-note.off > span { min-width: 0; }

/* ---- the school's own diary ---- */
.ycal-evts { display: flex; flex-direction: column; }
.ycal-evt {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
  padding: 7px 10px; border-bottom: 1px solid var(--border);
  border-left: 3px solid var(--accent);
}
.ycal-evt:last-child { border-bottom: none; }
.ycal-evt-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ycal-evt-t { font-size: 13px; font-weight: 700; }
.ycal-evt-n { font-size: 12.5px; color: var(--text-dim); }
.ycal-evt-by { font-size: 11px; color: var(--muted); }
.ycal-evt-acts { display: flex; gap: 2px; flex: none; }

/* The one line of prose on the sheet, and it is on the only control that takes
   buses off the road. */
.ycal-close-say { font-size: 12px; color: var(--text-dim); margin: 0 0 4px; }
.modal .ycal-noteform, .modal .ycal-closeform { margin-top: 12px; }

/* ---- the years, folded ---- */
/* The fold reuses the section heading's own type so it reads as a heading you
   can press, not as a control bolted under one. */
.ycal-fold {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 0 0 5px; border: none; border-bottom: 1px solid var(--border);
  background: none; cursor: pointer; text-align: left;
  font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em;
  color: var(--accent);
  transition: border-color .12s ease;
}
.ycal-fold:hover { border-bottom-color: var(--accent); }
.ycal-fold-l { flex: 1 1 auto; min-width: 0; }
/* The chevron leans the way it is about to rotate, the same gesture every other
   openable row in this app makes. */
.ycal-fold-c {
  flex: none; font-size: 15px; line-height: 1; color: var(--muted);
  transition: transform .15s ease, color .12s ease;
}
.ycal-fold:hover .ycal-fold-c { color: var(--accent); }
.ycal-fold.open .ycal-fold-c { transform: rotate(90deg); color: var(--accent); }
.ycal-fold-body { margin-top: 8px; }
.ycal-hist-add { margin-bottom: 2px; }
@media (prefers-reduced-motion: reduce) {
  .ycal-fold, .ycal-fold-c { transition: none; }
}

.ycal-sec { margin-top: 20px; }
.ycal-sec-h {
  display: flex; align-items: center; gap: 8px;
  font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em;
  color: var(--accent); padding-bottom: 5px; border-bottom: 1px solid var(--border); margin-bottom: 6px;
}
/* The count belongs to the heading and sits against it, not out at the far end
   of a rule — "RUNS 2" is one phrase. */
.ycal-sec-n {
  font-size: 11px; font-weight: 700; letter-spacing: 0;
  color: var(--text-dim); background: var(--bg-2);
  padding: 1px 7px; border-radius: 999px;
}
.ycal-runs { display: flex; flex-direction: column; }
/* One row recipe for a run, a change and a trip: a time, a name and a number,
   with a rule between them and a bar of the row's own colour down the side. */
.ycal-run {
  --row-c: var(--text-dim);
  display: flex; align-items: baseline; gap: 10px;
  font-size: 13px; padding: 7px 10px;
  border-bottom: 1px solid var(--border);
  border-left: 3px solid var(--row-c);
}
.ycal-run:last-child { border-bottom: none; }
.ycal-run.t-runs { --row-c: var(--ycal-run); }
.ycal-run.t-runs.short { --row-c: var(--danger); }
.ycal-run.t-chg { --row-c: var(--ycal-chg); }
.ycal-run.t-trip { --row-c: var(--ycal-trip); }
.ycal-run-t { flex: 0 0 74px; font-weight: 700; }
.ycal-run-n { flex: 1 1 auto; min-width: 0; }
.ycal-run-b { flex: none; font-size: 11.5px; color: var(--text-dim); }
/* Short-staffed marks the number that is short, here as on the grid. */
.ycal-run-b b { color: var(--danger); }

/* ---- what happened in earlier years ---- */
.ych-loading, .ych-empty { font-size: 12.5px; color: var(--text-dim); padding: 6px 0; }
.ych-year { padding: 8px 0; border-bottom: 1px solid var(--border); }
.ych-year:last-child { border-bottom: none; }
.ych-year.none { opacity: .5; }
.ych-year-h { display: flex; align-items: baseline; gap: 8px; }
.ych-year-n { font-size: 15px; font-weight: 800; }
.ych-year-d { font-size: 11.5px; color: var(--muted); }
.ych-blank { font-size: 12px; color: var(--muted); padding: 2px 0 0; }

.ych-row {
  --ych-c: var(--muted);
  display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
  margin-top: 6px; padding: 6px 10px;
  border-left: 3px solid var(--ych-c); border-radius: 8px; background: var(--bg-2);
}
.ych-row.t-off { --ych-c: var(--hol-fast); }
.ych-row.t-chg { --ych-c: var(--ycal-chg); }
.ych-row.t-add { --ych-c: var(--ycal-trip); }
.ych-row.t-ok { --ych-c: var(--success); }
.ych-row.t-note { --ych-c: var(--muted); }
/* A booking the app itself holds, not somebody's recollection. Brand orange is
   deliberately not used here — it's a source, not a status. */
.ych-row.t-rec { --ych-c: var(--text-dim); background: var(--surface); }
.ych-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ych-kind { font-size: 13.5px; font-weight: 700; color: var(--ych-c); }
.ych-bits { font-size: 12.5px; color: var(--text); }
.ych-note { font-size: 12.5px; color: var(--text-dim); }
.ych-by { font-size: 11px; color: var(--muted); }

.ych-add { margin: 4px 0 14px; padding: 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-2); }
.ych-add-h { font-size: 13px; font-weight: 700; margin-bottom: 4px; }
.ych-add-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 12px; }
.ych-f { min-width: 0; }
.ych-f.wide { grid-column: 1 / -1; }
/* The sheet's own field rules are (0,1,1) and beat a single class, so anything
   that has to override them is scoped through .modal as well — see the note by
   .modal input. */
.modal .ych-add { --notch-bg: var(--bg-2); }
.modal .ych-f label { margin-top: 14px; }

/* THE ONE SCREEN THAT INVERTS THIS FILE'S DIRECTION, and it is worth saying why.
   Everything else here is drawn for a phone and enriched at 1024px. A month grid
   is seven columns whatever the width, so on a phone each cell is about 45px
   across — a run name does not go in it and never will. But a month that says
   NOTHING on a phone is a month nobody checks on the way out of the building.
   So the count keeps its dot and drops its WORD (see ycalTally): "●5" is the
   same fact as "● 5 runs" and it fits in a thumb's width. */
@media (max-width: 719px) {
  .hcal-grid { gap: 3px; }
  .hcal-cell { min-height: 62px; padding: 3px 4px 4px; border-radius: 8px; }
  .hcal-heb { font-size: 14px; line-height: 18px; }
  .hcal-cell.today .hcal-heb { padding: 0 6px; line-height: 18px; }
  .hcal-civ { display: none; }
  .hcal-t-w { display: none; }
  .hcal-tally { gap: 2px 5px; padding-top: 2px; }
  .hcal-t { font-size: 10px; gap: 2px; }
  .hcal-t i { width: 5px; height: 5px; }
  .hcal-cell.mark::before { inset-inline: 6px; height: 2px; }
  .hcal-note { font-size: 9px; padding: 0 3px; }
  /* THE YOM TOV STAYS READABLE, because it is the reason to open a Hebrew
     calendar at all. A 45px cell cannot hold "ערב ראש השנה" inside a bordered
     chip, and clipping it to "ערב…" is worse than not printing it — so on a
     phone the chip loses its box and becomes two lines of small coloured Hebrew,
     with the colour still carrying the kind. Only the FIRST one: a day that is
     both Rosh Chodesh and Chanukah would otherwise take the row to four lines
     and push the end of the month off the screen. */
  .hcal-hols { flex-wrap: wrap; }
  .hcal-hol {
    font-size: 9px; line-height: 1.25; padding: 0; border: none; background: none;
    white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  .hcal-hol:not(:first-child) { display: none; }
  .hcal-month { font-size: 20px; }
  .hcal-dow { font-size: 12px; }
}

/* THE PEEK, and the two things that have to be true before it is drawn: a
   pointer that can HOVER (a touch device fires a hover on tap and would flash a
   card over the sheet it just opened) and a screen with room either side of a
   cell for it to land in. Everywhere else the day sheet is the answer. */
@media (hover: hover) and (min-width: 720px) {
  .hcal-cell:hover .hcal-peek {
    opacity: 1; visibility: visible; transform: translate(-50%, 0);
    transition: opacity .16s ease .16s, transform .16s ease .16s, visibility 0s .16s;
  }
}

/* The month still colours, lifts nothing and slides nothing. */
@media (prefers-reduced-motion: reduce) {
  .hcal-cell, .hcal-cell::before, .hcal-heb, .hcal-civ, .hcal-t i, .hcal-peek, .hcal-dept { transition: none; }
  .hcal-cell:hover, .hcal-dept:hover { transform: none; }
  .hcal-cell:hover .hcal-t i { transform: none; }
  .hcal-cell:hover .hcal-peek { transform: translate(-50%, 0); transition: none; }
}

/* ============================================================
 * DESKTOP
 *
 * Everything above is built for a phone in one hand, and it should stay that
 * way — it's the shape the drivers and the janitor actually use. But the office
 * works at a desk on a wide screen, where the same layout is a tall thin column
 * of content with a metre of grey either side and a thumb-sized tab bar at the
 * bottom of a 27" monitor.
 *
 * So at 1024px the SAME markup becomes a desktop app: the tab bar stands up
 * into a left rail with room for every section at once (no "More" — that only
 * exists because a phone has five slots), the content gets real width, the
 * subsection tabs stop being stretched to the full window, and the floating
 * action button becomes a labelled button where a mouse expects one.
 *
 * No JS branches on width. One markup, two shapes.
 * ============================================================ */
@media (min-width: 1024px) {
  :root {
    --rail: 252px;
    /* .topbar-app measured: 14+14 padding + 46px logo + 1px border. */
    --topbar-h: 75px;
  }

  body { padding-bottom: 0; padding-left: var(--rail); }
  /* No rail on the auth screens, so no room reserved for one — otherwise the
     card centres in a column 252px narrower than the window and reads as
     off-centre to the right. */
  body:has(.auth-wrap) { padding-left: 0; }

  /* The bar spans the whole window and the rail hangs below it, so the brand
     keeps its place and the rail needs no second copy of it. Fixed rather than
     sticky: sticky would be inset by the body's left padding and leave a hole
     in the corner above the rail. */
  /* The header spans a whole monitor here, so the office's mark gets the room
     a wordmark actually needs. Height is untouched — --topbar-h above is
     measured from the 46px marks. */
  .topbar-app .brand-logo-org { max-width: 180px; }

  .topbar-app {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    padding-left: 24px;
    padding-right: 68px;
  }
  /* The school's header pads its rows instead of itself, so the desk's wider
     gutter has to be restated on them. The tinted row still runs to both edges
     of the window — it is the header, not a column. */
  .topbar-app.topbar-acct-on { padding: 0; }
  .topbar-acct-on .tbar-row { padding-left: 24px; padding-right: 68px; }
  .topbar-acct-on .tacct { padding: 0 24px; }
  main { padding-top: calc(var(--topbar-h) + 22px); }
  /* The tab bar runs to the edges of a column that's padded 28px here, not 16,
     and butts the header past a top padding of 22px rather than 16. */
  .disp-tabs { margin-left: -28px; margin-right: -28px; padding-left: 28px; padding-right: 28px; }
  main > .disp-wrap:first-child > .disp-tabs { margin-top: -22px; }
  /* Past main's max-width the column stops short of the window, and a header row
     that stops short of the window isn't a header row. This carries its colour
     on to the right edge; body's own `overflow-x: hidden` (line ~164), which the
     root propagates to the viewport, is what keeps the overhang from turning
     into a sideways scroll. Don't "fix" that by clipping on `html` instead —
     that stops the propagation, makes BODY the scroll container, and every
     sticky bar in the app silently stops sticking. */
  .disp-tabs::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 100%;
    width: 100vw;
    background: var(--chrome-bg);
    box-shadow: inset 0 -1px 0 var(--chrome-border);
  }
  .tabs { top: var(--topbar-h); }

  .bottom-nav {
    top: var(--topbar-h);
    right: auto;
    bottom: 0;
    width: var(--rail);
    flex-direction: column;
    justify-content: flex-start;
    align-items: stretch;
    gap: 2px;
    padding: 14px 12px 20px;
    overflow-y: auto;
    border-top: none;
    border-right: 1px solid var(--chrome-border);
  }
  /* The rail has room for every section, so the index — which exists only
     because a phone's bar has five slots — isn't shown. */
  .nav-item[data-bar="0"] { display: flex; }
  .nav-item[data-nav="__index"] { display: none; }

  /* A row, not a stacked icon-over-label: the rail is read down the left edge
     like a list of places, which is what it is. */
  .nav-item {
    flex: 0 0 auto;
    max-width: none;
    flex-direction: row;
    justify-content: flex-start;
    gap: 12px;
    padding: 9px 12px;
    border-radius: 10px;
    transition: background 0.13s ease;
  }
  /* On a phone the highlight is a bubble behind the icon because there's no row
     to fill. Here there is, so the whole row lights up and the bubble goes. */
  .nav-item .ni-icon { font-size: 19px; padding: 0; width: 24px; border-radius: 0; background: none !important; }
  .nav-item .ni-label { font-size: 14px; font-weight: 600; letter-spacing: 0; }
  .nav-item.active { background: var(--chrome-active-bg); }
  .nav-item.active .ni-label { color: var(--accent); font-weight: 700; }
  .nav-item:not(.active):hover { background: var(--bg-3); }

  /* In the rail the whole row is the tab, so the row is what lifts when it's
     held — the same swap the active state makes here. Dragging is up and down
     rather than side to side, and nothing in the JS had to be told: the axis
     comes off this rail's own flex-direction. */
  .nav-item.dragging {
    background: color-mix(in srgb, var(--accent) 24%, transparent);
    outline: 2px solid var(--accent);
    outline-offset: -2px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
    cursor: grabbing;
  }
  .nav-item.dragging .ni-icon { background: none; transform: none; box-shadow: none; }

  /* The count sits at the end of the row, where a list puts its total. */
  .nav-item .ni-badge {
    position: static;
    margin-left: auto;
    box-shadow: none;
    flex-shrink: 0;
  }

  /* Real width for the work. Left-aligned rather than centred in the remaining
     space — a column that drifts right as the window grows is harder to read
     than one that starts where the rail ends. */
  main { max-width: 1280px; margin: 0; padding-right: 28px; padding-left: 28px; padding-bottom: 40px; }

  /* ...BUT NOT FOR EVERY ROLE. 1280px is the right answer for a board, a
     fortnight of payroll or thirty buses in a grid — screens whose content is
     genuinely two-dimensional. A school's app is a list of their own bookings
     and one form: stretched to a metre it becomes a ribbon of white cards with
     a title at the far left, a status pill at the far right and a lake of grey
     between them, and the eye has to travel the whole way to read one row. The
     same markup, so the difference can't come from the HTML — hence the role on
     the body (see render()). A driver's app is the same shape and gets the same
     treatment; the office keeps the width it needs.

     CENTRED, unlike the office's. The office's column is left-aligned on
     purpose — it fills most of the window, so starting it where the rail ends
     keeps it from drifting as the window grows. A 900px column in a 1400px
     window is a different case: pinned left it reads as a page that has slipped
     off to one side with a field of empty grey beside it, which is exactly what
     it looked like. Centred in the room the rail leaves, it reads as the page. */
  body[data-role="school"] main,
  body[data-role="driver"] main {
    max-width: 900px; margin-left: auto; margin-right: auto;
  }

  /* Five tabs stretched across 1300px are five buttons with 200px of dead space
     each. At this width every capsule takes the room it needs and no more; the
     runs stay together at the left, one after the other, rather than being flung
     to opposite ends of a bar nobody's eye crosses. */
  .disp-segs > .disp-seg { flex: 0 0 auto; max-width: 100%; }
  .disp-seg { justify-content: flex-start; }
  .disp-tab { flex: 0 0 auto; padding: 8px 18px; font-size: 13.5px; }
  .sub-run-tag { padding: 0 12px 0 8px; }

  /* A "+" floating over the bottom-right corner is a thumb target. With a mouse
     it should say what it does. */
  .fab {
    bottom: 26px;
    right: 30px;
    width: auto;
    height: auto;
    padding: 12px 20px 12px 16px;
    border-radius: 999px;
    font-size: 15px;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    gap: 8px;
  }
  .fab::after { content: attr(title); font-size: 14.5px; }

  /* More columns, now that there are columns to have. */
  /* Three buses across 1280px is a 410px-wide bus — a photo blown up past the
     detail it has. Five fit the width; the overlays are sized in cqw so the
     windshield text and number chips scale with the tile rather than needing
     their own rules here. */
  .bus-icon-grid { grid-template-columns: repeat(5, 1fr); }
  .bus-list { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
  .mech-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
  /* No .pfp-grid rule here: the driver card is the same row card at every width
     and its auto-fill minimum already gives four columns at this one. */
  .kpi-grid { grid-template-columns: repeat(4, 1fr); }
  .due-grid { grid-template-columns: 1fr 1fr; }
  /* Three, not the four that fit: a settings row is a title and a sentence
     explaining it, and at a quarter of the width the sentence wraps to three
     lines and stops being scannable. */
  .admin-menu { grid-template-columns: repeat(3, 1fr); }

  /* The section index is a directory; at this width it reads as two columns. */
  .more-group { columns: 2; column-gap: 10px; }
  .more-group .more-row { break-inside: avoid; }

  /* Sheets can afford to be bigger than a phone's 420px. */
  .modal { max-width: 560px; }
  .modal:has(.driver-detail) { max-width: 900px; }
  .modal:has(#ycal-day) { max-width: 760px; }

  /* THE YEAR CALENDAR TAKES THE ROOM, whoever is looking at it.
     A school's app is capped at 900px above, and that is right for a list of
     bookings — but this screen is a seven-column month, which is the one shape
     in a school's app that is genuinely two-dimensional. At 900px its cells are
     120px wide and the run times wrap; the whole reason it exists is being able
     to see a year's shape at a glance. `:has` on the body, because the same
     markup serves every role and the width has to follow the SCREEN and not the
     person looking at it. */
  body:has(.hcal) main { max-width: 1440px; }

  /* Room for what a cell actually holds: a Hebrew date, a Yom Tov and a row of
     counts. It is SHORTER than it was — the cell used to reserve four run lines
     it no longer prints, and a month of tall half-empty boxes is the same
     scrolling the lists caused, without even the lists. */
  .hcal-grid { gap: 7px; }
  .hcal-cell { min-height: 104px; padding: 7px 9px 8px; }
  .hcal-heb { font-size: 19px; }
  .hcal-civ { font-size: 11px; }
  .hcal-t { font-size: 12px; }
  .hcal-t i { width: 7px; height: 7px; }
  .hcal-hol { font-size: 11.5px; }
  .hcal-note { font-size: 11px; }
  .hcal-month { font-size: 27px; }
  .hcal-dow { font-size: 15px; padding: 6px 0 4px; }
}

/* A wide desk: let the content breathe further, but stop before lines get too
   long to track across. The single-column roles don't grow — a list of bookings
   is no more readable at 1480px than at 780px, it is only further apart. */
@media (min-width: 1600px) {
  main { max-width: 1480px; }
  body[data-role="school"] main,
  body[data-role="driver"] main { max-width: 780px; }
  /* ...except the month grid, for the reason given in the 1024px block: it is
     the one screen in those apps that is two-dimensional, and 780px of it is a
     calendar with the year squeezed out of it. Restated here because this query
     comes after that one and would otherwise win. */
  body:has(.hcal) main { max-width: 1480px; }
}
