/* 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;
}

/* THE APP'S TWO TEXT FACES, one per script, self-hosted for the same reason
   as Sora.

   Hebrew is Frank Ruhl Libre — the open cut of the Frank-Rühl face the
   ArtScroll books are set in, which is what the office reads all day and what
   a name looks like to them. It is listed FIRST in the body stack on purpose:
   its unicode-range is the Hebrew block alone, so a Hebrew letter takes it and
   every other character falls straight through to the Latin face. That is
   what makes one stack serve a name written in two scripts on one line with
   no class on either half. It carries a little more size than Latin at the
   same point (.heb bumps it) because a serif Hebrew sits low on the line.

   Latin is Inter — the Helvetica-shaped UI face — with Helvetica Neue,
   Helvetica and Arial behind it for a browser that can't load the file. */
@font-face {
  font-family: 'Frank Ruhl Libre';
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url('/static/fonts/frank-ruhl-libre-hebrew-var.woff2') format('woff2-variations');
  unicode-range: U+0590-05FF, U+FB1D-FB4F;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/static/fonts/inter-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+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);
  /* Our own words in a conversation. A tint of the brand, not the brand — a
     solid orange slab per message made every reply shout, and a screen of them
     was a wall. Text stays --text on it in both themes. */
  --bubble-mine: color-mix(in srgb, var(--brand-600) 34%, var(--surface-2));
  --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);
  --bubble-mine: color-mix(in srgb, var(--brand-600) 16%, var(--surface));
  --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: "Frank Ruhl Libre", Inter, "Helvetica Neue", Helvetica, 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; }

/* ONE GRID, NOT SEVEN ROWS OF LABELLED TICKS. The channel words used to be
   repeated on every row with a 15px tick in front of each: the noisiest way to
   ask the question, and the smallest thing on the screen to aim at. The words
   are headings now, said once, and the rows carry only the answers — lined up
   in columns, so reading DOWN a column says who emails you and reading ALONG a
   row says how you're told about one thing.

   The whole thing is a single grid so those columns line up across the group
   headings between them; the groups and rows are `display: contents` and their
   children are the real grid items. --notif-cols is the channel count, set on
   the element by the renderer, since the columns are read off what this role
   is actually sent. The last track is Off, which is narrower because it's an
   action rather than a channel. */
.notif-matrix {
  display: grid;
  grid-template-columns: minmax(0, 1fr) repeat(var(--notif-cols, 3), var(--notif-cw, 52px)) var(--notif-ow, 42px);
  align-items: center;
}
.notif-mhead, .notif-mhead-spacer {
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--muted); text-align: center; padding-bottom: 6px;
}

/* 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-pref, .notif-chs { display: contents; }
.notif-group-head {
  grid-column: 1 / -1;
  font-size: 11px; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--accent); margin: 12px 0 2px;
}
.notif-matrix > .notif-group:first-of-type .notif-group-head { margin-top: 2px; }

/* The rule between rows is drawn per CELL rather than on the row, because the
   row itself has no box to put a border on. */
.notif-pref-label { font-weight: 600; font-size: 14px; padding: 9px 10px 9px 0; }
.notif-pref + .notif-pref > .notif-pref-label,
.notif-pref + .notif-pref > .notif-chs > * { border-top: 1px solid var(--border); }

/* A cell is a big blank target with a small mark in it: the tile says on or
   off, the 40px cell around it is what a thumb actually hits. */
.notif-cell {
  display: flex; align-items: center; justify-content: center;
  min-height: 40px; border: 0; background: none; padding: 0; cursor: pointer;
}
.notif-tile {
  width: 26px; height: 26px; border-radius: 8px; flex-shrink: 0;
  border: 1.5px solid var(--border); background: var(--surface);
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.notif-cell:hover .notif-tile { border-color: var(--muted); }
.notif-cell.on .notif-tile {
  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 26px 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: 18px 18px; background-position: center; background-repeat: no-repeat;
}
.notif-cell.on:hover .notif-tile { border-color: var(--accent); transform: scale(1.06); }
/* Blocked (no mobile number) is a dashed outline and no fill — visibly a
   control that isn't available, not an empty answer. The line under the grid
   says why; a title attribute can't say it to a phone. */
.notif-cell.blocked { cursor: not-allowed; }
.notif-cell.blocked .notif-tile {
  /* Outline only, and the dash carries it: a dimmed FILL reads as a hole in the
     grid on a dark background, where the tile and the card are the same colour. */
  background: none; border-style: dashed; border-color: var(--muted); opacity: 0.5;
}
.notif-cell.blocked:hover .notif-tile { border-color: var(--muted); }
/* A channel this category doesn't have at all: nothing to answer, so nothing
   is drawn. The cell still holds the column open. */
.notif-cell.na { min-height: 40px; }

/* Off is an action, not a fourth channel, so it stays plain text under a blank
   heading — and lit it goes grey rather than brand-orange, since orange in
   this grid means "you'll be told", which is the one thing it must never say. */
.notif-off {
  border: 0; background: none; padding: 0; cursor: pointer; min-height: 40px;
  font-size: 12.5px; font-weight: 550; color: var(--muted); opacity: 0.75;
}
.notif-off:hover { opacity: 1; }
.notif-off.on { opacity: 1; font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }

.notif-foot { margin-top: 10px; font-size: 12px; color: var(--muted); }

@media (max-width: 430px) {
  .notif-matrix { --notif-cw: 44px; --notif-ow: 36px; }
  .notif-pref-label { font-size: 13.5px; }
}
@media (prefers-reduced-motion: reduce) {
  .notif-tile { transition: none; }
  .notif-cell.on:hover .notif-tile { transform: none; }
}

/* 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; }
/* A pill that opens something (the text-consent pill → the record on Info):
   same shape as its neighbours, a hand cursor and a lift are the whole tell. */
.cpill-btn { cursor: pointer; font-family: inherit; line-height: inherit; }
.cpill-btn:hover { filter: brightness(1.12); }

/* 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;
}

/* 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; }
/* The select is in this list because a settings field can be a CHOICE as well
   as a value — the clinic authorization's usual location is one. Left at the
   browser default a select is a shorter box than the inputs beside it, and the
   notched label, which is positioned by a fixed negative margin, then lands
   across its text instead of on its border. */
.org-field input,
.org-field select {
  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,
.org-field select: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); }

/* Rocket Fleet's sign-in rocket (rocketFlightSvg in client.ts). It flies in
   along its own heading — from the lower left, as the 45° rocket points up
   and right — and lands by ~1.1s, when the wordmark and form above take over.
   The exhaust is the ARRIVAL only (the owner, 2026-10-11): the puffs play once
   during the flight and the flame goes out as it lands, so the page then sits
   still. Offsets in the puffs are in the rocket's own frame, so +y is
   straight out of the nozzle. */
.rf-flight .rf-body { fill: var(--accent); }
/* --rf-t is how far into the flight this paint is (the header sets it, since
   it is redrawn on every render); every delay below is its own less that. */
.rf-flight .rf-flame {
  fill: #fb923c; transform-box: fill-box; transform-origin: 50% 0;
  animation: rf-flicker 0.16s ease-in-out 6 alternate, rf-out 0.35s ease-in 0.8s forwards;
  animation-delay: calc(0s - var(--rf-t, 0ms)), calc(0.8s - var(--rf-t, 0ms));
}
.rf-flight .rf-fly { transform-box: view-box; animation: rf-fly-in 1.05s cubic-bezier(0.16, 0.85, 0.28, 1) both; animation-delay: calc(0s - var(--rf-t, 0ms)); }
.rf-flight .rf-puff { opacity: 0; transform-box: fill-box; transform-origin: center; animation: rf-puff 0.9s ease-out both; }
.rf-flight .rf-puff-1 { fill: #fb923c; animation-delay: calc(0s - var(--rf-t, 0ms)); }
.rf-flight .rf-puff-2 { fill: #fdba74; animation-delay: calc(0.14s - var(--rf-t, 0ms)); --rf-drift: -2.5px; }
.rf-flight .rf-puff-3 { fill: #fed7aa; animation-delay: calc(0.28s - var(--rf-t, 0ms)); --rf-drift: 2.5px; }
.rf-flight .rf-puff-4 { fill: #fdba74; animation-delay: calc(0.42s - var(--rf-t, 0ms)); --rf-drift: -1px; }
@keyframes rf-fly-in {
  0%   { opacity: 0; transform: translate(-46px, 46px) scale(0.7); }
  35%  { opacity: 1; }
  100% { opacity: 1; transform: translate(0, 0) scale(1); }
}
@keyframes rf-flicker {
  from { transform: scaleY(0.78); }
  to   { transform: scaleY(1.12); }
}
@keyframes rf-out { to { opacity: 0; } }
/* The frame IS the exhaust: as the rocket lands, the trail leaves the nozzle
   (the circle is rotated so its path starts at the lower left, where the
   nozzle points) and sweeps round until it closes into a ring. */
.rf-flight .rf-ring {
  fill: none; stroke: var(--accent); stroke-width: 4.5; stroke-linecap: round;
  stroke-dasharray: 100; stroke-dashoffset: 100;
  animation: rf-ring 0.85s cubic-bezier(0.45, 0, 0.2, 1) forwards;
  animation-delay: calc(0.7s - var(--rf-t, 0ms));
}
/* When the flame goes out, one dot of exhaust stays behind the nozzle. */
.rf-flight .rf-dot {
  fill: #fb923c; opacity: 0; transform-box: fill-box; transform-origin: center;
  animation: rf-dot-in 0.35s cubic-bezier(0.2, 1.4, 0.4, 1) forwards;
  animation-delay: calc(0.95s - var(--rf-t, 0ms));
}
@keyframes rf-dot-in {
  from { opacity: 0; transform: scale(0.3); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes rf-ring {
  0%   { stroke-dashoffset: 100; stroke: #fb923c; }
  100% { stroke-dashoffset: 0;   stroke: var(--accent); }
}
@keyframes rf-puff {
  0%   { opacity: 0;   transform: translate(0, 0) scale(0.35); }
  12%  { opacity: 0.95; }
  100% { opacity: 0;   transform: translate(var(--rf-drift, 0px), 21px) scale(2.3); }
}

@media (prefers-reduced-motion: reduce) {
  /* No flight, so no puffs: the rocket stands in its ring with its dot. */
  .rf-flight .rf-fly { animation: none; }
  .rf-flight .rf-puff, .rf-flight .rf-flame { animation: none; opacity: 0; }
  .rf-flight .rf-ring { animation: none; stroke-dashoffset: 0; }
  .rf-flight .rf-dot { animation: none; opacity: 1; }
  .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; }
.fs-overlay[data-mode="text"] .fs-ink { cursor: text; }
.fs-overlay[data-mode="erase"] .fs-ink { cursor: cell; }
/* Tool switch: pen / text / eraser. */
.fs-mode {
  display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 11px;
  background: transparent; color: var(--text); border: none; border-radius: 999px;
  font-size: 13px; font-weight: 600; cursor: pointer; transition: background .12s, color .12s;
}
.fs-mode:hover { background: var(--surface); }
.fs-mode:active { transform: scale(.96); }
.fs-mode-on, .fs-mode-on:hover { background: var(--surface); box-shadow: inset 0 0 0 2px var(--accent); }
.fs-mode-t { font: 700 15px/1 Georgia, "Times New Roman", serif; }
.fs-mode-ico { width: 16px; height: 16px; }
@media (max-width: 560px) { .fs-mode span { display: none; } .fs-mode { padding: 0 9px; } }
/* The size preview: a ring for the eraser, a letter for text. */
.fs-pen-dot[data-mode="erase"] { background: transparent; box-shadow: inset 0 0 0 2px var(--text); }
.fs-pen-dot[data-mode="text"] {
  width: auto !important; height: auto !important; background: none; border-radius: 0;
  color: var(--text); font-weight: 700; line-height: 1;
}
/* The box text is typed into before it is written onto the page. */
.fs-textbox {
  position: absolute; z-index: 2; min-width: 60px; width: 14em; max-width: 90%;
  margin: 0; padding: 0 2px; line-height: 1.25; font-family: inherit;
  background: rgba(255, 255, 255, .85); border: 1px dashed var(--accent); border-radius: 3px;
  outline: none; box-shadow: none; -webkit-user-select: text; user-select: text; touch-action: auto;
}

/* 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; }

/* ============================================================
 * TEXTS (two-way SMS with drivers)
 *
 * Almost nothing, on purpose: the screen is drawn out of the chat screen's own
 * parts (.chat-row, .chat-bubble, .chat-composer), because it is the same kind
 * of screen and the app should have exactly one idea of what a conversation
 * looks like. What is left is the two things a text has and a chat message
 * doesn't — a number that may belong to nobody on the roster, and a delivery
 * that can fail after we have accepted it.
 * ============================================================ */
/* Where the reply box would be, for a driver who hasn't agreed to texts. */
.sms-noconsent { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 8px 0 0; }
.sms-devnote {
  margin-bottom: 12px;
  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;
}
/* A number with no driver behind it. Grey rather than branded — it is the
   absence of a person, not a kind of one. */
.chat-av-sms { background: var(--bg-3); color: var(--text-dim); }
/* Rides in the bubble's clock line, so it reads as part of the timestamp
   rather than as a second message. It is only ever on an outbound bubble,
   whose ground is a light tint (--bubble-mine), so the danger red reads on it
   — the WORD still does most of the work. No underline: on a line whose other
   half is a timestamp, an underlined phrase reads as a link to somewhere, and
   there is nowhere to go. */
.sms-undelivered { font-weight: 800; color: var(--danger); }


/* ============================================================
 * 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; flex-wrap: wrap; }
.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; }
/* On a board slot (an extra-day bus answering the day-before text). */
.disp-slot .slot-line .tconf { margin-left: 6px; }
.disp-slot .tconf-note { margin-top: 0; }

/* 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; }
/* Dispatch → Plans (back on its own screen, 2026-10-12). The settings button
   sits at the end of the hero's top line; the counts are a row of figures. */
.plan-hero .plan-settings { margin-left: auto; }
.plan-stats { display: flex; flex-wrap: wrap; gap: 8px; border-top: 1px solid var(--border); padding-top: 10px; }
.plan-stat {
  font-size: 12.5px; color: var(--text-dim); padding: 4px 11px; border-radius: 999px;
  background: var(--bg); border: 1px solid var(--border);
}
.plan-stat b { color: var(--text); font-size: 14px; font-variant-numeric: tabular-nums; margin-right: 2px; }
.plan-stat.ok { background: rgba(16,185,129,0.12); border-color: transparent; }
.plan-stat.ok b { color: var(--success); }
.plan-stat.warn { background: rgba(234,179,8,0.14); border-color: transparent; }
.plan-stat.warn b { color: var(--warning); }
/* Morning and afternoon, inside each school's card. */
.plan-half + .plan-half { border-top: 1px solid var(--border); }
.plan-half-label {
  padding: 8px 12px 2px; font-size: 10.5px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-dim);
}
.plan-run { display: flex; align-items: center; padding: 0; }
.plan-half .plan-run:first-of-type, .plan-half-label + .plan-run { border-top: 0; }
.plan-run .rt-run-main { padding: 8px 6px 8px 12px; flex: 1; min-width: 0; }
.plan-run .rt-run-main .run-asg { flex: none; }
.plan-run .run-edit { flex: none; padding: 8px 12px 8px 4px; }
.plan-others { margin-top: 12px; }
.plan-others .plan-run .rt-run-main { padding-left: 10px; }

/* 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); }
/* Whether the run's buses have their children routed (Routes → Build), and
   the door back to the map for this run. Same pill as the driver badge, so the
   row's two states — drivers, routes — read as a pair; a run with buses and no
   routes gets a plain "Build routes ›" rather than nothing. */
.run-routes { font-size: 10.5px; font-weight: 700; letter-spacing: .2px; border-radius: 999px; padding: 1px 8px; white-space: nowrap; cursor: pointer; border: 1px solid transparent; }
.run-routes.ok { color: var(--success); background: rgba(52, 211, 153, 0.15); }
.run-routes.draft { color: var(--warning); background: rgba(234, 179, 8, 0.16); }
.run-routes.none { color: var(--accent); background: transparent; border-color: var(--accent-soft); }
.run-routes:hover { border-color: var(--accent); }
.plan-head-acts { display: inline-flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.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-soft); border-color: var(--accent); color: var(--accent); }
/* Which way a run goes (0138): one segmented switch, two answers, full width so
   it reads as a row of the sheet and not as a chip somebody might miss. The
   lit half is the accent, like the weekday pills above it. */
.run-way { display: grid; grid-template-columns: 1fr 1fr; border: 1.5px solid var(--border); border-radius: 10px; overflow: hidden; margin: 2px 0 6px; }
.run-way-btn { display: flex; align-items: center; justify-content: center; gap: 7px; padding: 9px 8px; border: 0; background: var(--surface); color: var(--text-dim); font: inherit; font-size: 13.5px; font-weight: 700; cursor: pointer; }
.run-way-btn + .run-way-btn { border-left: 1.5px solid var(--border); }
.run-way-btn.on { background: var(--accent-soft); color: var(--accent); }
.run-way-i { font-size: 15px; letter-spacing: -1px; }
.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-soft); border-color: var(--accent); color: var(--accent); }
.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 */
/* Who drives this run: one row per driver — the name, the bus it comes with
   (a select, since the roster answers it and it only needs overruling), and a
   ⋯ for the rarer things. The name is the row; the bus is small on purpose. */
.rdrv { display: flex; flex-direction: column; gap: 6px; max-height: 55vh; overflow-y: auto; margin: 6px 0; }
.rdrv-row { display: flex; align-items: center; gap: 6px; }
.rdrv-name { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; 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; white-space: nowrap; }
.rdrv-name:hover { border-color: var(--accent); }
.rdrv-name.empty { color: var(--warning); font-weight: 600; }
.rdrv-tags { font-size: 11px; font-weight: 700; color: var(--accent); white-space: nowrap; }
.modal .rdrv-bus { flex: none; width: 136px; padding: 7px 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 12.5px; }
.modal .rdrv-bus.warn { border-color: var(--warning); color: var(--warning); }
.rdrv-more { flex: none; padding: 7px 9px; }
.rdrv-fill { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 10px; border-radius: 8px; background: rgba(234, 179, 8, 0.12); color: var(--warning); font-size: 12.5px; font-weight: 600; }

/* 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. */
/* ---- Day schedules (0132): the day dialog, the list, the editor ---- */
.dsch-row { align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border); }
.dsch-row:last-child { border-bottom: none; }
.dsch-main { flex: 1; min-width: 0; text-align: left; }
button.dsch-main { background: none; border: 0; padding: 0; color: inherit; font: inherit; cursor: pointer; }
.dsch-what { font-size: 14px; font-weight: 700; color: var(--text); }
.dsch-sub { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.dsch-form { margin-top: 10px; }
/* The editor: morning and afternoon runs side by side, one segmented answer
   per run — As usual · At · Off — with the time box under the row when "At"
   is the answer and the like-weekday's reading under "As usual". */
.dsch-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; margin-top: 8px; }
.dsch-col-h { font-size: 11px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; color: var(--accent); margin: 8px 0 2px; }
.dsch-run { padding: 6px 0; border-top: 1px solid var(--border); display: grid; grid-template-columns: 1fr auto; gap: 4px 8px; align-items: center; }
.dsch-run-name { font-size: 13px; font-weight: 700; color: var(--text); min-width: 0; }
.dsch-seg { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.dsch-seg-btn { border: 0; background: var(--surface); color: var(--text-dim); font-size: 12px; font-weight: 700; padding: 5px 9px; cursor: pointer; font-family: inherit; }
.dsch-seg-btn + .dsch-seg-btn { border-left: 1px solid var(--border); }
.dsch-seg-btn.on { background: var(--accent); color: #fff; }
.modal .dsch-time { grid-column: 1 / -1; width: auto; justify-self: end; padding: 6px 9px; }
.dsch-usual { grid-column: 1 / -1; }
.dsch-usual:empty { display: none; }
@media (max-width: 560px) { .dsch-cols { grid-template-columns: 1fr; } }

.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; }
/* A section head with two buttons: they stay together on the trailing edge
   and wrap under the title on a phone rather than squeezing it. */
.admin-section-btns { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }

/* The days read off the district's calendar, waiting to be ticked. A list of
   thirty inside a 420px dialog scrolls on its own so the buttons stay put. */
.dread-list { max-height: 44vh; overflow-y: auto; margin: 6px 0 4px; border: 1px solid var(--border); border-radius: 10px; }
.dread-row { padding: 8px 10px; border-bottom: 1px solid var(--border); }
.dread-row:last-child { border-bottom: none; }
.modal label.dread-row { display: flex; gap: 10px; }
.dread-already { display: flex; align-items: center; gap: 10px; color: var(--text-dim); }
.dread-date { flex: none; width: 96px; font-weight: 700; }
.dread-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dread-note { flex: none; font-size: 11px; color: var(--text-dim); }
.dread-sub { margin: 10px 0 0; }

/* 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; }
/* The day's schedule in a word (0132). Ellipsised rather than wrapped: a cell
   is ~40px across and "AM Chanukah · PM Friday" is the tooltip's job. */
.cal-sched { font-size: 10px; font-weight: 700; color: var(--ycal-chg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }

.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);
}
/* THE DAY IS ON A DIFFERENT SCHEDULE (0132) — "afternoon on the Friday
   schedule". The same cyan as a moved run, because it is the same fact at the
   scale of the day: times moved, nothing shut. On the board it is a BUTTON —
   tapping the sentence opens the dialog that set it — and it has to read as
   the banner it sits among, not as a control. */
.sday-banner.sched {
  background: color-mix(in srgb, var(--ycal-chg) 14%, transparent);
  color: var(--ycal-chg);
}
button.sday-banner { display: block; width: 100%; text-align: left; border: 0; cursor: pointer; font: inherit; font-weight: 700; }
button.sday-banner:hover { filter: brightness(1.06); }
/* The schedule button in the date bar: the day's answer, always in view. Lit
   in the same cyan when the day is on something. */
.disp-sched-btn { white-space: nowrap; margin-left: auto; }
.disp-sched-btn.on {
  color: var(--ycal-chg);
  border-color: color-mix(in srgb, var(--ycal-chg) 45%, transparent);
  background: color-mix(in srgb, var(--ycal-chg) 12%, transparent);
}

.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-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; }

/* The face on a conversation — deliberately the same object the Drivers grid
   draws (.pfp): same circle, same photo url, same fallback arithmetic. It keeps
   a class of its own only because it is a different SIZE here and sits in a
   flex row rather than on a card; nothing about how a person LOOKS is decided
   twice. */
.chat-av {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: 17px;
  font-weight: 700;
  border: 2px solid var(--border);
}
.chat-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The same hsl() as .pfp-initials, off the same --pfp-hue, so a driver with no
   photo on file is the same coloured circle here as on their card. */
.chat-av-initials {
  color: #fff;
  letter-spacing: 0.5px;
  background: hsl(var(--pfp-hue, 210), 45%, 42%);
  border-color: hsl(var(--pfp-hue, 210), 45%, 32%);
}
.chat-av-office { background: var(--brand-grad); color: #fff; border-color: transparent; }
.chat-av-group  { background: var(--bg-3); }
/* Beside a message rather than beside a name, so a run of three lines is still
   mostly words. */
.chat-av-sm { width: 26px; height: 26px; font-size: 11px; border-width: 1px; }

/* ---------- 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 {
  /* --subbar-h is added HERE and not folded into --chat-chrome because it is
     redefined on the BODY (0, or 42px under body:has(.disp-tabs)) — a var() read
     up on :root would resolve against :root's own 0 and silently do nothing.
     Without it the office read this screen with 43px of page scroll under a
     composer that is fixed and so did not move with it; a driver, whose
     Messages section has one child and therefore no bar, was always exact. */
  height: calc(100vh - var(--chat-chrome) - var(--subbar-h));
  height: calc(100dvh - var(--chat-chrome) - var(--subbar-h));
  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;
  /* Their words sit on a ground tinted with THEIR colour — the same --conv-c
     the row and the header carry, set on the thread's scroller (or on the run,
     in a group, where each speaker brings their own). Plain --surface-2 was one
     grey step off the pane behind it and on the split's white pane it all but
     vanished; the tint is what makes an incoming bubble a bubble. Where no
     colour is set (a thread with the office, which takes the brand and must
     not read as ours) the mix falls to a neutral that still lifts a full step. */
  background: color-mix(in srgb, var(--conv-c, var(--muted)) 16%, 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;
}
/* Ours: a tint of the brand (see --bubble-mine), dark text. It used to be the
   solid brand orange with white type, and a thread of five replies was five
   slabs of it — the loudest thing on a screen whose job is somebody else's
   words. The squared corner and the right edge already say which side is ours;
   the colour only has to agree. */
.chat-msg.mine .chat-bubble {
  background: var(--bubble-mine);
  color: var(--text);
  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: var(--text-dim); }

/* ---------- 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 pay cycle: what rolled in, what rolls on (migration 0134) ----
   Same recipe as the salary strip: a row of facts under the driver's head.
   The paying figure is the one that matters, so it is the only bold one in
   the row's own colour; a period that ROLLS is amber, because the office
   reads the gross column as "the check" and on that row it isn't. */
.pay-carrystrip {
  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-carrystrip b { font-variant-numeric: tabular-nums; color: var(--text); }
.pay-carrystrip .pay-carry-due b { color: var(--success); }
.pay-carrystrip.rolls .pay-carry-due b { color: var(--warning); }
.payrow-due {
  display: block;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--success);
  letter-spacing: 0;
}
.payrow-due.rolls { color: var(--warning); }

/* ---- 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; }
/* The same field drawn inside the driver record's modal (Info → Pay): `.modal
   label` (0,1,1) would uppercase the whole box, caption included, and push it
   down 18px — the notched org-field carries its own spacing. */
.modal label.rate-field { text-transform: none; letter-spacing: 0; margin: 0; font-size: inherit; color: inherit; }
/* 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; }
/* THE SCHOOL, on the customer list (0130). Brand, not a status: it says which
   row is ours, and the green/amber/red ramp means something else on every
   other chip in this section. */
.bp-school { background: var(--accent-soft);    color: var(--accent); }

/* ---- 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 block that holds BOTH halves — the department tabs and the pills under
   them. It used to sit indented 22px under a school's row; there is one school
   (0130) and no row, so it is the sheet's own field now. */
.modal .rd-pick { margin: 6px 0 10px; }
.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;
}

/* ---- Width: capped, and centred so the space falls evenly either side ---- */
.disp-body.paperq { width: 100%; max-width: 1080px; margin-inline: auto; }

/* ---- The cuts: coloured buttons, the chosen one solid ---- */
.repq .rep-cuts { display: flex; flex-wrap: wrap; gap: 8px; }

/* ---- "By requirement": the file cabinet ----
   The folder tabs, folder body and file cards are the cabinet's own
   (.folder-tab, .folder-body, .cabinet-file). All this adds: a file is a
   div with a button for its summary, not a <details>, and its left edge wears
   the worst thing inside it. */
.rep-folders .folder-body.active { background: var(--surface); }
/* Light: white panel, white drawer and white files were one sheet of white.
   Layer them instead — the open drawer (and its tab, which has to run into it)
   is a grey well, and the files sit in it as white cards. */
:root[data-theme="light"] .rep-folders .folder-body.active,
:root[data-theme="light"] .rep-folders .folder-tab.active { background: var(--bg-2); }
:root[data-theme="light"] .rep-folders .folder-tab:not(.active) { background: var(--bg); }
:root[data-theme="light"] .rep-file { background: var(--surface); }
:root[data-theme="light"] .rep-file .cf-summary:hover { background: var(--bg); }
.rep-file { border-left-color: var(--rep-c); }
.rep-file.open { box-shadow: var(--shadow); }
.rep-file .cf-summary {
  width: 100%; background: none; border: 0; text-align: left;
  font: inherit; color: inherit;
}
.rep-file .cf-summary-right { flex-wrap: wrap; justify-content: flex-end; }
.rep-file .cf-caret { display: inline-block; }
.rep-file.open .cf-caret { transform: rotate(90deg); }
.rep-file .cf-body { padding: 10px; }
.cab-chip-none { color: var(--text-dim); background: var(--surface-2); border-color: var(--border); }
@media (max-width: 560px) {
  .rep-file .cf-summary { flex-wrap: wrap; }
  .rep-file .cf-summary-right { flex-basis: 100%; justify-content: flex-start; }
}

/* 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,
  .chat-av, .conv-ch, .chat-count, .chat-row-preview, .chat-row-when,
  .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); }

  /* ---- Conversations ----
     A conversation row is a way in, so it lifts one step like every other row in
     the app that opens something. What a flat wash misses is that the row is not
     one surface: it is a face, two lines held deliberately dim, a channel tag
     and a count, and tinting the ground behind them leaves every one of them
     exactly as it was. So the row's parts answer too — the dim lines come up to
     full strength and the face lifts off the row. Hovering a conversation should
     make it more LEGIBLE, not merely tinted, and nothing here repaints anything
     that carries whose conversation it is.

     And it answers in THAT PERSON'S colour. Colour on this screen is identity
     (see the --conv-c block), and the open row already says so with a bar in
     its conversation's hue — so a hover is that row a beat early: the lifted
     ground takes a breath of the same wash the thread's header wears, and a
     softer copy of the bar appears on the edge. Pointing at a row previews what
     opening it does, rather than tinting it a grey that belongs to nobody. A
     row with no colour of its own (the picker's fixed choices) keeps the plain
     lift and no bar. */
  .chat-row:hover, .chat-pick:hover {
    background: color-mix(in srgb, var(--conv-c, var(--surface-2)) 8%, var(--surface-2));
    box-shadow: inset 3px 0 0 color-mix(in srgb, var(--conv-c, var(--brand-600)) 45%, transparent);
  }
  .chat-pick:not([style*="--conv-c"]):hover { box-shadow: none; }
  /* A read row dims its preview and its clock; an unread one is already at full
     weight and has nowhere to go, which is why this names the two dim lines
     rather than the row. */
  .chat-row:hover .chat-row-preview,
  .chat-row:hover .chat-row-when { color: var(--text); }
  /* The avatar answers the way this sheet says a control whose own surface
     already carries meaning answers: it LIFTS, rather than being repainted.
     Stepping its ground instead would have worked while every bubble was a
     letter on grey, and would now flatten the one thing on the row that says
     WHOSE conversation it is — a photo, or that person's own colour. A shadow
     says the same thing and argues with none of it. */
  .chat-row:hover .chat-av,
  .chat-pick:hover .chat-av {
    transform: scale(1.06);
    box-shadow: 0 2px 9px rgba(0, 0, 0, 0.3);
    /* Its rim takes the person's colour — the one part of the row that is
       already THEM gets a little more so. The office's face has no rim to
       colour, and a group's or a phone's stays neutral for the same reason
       their grounds do: they are a kind, not a person. */
    border-color: var(--conv-c, var(--border));
  }
  .chat-row:hover .chat-av-office,
  .chat-pick:hover .chat-av-office { border-color: transparent; }
  .chat-row:hover .chat-av-group,
  .chat-row:hover .chat-av-sms,
  .chat-pick:hover .chat-av-group,
  .chat-pick:hover .chat-av-sms { border-color: var(--border); }
  /* The channel is the one fact on the row that decides where a reply lands and
     what it costs, so it sharpens with the lines around it. Crisper and NOT
     warmer: brand orange on this row would be a third colour system beside the
     conversation's own, and a static tag that turns orange under a pointer
     reads as something you can press. */
  .chat-row:hover .conv-ch { color: var(--text); }
  /* Filled control, same 1.08 lift as every other one in the app. */
  .chat-row:hover .chat-count { filter: brightness(1.08); }

  /* ---- 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; }

/* ============================================================================
   FLUID CHECKS (migration 0118)

   NOTHING FOR THE SCHEDULE ITSELF, and that is the whole story. Fluids briefly
   had a screen of their own — a Fleet tab, a panel, four tones, a row with a
   button on the end — and every line of it was a second way of saying what the
   fleet screen already says about the PM and the DOT. A fluid check is a
   maintenance schedule like those two, so it is drawn like those two: a `.cpill`
   on the bus's row, a `.kpi` on its record, and the "Due Soon" group it was
   already in. There is no ramp here because none is needed; the ones the
   compliance chips use were right all along.

   What is left is the one thing the app has no part for: the history inside the
   log dialog.
   ============================================================================ */

/* 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); }
/* A day on a different schedule (0132): the cyan of a moved run. */
.hcal-note.sched { --note-c: var(--ycal-chg); }

/* ---- 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-sched { --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; }

/* HOW MUCH OF THE SCHOOL IS SHUT (0150) — the sizes, biggest first.
   It borrows the Extra Work wizard's big answer buttons (.ew-big / .ew-grid)
   rather than inventing a second set: they are already the app's way of asking
   a question a thumb answers, and the colours are the same boys' blue and
   girls' pink the run chips and the department pills wear. Only the tones and
   the two headings are here. */
.ycal-close-who { margin: 2px 0 10px; }
.ycal-close-step {
  font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .4px;
  color: var(--accent); margin: 10px 0 6px;
  display: flex; align-items: center; gap: 6px;
}
.ycal-close-step .muted { font-weight: 600; text-transform: none; letter-spacing: 0; font-size: 11.5px; }
.ycal-close-n {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
  background: var(--accent-soft); color: var(--accent); font-size: 11px; font-weight: 800;
}
/* The whole school is nobody's department, so it takes neither colour — and it
   must not take the brand orange either, which on every other screen means
   "press me" rather than "this one". */
/* WHICH DAYS — the driver's own day strip, ticking more than one. A closure
   runs for Sukkos, not for Tuesday, so the days wrap as a grid of small
   answers rather than a row that scrolls out of sight. */
.ycal-close-days-wrap { margin: 2px 0 4px; }
.ycal-close-days { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px; }
.modal .ycal-close-days .ew-big {
  --ew-c: var(--accent);
  min-height: 58px; padding: 9px 6px; font-size: 14px; gap: 1px;
}
.modal .ycal-close-days .ew-big small { font-size: 10.5px; }
.modal .ycal-close-days .ew-big.on { box-shadow: inset 0 0 0 2px var(--ew-c); }
/* The typed day is a door, not an answer, so it never wears the lit state. */
.modal .ycal-close-day-add { --ew-c: var(--border); font-weight: 700; color: var(--text-dim); }
.modal .ycal-close-day-add input { width: auto; margin: 0; font-size: 12px; padding: 4px 6px; }

.ycal-close-who .ycal-close-all { --ew-c: var(--ew-mixed); }
/* The typed day is a door, not an answer, so it never wears an answer's colour.
   After the grid rule above, which is more specific than the one beside it. */
.modal .ycal-close-days .ycal-close-day-add { --ew-c: var(--border); color: var(--text-dim); font-weight: 700; }
/* A closure the Jewish calendar wrote says so instead of naming an author. */
.ycal-auto-tag {
  margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  background: var(--surface-2); color: var(--muted);
  font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .3px;
  white-space: nowrap;
}

/* ---------- Settings → Jewish calendar ----------
   A switch and a preview, because there is nothing on this calendar to enter —
   only whether we act on what the app already knows. */
.jc-switch {
  display: flex; align-items: flex-start; gap: 10px; cursor: pointer;
  padding: 12px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); margin-bottom: 12px;
}
.jc-switch input { width: auto; margin: 2px 0 0; flex: none; }
.jc-switch span { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.jc-switch small { color: var(--text-dim); font-size: 12px; line-height: 1.45; }
.jc-days { display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.jc-day {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: baseline;
  padding: 8px 12px; border-bottom: 1px solid var(--border-soft, var(--border));
  border-left: 3px solid var(--jc-c);
}
.jc-day:last-child { border-bottom: none; }
.jc-day.closed { --jc-c: var(--ycal-shut, var(--muted)); }
.jc-day.friday { --jc-c: var(--ycal-chg, var(--info, #06b6d4)); }
.jc-day-d { grid-column: 1; font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; }
.jc-day-n { grid-column: 1; font-size: 12px; color: var(--text-dim); }
.jc-day-k { grid-column: 2; grid-row: 1 / span 2; justify-self: end; font-size: 11px; font-weight: 800; color: var(--jc-c); text-transform: uppercase; letter-spacing: .3px; }
/* WHICH ONE IS PICKED, said twice: a ring in the answer's own colour, and the
   sizes not chosen standing back. A deeper wash of the same pink is the
   difference between 12% and 26% of one colour, which is not enough on the one
   control on this form that decides how many buses come off the road. */
.ycal-close-who .ew-big.on { box-shadow: inset 0 0 0 2px var(--ew-c); }
.ycal-close-who.picked .ew-big:not(.on) { opacity: .5; }
.ycal-close-who.picked .ew-big:not(.on):hover { opacity: 1; }
.modal .ycal-close-who .ew-big { font-size: 16px; min-height: 76px; }
/* The pills take the division's own colour, so the grades read as part of the
   button above them rather than as a separate question in brand orange. */
.ycal-close-grades { --gp-c: var(--accent); }
.ycal-close-grades.boys { --gp-c: var(--cat-boys); }
.ycal-close-grades.girls { --gp-c: var(--cat-girls); }
.ycal-close-grades.mixed { --gp-c: var(--ew-mixed); }
.modal .ycal-close-grades .gpill.sel {
  border-color: var(--gp-c); color: var(--gp-c);
  background: color-mix(in srgb, var(--gp-c) 16%, transparent);
}
.modal .ycal-close-grades .gpill:hover { border-color: color-mix(in srgb, var(--gp-c) 65%, var(--border)); }

/* ---- 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; }
}


/* ============================================================
 * Routes → Map — the map is the screen
 *
 * A stage FIXED under the header and the section bar, filling the rest of the
 * window: left of the desktop rail, above the phone's tab bar. The map fills
 * the stage, drawn the way a navigation screen draws it — a washed road map
 * (RT_MAP_STYLE in client.ts): white roads, pale ground, no shops, nothing on
 * it that isn't a road or a park — so the only colour on the screen is ours,
 * the buses. EVERYTHING ELSE IS ON ONE SIDE: a single panel (`.rt-panel`)
 * holds the control card, the bus tiles and the open bus's editor, top to
 * bottom — a sheet along the bottom of a phone, the right edge of a desk —
 * and the two map switches are a column of round white buttons on the map's
 * right edge, where a navigation app keeps its own. The page under it does
 * not scroll, so nothing here has to clear anything sticky. Colours on the
 * tiles and the bubbles are ROUTE colours — the solver's palette on `--rt-c`
 * — never brand orange, which on this screen means "a control you press"
 * exactly as everywhere else.
 * ============================================================ */

body:has(.rt-stage) { overflow: hidden; }
body:has(.rt-stage) .disp-wrap { padding-bottom: 0; }

.rt-stage {
  position: fixed;
  top: calc(var(--topbar-h) + var(--subbar-h));
  left: var(--rail, 0px);
  right: 0;
  bottom: calc(var(--chat-nav-h, 72px) + env(safe-area-inset-bottom, 0px));
  z-index: 3;
  /* The map's own ground, so the tiles arriving don't flash a darker frame. */
  background: #f1f2f4;
  overflow: hidden;
  /* Floating things measure themselves against the stage, and the Google
     tooltips must not draw over the header. */
  isolation: isolate;
}
.rt-map, .rt-map-node { position: absolute; inset: 0; }
.rt-map .stu-geo { margin: 12px; position: relative; z-index: 1; }

/* ---- THE PANEL — one side, everything ----
   On a phone it is a sheet along the bottom: the card, then the tiles as a
   strip, then the open bus. It grows with what is in it up to two thirds of
   the stage and the map keeps the rest. */
.rt-panel {
  position: absolute; z-index: 5;
  left: 0; right: 0; bottom: 0;
  max-height: 66%;
  display: flex; flex-direction: column;
  background: var(--surface);
  border-radius: 18px 18px 0 0;
  box-shadow: 0 -4px 24px rgba(0,0,0,.18);
}
/* The sheet's grab bar, so it reads as a sheet. */
.rt-panel::before {
  content: ""; flex: none; align-self: center;
  width: 36px; height: 4px; border-radius: 2px; margin: 8px 0 0;
  background: var(--border);
}
/* Lists that hang off a picker and a tile's menu must not be clipped by the
   panel's edge; while one is open the panel stops clipping. */
.rt-panel:has(.rt-pickwrap.open), .rt-panel:has(.rt-bus.menu) { overflow: visible; }
/* In the sheet, a picker's list opens UPWARD — downward is off the screen.
   Specific enough (.open) to outrank the generic open-state rule below, which
   otherwise ties it and, being later, pointed the list down into the sheet. */
.rt-panel .rt-pickwrap.open .rt-pop { top: auto; bottom: calc(100% + 4px); border-radius: 12px 12px 6px 6px; }
/* The last picker in the row hangs its list from its RIGHT edge, so a list
   wider than its box grows into the card and not off the panel. */
.rt-hud-pick .rt-pickwrap:last-child .rt-pop { left: auto; right: 0; }

/* ---- the control card ---- */
.rt-hud {
  flex: 0 1 auto; min-height: 0;
  overflow-y: auto; scrollbar-width: thin;
  padding: 8px 14px 12px;
  display: flex; flex-direction: column; gap: 10px;
}
.rt-hud.folded { gap: 6px; }
/* The card SCROLLS; nothing on it shrinks. It is a flex column with a cap,
   and a flex item that clips (.rt-plan-cat, overflow: hidden for its corners)
   may shrink below its content — so a long run list was cut to fit the cap
   with nothing left over to scroll, and the fold under it drawn as if the
   list were whole. */
.rt-hud > * { flex-shrink: 0; }
.rt-hud:has(.rt-pickwrap.open) { overflow: visible; }
.rt-hud .rt-pop { max-height: min(340px, 60vh); }
.rt-hud .rt-pop-list { max-height: min(300px, 55vh); }
.rt-hud-head { display: flex; align-items: center; gap: 8px; }
.rt-hud-pick { flex: 1; display: flex; gap: 8px; min-width: 0; padding-top: 6px; }
.rt-hud-pick select {
  flex: 1 1 0; min-width: 0; height: 34px; padding: 0 8px; font-size: 13px;
  border-radius: 9px; border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
}
.rt-fold {
  flex: none; width: 28px; height: 28px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-dim);
  cursor: pointer; font-size: 13px; line-height: 1;
}
.rt-fold:hover { color: var(--accent); border-color: var(--accent); }
/* With a bus open on a phone, the card is its head row: the editor needs the
   room, and Save is on the tile's menu. */
@media (max-width: 1023px) { .rt-stage.has-side .rt-hud > :not(.rt-hud-head) { display: none; } }

/* AM / PM as one control — two halves of a question, not two buttons. */
.rt-seg { flex: none; display: inline-flex; border: 1px solid var(--border); border-radius: 9px; overflow: hidden; height: 34px; }
.rt-seg button {
  padding: 0 10px; border: 0; background: var(--surface-2); color: var(--text-dim);
  font-size: 12px; font-weight: 700; cursor: pointer;
}
.rt-seg button + button { border-left: 1px solid var(--border); }
.rt-seg button.on { background: var(--accent); color: #fff; }

/* THE GRADES WEAR THE DEPARTMENT. The row carries `--rt-cat` from the run's
   category — boys blue, girls pink, the same two hues the run chips and the
   family page use — and the department's own badge at its head, so the card
   says whose bus this is the way the plan list did before the run was opened.
   A grade that is ON is a filled chip in that colour, the same recipe as the
   .gpill the run's grades were ticked with; OFF is a bare outline. Brand
   orange is never a department. */
.rt-grades { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; --rt-cat: var(--accent); }
.rt-grades.cat-boys { --rt-cat: var(--cat-boys); }
.rt-grades.cat-girls { --rt-cat: var(--cat-girls); }
.rt-grades.cat-mixed, .rt-grades.cat-other { --rt-cat: #a855f7; }
.rt-grades .run-badge { margin-right: 2px; }
.rt-grade {
  padding: 5px 11px; border-radius: 999px; font-size: 12.5px; font-weight: 600; cursor: pointer;
  border: 1.5px solid var(--border); background: var(--surface); color: var(--text-dim);
  transition: border-color .12s, background .12s, color .12s;
}
.rt-grade.on {
  border-color: var(--rt-cat); color: var(--rt-cat); font-weight: 700;
  background: color-mix(in srgb, var(--rt-cat) 14%, transparent);
}
.rt-grade:hover { border-color: color-mix(in srgb, var(--rt-cat) 65%, var(--border)); color: var(--text); }
.rt-grade.on:hover { background: color-mix(in srgb, var(--rt-cat) 24%, transparent); color: var(--rt-cat); }

/* The versions: three answers as a short radio list, each with its figures
   under its name so the choice is made on numbers. The one on screen is the
   outlined row. */
.rt-versions { display: flex; flex-direction: column; gap: 4px; margin: 6px 0 2px; }
.rt-version {
  display: flex; flex-direction: column; gap: 1px; text-align: left; cursor: pointer;
  padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: transparent; color: var(--text-dim);
}
.rt-version:hover { border-color: var(--accent); }
.rt-version.on { border-color: var(--accent); background: var(--surface-2); color: var(--text); }
.rt-version-name { font-size: 13px; font-weight: 600; }
.rt-version.on .rt-version-name { color: var(--accent); }
.rt-version-figs { font-size: 11.5px; }
.rt-version-figs em { font-style: normal; }
.rt-version-figs i { font-style: normal; color: var(--muted); }
.rt-version-figs .warn { color: var(--warning); }
.rt-version-figs .bad { color: var(--danger); font-weight: 600; }

/* SAVED DRAFTS (0148) — folded shut, because a run that has never kept one
   should cost the card a single line. Borrows the version list's recipe: the
   two things ARE alike from the office's side, a set of routes with figures
   to judge it by, and the difference is only that one of these was kept on
   purpose. */
.rt-drafts { margin-top: 8px; border-top: 1px solid var(--border); padding-top: 6px; }
.rt-drafts-head {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  background: none; border: 0; padding: 2px 0; cursor: pointer;
  color: var(--text-dim); font-size: 12.5px; font-weight: 600;
}
.rt-drafts-head:hover { color: var(--accent); }
.rt-drafts-body { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.rt-draft { display: flex; align-items: stretch; gap: 4px; }
.rt-draft-open {
  flex: 1; display: flex; flex-direction: column; gap: 1px; text-align: left; cursor: pointer;
  padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: transparent; color: var(--text-dim); min-width: 0;
}
.rt-draft-open:hover { border-color: var(--accent); }
.rt-draft-name { font-size: 13px; font-weight: 600; color: var(--text); }
.rt-draft-figs { font-size: 11.5px; }
.rt-draft-figs em { font-style: normal; }
.rt-draft-figs .warn { color: var(--warning); }
.rt-draft-figs .bad { color: var(--danger); font-weight: 600; }
.rt-draft-by { font-size: 11px; color: var(--muted); }

.rt-facts { font-size: 13px; color: var(--text-dim); }
.rt-facts b { color: var(--text); font-size: 15px; }
.rt-facts .warn { color: var(--warning); }
.rt-facts .bad { color: var(--danger); font-weight: 600; }
.rt-facts .rt-facts-sub { color: var(--muted); font-size: 12px; }
/* The traffic the estimate is priced in — a fact about the morning, not a
   status, so it is the card's own grey. */
.rt-facts .rt-traffic { font-size: 12.5px; color: var(--text-dim); }
/* The window (0136): when the buses are at the school, and the door to move it.
   A chip on the facts line rather than a field, because it is read far more
   often than it is set. */
.rt-window {
  display: inline-flex; align-items: center; gap: 4px; margin-left: 6px; padding: 2px 9px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
  color: var(--text); font-size: 12px; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.rt-window:hover { border-color: var(--accent); color: var(--accent); }
/* The 🏫 chip shares the recipe; amber is "there is no pin", which is the one
   thing on the card that stops a build. */
.rt-window.warn { border-color: var(--warning); color: var(--warning); }

/* THE BUILD, AS IT HAPPENS, on the card: one line naming the step and the bus
   it is happening to, a dot blinking in that bus's colour, a thin bar under
   it filling as the buses are done. A step with no count runs the bar back
   and forth instead. */
.rt-progress { --rt-c: var(--accent); display: flex; flex-direction: column; gap: 5px; margin-top: 2px; }
.rt-progress-line { display: flex; align-items: center; gap: 7px; min-width: 0; font-size: 12.5px; font-weight: 600; color: var(--text); }
.rt-progress-line > span:not(.rt-progress-dot) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rt-progress-line em { font-style: normal; font-weight: 500; font-size: 11.5px; color: var(--warning); margin-left: auto; white-space: nowrap; }
.rt-progress-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--rt-c); animation: rt-blink 1.1s ease-in-out infinite; }
.rt-progress-bar { height: 4px; border-radius: 2px; background: var(--surface-2); overflow: hidden; }
.rt-progress-bar i { display: block; height: 100%; border-radius: 2px; background: var(--rt-c); transition: width .35s ease; }
.rt-progress.indeterminate .rt-progress-bar i { animation: rt-slide 1.3s ease-in-out infinite; }
@keyframes rt-blink { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.7); } }
@keyframes rt-slide { from { transform: translateX(-100%); } to { transform: translateX(340%); } }

.rt-hud-note { font-size: 12.5px; color: var(--text-dim); line-height: 1.4; }
.rt-hud-note.bad { color: var(--danger); }
.rt-hud-note.warn { color: var(--warning); }
.rt-hud-note b { color: var(--text); }

/* ---- THE PLAN in the panel — with no run open, the season's runs ----
   The rows borrow the board's own parts (.run-badge, .run-asg, .run-routes,
   .run-rule-tag, .stu-chev) so a run reads here as it reads everywhere else;
   what is new is only the shape that fits a 400px column. */
.rt-back {
  flex: none; width: 28px; height: 38px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-dim);
  cursor: pointer; font-size: 20px; line-height: 1; padding: 0 0 3px;
}
.rt-back:hover { color: var(--accent); border-color: var(--accent); }
/* The season's own line — dates, calendar, what it is today — and the sheet
   behind it. A button the width of the card, because the whole line is the
   door and a gear alone would be a 12px target. */
.rt-season {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 8px 10px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); cursor: pointer;
}
.rt-season:hover { border-color: var(--accent); }
.rt-season-l { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rt-season-dates { font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rt-season-dates.warn { color: var(--warning); }
.rt-season-sub { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rt-season-gear { flex: none; color: var(--muted); font-size: 15px; }
.rt-season:hover .rt-season-gear { color: var(--accent); }
.rt-plan-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 2px; }
.rt-plan-title { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); }
/* One group per side, the board's colour on its edge. */
.rt-plan-cat { border: 1px solid var(--border); border-left: 4px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface); }
.rt-plan-cat.cat-boys { border-left-color: var(--cat-boys); }
.rt-plan-cat.cat-girls { border-left-color: var(--cat-girls); }
.rt-plan-cat.cat-mixed, .rt-plan-cat.cat-other { border-left-color: #a855f7; }
.rt-plan-cat.cat-standby { border-left-color: var(--muted); }
.rt-plan-cat-head { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--border); background: var(--bg); }
.rt-plan-cat-n { margin-left: auto; font-size: 11.5px; font-weight: 700; color: var(--text-dim); }
/* A run: the row opens it; the chips under it are its state and each one's door. */
.rt-run { border-top: 1px solid var(--border); padding: 0 0 6px; }
.rt-plan-cat .rt-run:first-of-type { border-top: 0; }
.rt-run.off { opacity: .8; }
.rt-run-main {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 8px 10px 4px; border: 0; background: none; color: var(--text); cursor: pointer;
}
.rt-run-main:hover .rt-run-name { color: var(--accent); }
.rt-run-time { flex: none; min-width: 58px; font-size: 12.5px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.rt-run-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rt-run-name { font-size: 13.5px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rt-run-sub { font-size: 11.5px; color: var(--text-dim); line-height: 1.35; }
.rt-run-sub .warn { color: var(--warning); font-weight: 700; }
.rt-run-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; padding: 0 10px 0 78px; }
.rt-run-chips .run-asg { cursor: pointer; border: 1px solid transparent; }
.rt-run-chips .run-asg:hover { border-color: var(--accent); }
.rt-run-chips .run-edit { margin-left: auto; }
/* Runs on file that aren't on this season, folded — last year's, or one made
   and never given a bus. */
.rt-plan-others { border: 1px dashed var(--border); border-radius: 10px; background: var(--surface); }
.rt-plan-others > summary { padding: 8px 10px; font-size: 12.5px; font-weight: 700; color: var(--text-dim); cursor: pointer; list-style: none; }
.rt-plan-others > summary::before { content: "▸ "; }
.rt-plan-others[open] > summary::before { content: "▾ "; }
.rt-plan-others .rt-run { border-top: 1px solid var(--border); }
@media (max-width: 480px) { .rt-run-chips { padding-left: 10px; } }

.rt-acts { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.rt-acts .btn-primary, .rt-acts .btn-outline, .rt-acts .btn-ghost {
  height: 32px; padding: 0 12px; font-size: 13px; border-radius: 9px;
}
.rt-acts .btn-ghost { color: var(--text-dim); }
.rt-acts .cpill { margin-right: 2px; }
.rt-short { color: #fecaca; font-weight: 800; margin-left: 2px; }

/* the bus picker */
.rt-need { font-size: 13px; color: var(--text-dim); }
.rt-need b { color: var(--text); font-size: 16px; }
.rt-need-why { font-size: 12px; color: var(--muted); margin-top: -4px; }
.rt-fleet-head { display: flex; align-items: center; gap: 10px; font-size: 12px; color: var(--muted); }
.rt-fleet-head span { flex: 1; }
.rt-fleet-head .linklike { background: none; border: 0; padding: 0; font: inherit; font-weight: 600; color: var(--accent); cursor: pointer; }
.rt-fleet-head .linklike:hover { text-decoration: underline; }
.rt-fleet {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: 6px;
  max-height: 224px; overflow-y: auto; padding: 2px; scrollbar-width: thin;
}
.rt-fleet-bus {
  position: relative; padding: 8px 4px 7px; border-radius: 10px; cursor: pointer;
  border: 1.5px solid var(--border); background: var(--surface);
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  transition: border-color .12s ease, transform .12s ease, background .12s ease; opacity: .6;
}
.rt-fleet-bus:hover { transform: translateY(-1px); opacity: .9; border-color: var(--muted); }
.rt-fleet-bus.on {
  border-color: var(--accent); opacity: 1;
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
  box-shadow: inset 0 0 0 1px var(--accent);
}
.rt-fleet-num { font-size: 16px; font-weight: 800; color: var(--text); line-height: 1; font-variant-numeric: tabular-nums; }
.rt-fleet-bus.on .rt-fleet-num { color: var(--accent); }
.rt-fleet-seats { font-size: 10.5px; color: var(--muted); line-height: 1; }
.rt-fleet-bus.onrun .rt-fleet-num::after { content: ""; display: inline-block; width: 5px; height: 5px; border-radius: 50%; background: var(--success, #22c55e); margin-left: 3px; vertical-align: middle; }

/* ---- the bus dock: the tiles, in the panel ---- */
.rt-dock {
  flex: none; position: relative;
  border-top: 1px solid var(--border);
  padding: 0 10px 8px;
}
.rt-dock:has(.rt-bus.menu) { overflow: visible; }
.rt-dock-strip {
  display: flex; gap: 10px; align-items: flex-end; overflow-x: auto; padding: 12px 2px 2px;
  scrollbar-width: none;
}
.rt-dock-strip::-webkit-scrollbar { display: none; }

/* One bus: a card. A band of the route's colour across the top, the number in
   that colour as the headline, the load and the minutes on one line under it,
   the lot → school times under those. The open bus takes the colour on its
   whole edge and a wash of it behind. */
.rt-bus {
  position: relative; flex: none; width: 104px;
  display: flex; flex-direction: column; align-items: stretch;
}
.rt-bus-card {
  position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  width: 100%; padding: 9px 10px 8px; text-align: left; cursor: pointer;
  background: var(--surface); color: var(--text);
  border: 1.5px solid var(--border); border-top: 5px solid var(--rt-c); border-radius: 12px;
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
  transition: transform .14s ease, box-shadow .14s ease, border-color .14s ease, background .14s ease;
}
.rt-bus-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,.14); border-color: var(--rt-c); }
.rt-bus.on .rt-bus-card {
  border-color: var(--rt-c); box-shadow: 0 0 0 1.5px var(--rt-c), 0 6px 14px rgba(0,0,0,.16);
  background: color-mix(in srgb, var(--rt-c) 9%, var(--surface));
}
.rt-bus-num {
  font-size: 22px; font-weight: 800; line-height: 1; letter-spacing: -.02em;
  color: var(--rt-c); font-variant-numeric: tabular-nums;
}
/* Children over seats, and the minutes. */
.rt-bus-figs {
  display: flex; align-items: baseline; gap: 2px; white-space: nowrap;
  font-size: 12px; line-height: 1; color: var(--text-dim); font-variant-numeric: tabular-nums;
}
.rt-bus-figs b { font-size: 14px; font-weight: 800; color: var(--text); }
.rt-bus-figs i { font-style: normal; }
.rt-bus-figs em { font-style: normal; font-weight: 600; margin-left: 4px; }
.rt-bus-figs em::before { content: "·"; margin-right: 4px; font-weight: 400; color: var(--muted); }
.rt-bus.over .rt-bus-figs b { color: var(--danger); }
.rt-bus.late .rt-bus-figs em { color: var(--warning, #d97706); }
/* The third way a route is wrong: the road turns the bus round, which it
   can't do. Red like over-seats — the route can't be driven as drawn. */
.rt-bus-uturn {
  text-decoration: none; font-weight: 800; margin-left: 5px; padding: 1px 5px; border-radius: 6px;
  color: #fff; background: var(--danger); font-size: 10.5px; letter-spacing: .02em;
}
.rt-bus.uturn .rt-bus-card { border-color: var(--danger); }
.rt-bus-name {
  max-width: 100%; font-size: 11.5px; font-weight: 700; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Out of the lot → at the school, this bus's own two times. */
.rt-bus-when { font-size: 10.5px; color: var(--text-dim); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ---- THE BUILD, AS IT HAPPENS, on the cards ----
   The bus being worked on is LIT in its own colour — a soft pulse on its
   edge and a light sweeping along its top band — and its bottom line names
   the act ("Driving on the roads…"). The buses still waiting their turn are
   faded, the ones done are plain, so the work can be watched going down the
   list. What the road moved stays on the card afterwards ("2 stops → corner"),
   in place of the toast that used to name a bus nobody could find. */
.rt-bus-figs .rt-bus-wait { color: var(--muted); font-weight: 500; }
.rt-bus-doing { font-size: 10.5px; font-weight: 700; color: var(--rt-c); white-space: nowrap; }
.rt-bus-note { font-size: 10.5px; color: var(--text-dim); white-space: nowrap; }
.rt-bus.queued .rt-bus-card { opacity: .5; }
.rt-bus.busy .rt-bus-card {
  border-color: var(--rt-c);
  background: color-mix(in srgb, var(--rt-c) 7%, var(--surface));
  animation: rt-ring 1.2s ease-in-out infinite;
}
.rt-bus.busy .rt-bus-card::before {
  content: ""; position: absolute; left: -1.5px; right: -1.5px; top: -5px; height: 5px;
  border-radius: 10px 10px 0 0; pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.85), transparent) no-repeat;
  background-size: 45% 100%;
  animation: rt-sweep 1s linear infinite;
}
@keyframes rt-ring {
  0%, 100% { box-shadow: 0 0 0 1.5px var(--rt-c), 0 6px 14px rgba(0,0,0,.16); }
  50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--rt-c) 28%, transparent), 0 6px 14px rgba(0,0,0,.16); }
}
@keyframes rt-sweep { from { background-position: -100% 0; } to { background-position: 200% 0; } }
.rt-bus-more {
  position: absolute; top: 9px; right: 6px; width: 24px; height: 24px; border-radius: 50%;
  border: 0; background: none; color: var(--text-dim);
  font-size: 15px; line-height: 1; cursor: pointer; z-index: 2;
}
.rt-bus-more:hover, .rt-bus.menu .rt-bus-more { background: color-mix(in srgb, var(--rt-c) 14%, transparent); color: var(--rt-c); }
.rt-bus-menu {
  position: absolute; bottom: calc(100% + 6px); right: 0; z-index: 6; min-width: 150px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 10px 28px rgba(0,0,0,.25); padding: 4px; display: flex; flex-direction: column;
}
.rt-bus-menu button {
  text-align: left; padding: 8px 12px; border: 0; background: none; color: var(--text);
  font-size: 13px; border-radius: 8px; cursor: pointer;
}
.rt-bus-menu button:hover:not(:disabled) { background: var(--surface-2); color: var(--accent); }
.rt-bus-menu button:disabled { color: var(--muted); cursor: default; }
/* The one destructive line on the menu, set apart from the edits above it. */
.rt-bus-menu button.danger { color: var(--danger); border-top: 1px solid var(--border); border-radius: 0 0 8px 8px; margin-top: 2px; }
.rt-bus-menu button.danger:hover:not(:disabled) { color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, transparent); }
.rt-bus-all {
  flex: none; align-self: center; width: 52px; height: 52px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  font-size: 11px; line-height: 1.2; cursor: pointer;
}
.rt-bus-all b { font-size: 15px; }
.rt-bus-all:hover { border-color: var(--accent); }
/* With a bus open, the panel IS that bus: the control card and the row of
   cards go, and the directions list has the whole panel. ‹ on the list's
   head is the way back to them. */
.rt-stage.has-side .rt-hud, .rt-stage.has-side .rt-dock { display: none; }
.rt-stage.has-side .rt-bus-all { width: 44px; height: 44px; font-size: 10px; }
.rt-stage.has-side .rt-bus-all b { font-size: 13px; }

/* ---- MAP ONLY: the panel folded down to the bus cards ----
   The round ⛶ on the map's edge. The app's rail, the control card and the
   open bus's editor go; the stage takes the window under the header, and the
   cards stay as a strip along its foot — the minutes and the load on each are
   what a drag on the map is being judged against, so they are the one part
   of the panel that must not fold. Translucent, so the road under the strip
   is still a road. With a bus open the strip is shown here where the panel
   would otherwise be that bus alone (`.has-side` hides the dock), and the
   round All button in it is the way back to every bus. A card's ⋯ still
   carries Save. */
.rt-stage.map-only { left: 0; bottom: 0; }
body:has(.rt-stage.map-only) { padding-left: 0; }
body:has(.rt-stage.map-only) .bottom-nav { display: none; }
.rt-stage.map-only .rt-hud, .rt-stage.map-only .rt-side { display: none; }
.rt-stage.map-only.has-side .rt-dock { display: block; }
.rt-stage.map-only .rt-panel {
  max-height: none;
  background: color-mix(in srgb, var(--surface) 86%, transparent);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
.rt-stage.map-only .rt-panel::before { display: none; }
.rt-stage.map-only .rt-dock { border-top: 0; }
/* Nothing on the run yet: no strip, the map alone. */
.rt-stage.map-only .rt-panel:has(.rt-dock[hidden]) { display: none; }

/* ---- one bus, open — the editor, in the panel ---- */
.rt-side {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; scrollbar-width: thin;
}
.rt-side .rt-editor { border: 0; border-top: 4px solid var(--rt-c); background: transparent; margin: 0; border-radius: 0; }
/* The pane's head: ‹, the number in the route's colour, the name, Save. */
.rt-back {
  flex: none; width: 30px; height: 30px; border-radius: 50%; border: 0; background: none;
  color: var(--text-dim); font-size: 24px; line-height: 1; cursor: pointer; padding: 0 0 3px;
}
.rt-back:hover { background: var(--surface-2); color: var(--text); }
.rt-bus-chip {
  flex: none; padding: 3px 9px; border-radius: 8px;
  background: var(--rt-c); color: #fff; font-size: 15px; font-weight: 800; line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

/* ---- the hover card ---- */
.rt-tip {
  position: absolute; z-index: 9; pointer-events: none; max-width: 300px;
  background: #fff; color: #202124; border-radius: 12px; padding: 10px 14px;
  box-shadow: 0 2px 6px rgba(0,0,0,.3), 0 10px 26px rgba(0,0,0,.25);
}
.rt-tip-head { font-size: 11px; color: #5f6368; margin-bottom: 4px; }
.rt-tip-fam { font-size: 22px; font-weight: 800; line-height: 1.15; color: #202124; }
.rt-tip-kids { font-size: 16px; margin-top: 3px; color: #3c4043; }
.rt-tip-row { font-size: 15px; line-height: 1.5; }
.rt-tip-row b { font-size: 16px; }
.rt-tip-row span { color: #3c4043; }
.rt-tip-addr { margin-top: 5px; font-size: 12px; color: #5f6368; font-weight: 500; }
.rt-tip-row em { display: block; font-style: normal; font-size: 11.5px; color: #5f6368; font-weight: 500; }

/* ---- the map switches: a column of round white buttons on the right edge ---- */
.rt-mapctl {
  position: absolute; z-index: 4; top: 12px; right: 12px;
  display: flex; flex-direction: column; gap: 10px;
}
.rt-mapbtn {
  width: 44px; height: 44px; border-radius: 50%; border: 0; cursor: pointer; padding: 0;
  background: #fff; color: #3c4043;
  box-shadow: 0 1px 4px rgba(0,0,0,.3);
  display: inline-flex; align-items: center; justify-content: center;
  transition: box-shadow .12s ease, color .12s ease;
}
.rt-mapbtn svg { width: 22px; height: 22px; display: block; }
.rt-mapbtn.on { color: var(--accent); }
.rt-mapbtn-3d { font-size: 13px; font-weight: 800; letter-spacing: .2px; }
.rt-mapbtn:hover { box-shadow: 0 2px 8px rgba(0,0,0,.35); }
/* The attribution strip stays legible and out of the way. */
.rt-map-node .gm-style-cc { opacity: .5; }

/* ---- the pickers: a button wearing its answer, a searchable list under it ----
   The box is the app's own field (the .modal input / .pickfield recipe): an
   outlined box, the name notched into its top edge, accent on focus. It was a
   smaller filled chip with a caption inside it, and beside the notched fields
   everywhere else in the app it read as a different kind of control. */
.rt-pickwrap { position: relative; flex: 1 1 0; min-width: 0; --notch-bg: var(--surface); }
.rt-pick {
  position: relative;
  width: 100%; height: 46px; padding: 0 30px 0 12px; text-align: left; cursor: pointer;
  border-radius: 8px; border: 1px solid var(--border); background: var(--notch-bg); color: var(--text);
  display: flex; align-items: center; gap: 8px; min-width: 0;
  font-family: inherit;
  -webkit-tap-highlight-color: transparent;
}
.rt-pick:hover { border-color: var(--muted); }
.rt-pick:focus-visible, .rt-pickwrap.open .rt-pick { border-color: var(--accent); outline: none; }
/* The chevron of every other picked field, so it reads as a thing that opens. */
.rt-pick::after {
  content: "\25BE"; position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  font-size: 12px; color: var(--text-dim); pointer-events: none;
}
.rt-pick-open::after { content: "\25B4"; color: var(--accent); }
/* The name, sitting on the border in the surface's own colour — the notch
   recipe from the modal labels, hand-placed because the label is inside the box. */
.rt-pick-k {
  position: absolute; top: -8px; left: 8px; z-index: 1;
  max-width: calc(100% - 16px); padding: 1px 5px;
  background: var(--notch-bg); color: var(--text-dim);
  font-size: 12px; font-weight: 500; line-height: 1.15; letter-spacing: 0; text-transform: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rt-pickwrap.open .rt-pick-k, .rt-pick:focus-visible .rt-pick-k { color: var(--accent); }
.rt-pick-v { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Nothing picked yet: the name is the value, in the colour of a control —
   it is the next thing to do on this card. */
.rt-pick.empty .rt-pick-v { color: var(--accent); }
.rt-pick.empty .rt-pick-k { display: none; }
.rt-pop {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 7; width: max(100%, 300px);
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 12px 32px rgba(0,0,0,.4); padding: 6px; display: flex; flex-direction: column; gap: 4px;
}
/* Open: the same box, now a field. The label stays; the value line is the input. */
.rt-pick-open { cursor: text; border-color: var(--accent); }
.rt-pick-open .rt-pick-v {
  width: 100%; min-width: 0; height: auto; padding: 0; margin: 0; border: 0; border-radius: 0; background: transparent;
  color: var(--text); font: inherit; font-size: 14px; font-weight: 600; line-height: 1.2; outline: none; box-shadow: none;
}
.rt-pickwrap.open .rt-pop { top: calc(100% + 4px); border-radius: 6px 6px 12px 12px; }
.rt-pop-list { max-height: 260px; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; display: flex; flex-direction: column; }
.rt-pop-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 10px; border: 0; border-radius: 8px; background: none; color: var(--text);
  font-size: 13.5px; font-weight: 500; text-align: left; cursor: pointer; min-width: 0;
}
/* The name keeps its line; the date beside it gives way first. */
.rt-pop-row > span { flex: none; max-width: 64%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rt-pop-row em { flex: 1 1 0; min-width: 0; text-align: right; font-style: normal; font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rt-pop-row[hidden] { display: none; }
.rt-pop-row:hover { background: var(--surface-2); }
.rt-pop-row.on { color: var(--accent); font-weight: 700; }
.rt-pop-row.on::after { content: "✓"; font-size: 12px; }
/* The one row that is an act rather than an answer — "+ New season" — at the
   head of the list, in the colour of a control. */
.rt-pop-new { color: var(--accent); font-weight: 700; border-bottom: 1px solid var(--border); border-radius: 8px 8px 0 0; margin-bottom: 2px; }
/* askText(): the one field of the app's own prompt sheet. Scoped through
   .modal because `.modal input` outranks a single class. */
.modal input.ask-text { width: 100%; margin: 4px 0 6px; font-size: 16px; font-weight: 600; }

/* ---- View: a bus opened to its stops ---- */
.rt-view .stu-row .rt-dot { width: 12px; height: 12px; margin-right: 10px; }
.rt-view.open > .stu-row { border-color: var(--rt-c); }
.rt-view-stops { list-style: none; margin: 0 0 8px; padding: 0 0 0 10px; border-left: 3px solid var(--rt-c); }
.rt-view-stops li { display: flex; gap: 10px; align-items: flex-start; padding: 6px 8px; border-bottom: 1px solid var(--border); }
.rt-view-stops li:last-child { border-bottom: 0; }
.rt-view-seq {
  flex: none; width: 22px; height: 22px; border-radius: 50%; background: var(--rt-c); color: #fff;
  font-size: 11px; font-weight: 800; display: inline-flex; align-items: center; justify-content: center;
}
.rt-view-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rt-view-name { font-size: 13px; font-weight: 600; }
.rt-view-name em { font-style: normal; color: var(--muted); font-weight: 400; margin-left: 6px; }
.rt-view-who { font-size: 13px; color: var(--text-dim); line-height: 1.5; }
.rt-view-who b { color: var(--text); }
.rt-view-sep { color: var(--border); margin: 0 6px; }
/* The sheet's two ends (0136): the lot and the school, as rows of the same
   list with a marker that isn't a stop number. */
.rt-view-end .rt-view-seq { background: var(--surface-2); color: var(--text-dim); font-size: 12px; }
.rt-view-end .rt-view-name { color: var(--text-dim); }
.rt-view-end .rt-view-name em { color: var(--text); font-weight: 700; }
/* Off the screen: the run's head carries Print / Excel for every bus on it,
   and an open bus carries the same pair for itself alone. */
.rt-view-gh { display: flex; align-items: center; gap: 6px; }
.rt-view-gh > span { flex: 1; min-width: 0; }
.rt-view-acts { display: flex; gap: 6px; padding: 0 0 10px 13px; }

/* ---- Changes ---- */
.rc-kid { padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); }
.rc-kid + .rc-kid { margin-top: 6px; }
.rc-kid-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.rc-kid-name { font-size: 17px; font-weight: 600; }
.rc-kid-sub { font-size: 12px; color: var(--muted); }
.rc-rides { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.rc-ride { display: flex; align-items: center; gap: 10px; }
.rc-ride-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.rc-ride-run { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); }
.rc-ride-bus { font-size: 13px; }
.rc-none { margin-top: 8px; font-size: 13px; color: var(--warning); }
.rt-unplaced .stu-geo { margin-bottom: 8px; }

/* the rules, in a sheet */
.rt-rules-body { padding: 0; }
.rt-rules .modal-actions { justify-content: flex-start; }

/* ---- bubbles on the map — the navigation screen's callouts, TINTED ----
   A bubble is a pale wash of the route it is about, edged a shade deeper,
   its title in the route's own dark: one glance says which line it belongs
   to. Its body stands BESIDE the road — up-right (`.ne`) or up-left (`.nw`)
   of the point it is tailed to, the side bubbleSide() found clear of the line
   — with the tail on the corner nearest the road. `--rt-c` is set inline per
   bubble; everything here is mixed from it. */
.rt-bubble {
  --rt-c: #5f6368;
  --bx: 10px; --by: 10px;
  --bubble-bg: color-mix(in srgb, var(--rt-c) 20%, #fff);
  --bubble-edge: color-mix(in srgb, var(--rt-c) 55%, #fff);
  position: absolute;
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  background: var(--bubble-bg); color: #3c4043;
  border: 1.5px solid var(--bubble-edge); border-radius: 12px; padding: 7px 11px;
  font-size: 13px; font-weight: 600; line-height: 1.15;
  box-shadow: 0 2px 6px rgba(0,0,0,.18), 0 8px 20px rgba(0,0,0,.12);
  cursor: pointer; user-select: none;
  transition: box-shadow .12s ease;
}
.rt-bubble.ne { transform: translate(var(--bx), calc(-100% - var(--by))); }
.rt-bubble.nw { transform: translate(calc(-100% - var(--bx)), calc(-100% - var(--by))); }
/* The tail: a corner of the same wash and edge, on whichever corner is
   nearest the road. */
.rt-bubble::after {
  content: ""; position: absolute; bottom: -7px; width: 12px; height: 12px;
  background: var(--bubble-bg); transform: rotate(45deg); border-radius: 0 0 2px 0;
  border-right: 1.5px solid var(--bubble-edge); border-bottom: 1.5px solid var(--bubble-edge);
}
.rt-bubble.ne::after { left: 7px; }
.rt-bubble.nw::after { right: 7px; }
.rt-bubble:hover { box-shadow: 0 4px 10px rgba(0,0,0,.22), 0 12px 26px rgba(0,0,0,.16); z-index: 2; }
.rt-bubble b { font-weight: 800; color: color-mix(in srgb, var(--rt-c) 70%, #000); }
.rt-bubble span { color: #3c4043; font-weight: 600; }
.rt-bubble em { font-style: normal; color: #5f6368; font-weight: 500; }
/* A stop's bubble clears the pin standing on its stop: further out, less up. */
.rt-bubble-stop { --bx: 16px; --by: 6px; font-size: 12.5px; padding: 5px 9px 5px 6px; border-radius: 10px; }
.rt-bubble-stop i {
  width: 18px; height: 18px; border-radius: 50%; flex: none; background: var(--rt-c);
  color: #fff; font-style: normal; font-size: 10.5px; font-weight: 800;
  display: inline-flex; align-items: center; justify-content: center;
}
.rt-bubble-stop span { color: #202124; }
.rt-bubble-stop em { font-size: 11px; }
/* A stop's bubble is drawn only while the pointer is on its pin: every stop
   labelled at once made a route read far busier than it is. It takes no
   pointer events, so appearing beside the pin can't end the hover that
   showed it. */
.rt-bubble.on-hover { display: none; pointer-events: none; }
.rt-bubble.on-hover.shown { display: inline-flex; z-index: 2; }

/* the season picker standing on a list screen */
.rt-listpick { display: flex; margin-bottom: 12px; max-width: 320px; }
/* On the list screens the picker sits on a .disp-body, which is --modal-bg. */
.rt-listpick .rt-pickwrap { --notch-bg: var(--modal-bg); }
.rt-listpick .rt-pop { width: max(100%, 300px); }

.rt-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  background: var(--rt-c, var(--muted));
}

/* On a desk the panel is a column down the right edge — card, tiles, editor —
   and the switches stand just left of it. Same markup; the stylesheet turns
   the sheet into a column and the strip of tiles into a grid. */
@media (min-width: 1024px) {
  .rt-stage { bottom: 0; }
  .rt-panel {
    /* As tall as what is in it, up to the stage: a card, not a wall. */
    left: auto; top: 12px; right: 12px; bottom: auto; width: 400px; max-height: calc(100% - 24px);
    border-radius: var(--radius); border: 1px solid var(--border);
    box-shadow: 0 8px 28px rgba(0,0,0,.2);
  }
  .rt-panel::before { display: none; }
  .rt-panel .rt-pickwrap.open .rt-pop { top: calc(100% + 4px); bottom: auto; border-radius: 6px 6px 12px 12px; }
  .rt-hud { padding: 12px 14px; }
  /* A bus open is the job: the panel takes the full height so the stop list
     has room to be worked in. */
  .rt-stage.has-side .rt-panel { bottom: 12px; max-height: none; }
  /* THE CARD KEEPS ITS HEIGHT — the season and run, the timing, the version
     chips and the progress line are on it — and the TILES take what is left
     and scroll. It was the other way round: the dock's 44% cap was a
     percentage of a panel that has a max-height and no height, which a
     percentage max-height resolves against as nothing, so twenty-two tiles
     took the whole panel and squeezed the card to its picker row. The card's
     own cap is in viewport units for the same reason. */
  .rt-hud { flex: 0 0 auto; max-height: 68vh; }
  .rt-dock { flex: 0 1 auto; min-height: 0; overflow-y: auto; scrollbar-width: thin; padding: 0 10px 10px; }
  .rt-dock-strip {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: 12px 8px; align-items: end; overflow: visible; padding: 12px 2px 2px;
  }
  .rt-bus { width: 100%; }
  /* The menu opens UPWARD here too: the tiles sit at the foot of the panel,
     and a menu dropping below them ran off the bottom of the window. */
  .rt-mapctl { right: 428px; }
  /* Map only on a desk: the column becomes the phone's strip along the foot
     of the window, one row of cards scrolling sideways, and the switches
     come back to the map's own edge. */
  .rt-stage.map-only .rt-mapctl { right: 12px; }
  .rt-stage.map-only .rt-panel {
    left: 12px; right: 12px; top: auto; bottom: 12px; width: auto;
    border-radius: 14px;
  }
  .rt-stage.map-only.has-side .rt-panel { bottom: 12px; }
  .rt-stage.map-only .rt-dock { flex: none; overflow: visible; padding: 0 10px 8px; }
  .rt-stage.map-only .rt-dock-strip {
    display: flex; gap: 10px; align-items: flex-end; overflow-x: auto; padding: 12px 2px 2px;
  }
  .rt-stage.map-only .rt-bus { width: 104px; }
  /* The way back to every bus stands at the strip's head and stays there as
     the strip scrolls — at its tail, twenty cards to the right, it was a
     button nobody could see. */
  .rt-stage.map-only .rt-bus-all { order: -1; position: sticky; left: 0; z-index: 1; }
}

/* ---- one bus, open for work ---- */
.rt-editor {
  border: 1px solid var(--border); border-radius: var(--radius);
  border-top: 3px solid var(--rt-c); background: var(--surface);
  padding: 12px; margin-top: 4px;
}
.rt-editor-head { display: flex; align-items: center; gap: 8px; }
.rt-name {
  flex: 1 1 140px; min-width: 120px;
  background: transparent; border: 1px solid transparent; border-radius: 8px;
  color: var(--text); font-size: 15px; font-weight: 600; padding: 4px 6px;
}
.rt-name:hover { border-color: var(--border); }
.rt-name:focus { border-color: var(--accent); outline: none; background: var(--surface-2); }
.rt-editor-nums { font-size: 12.5px; color: var(--text-dim); margin: 4px 0 0 38px; font-variant-numeric: tabular-nums; }
.rt-editor-nums b { color: var(--text); }
.rt-editor-acts {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; flex-wrap: wrap; margin: 8px 0 10px;
}
.rt-solo { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--text-dim); }
.rt-solo input { width: 16px; height: 16px; }

/* THE DIRECTIONS LIST. Marker circles down a dotted line, the place beside
   each, a grip and an × on the right, the school pinned at the end, the trip's
   total along the bottom — the list every navigation app draws for a drive
   with stops, because it is the one shape everybody already knows how to
   re-order. Rows are separated by a hairline under the TEXT, not boxed: the
   dotted line is what says these are one route. */
.rt-stops { list-style: none; margin: 0; padding: 0; }
.rt-stop { position: relative; }
/* The dotted line runs the marker column, from the first marker's middle to
   the last's; each marker paints over it. */
.rt-stop::before {
  content: ""; position: absolute; left: 12px; top: 0; bottom: 0;
  border-left: 2px dotted var(--text-dim); opacity: .55;
}
.rt-stop:first-child::before { top: 20px; }
.rt-stop:last-child::before { bottom: auto; height: 20px; }
.rt-stop:not(:last-child)::after {
  content: ""; position: absolute; left: 36px; right: 4px; bottom: 0;
  border-bottom: 1px solid var(--border);
}
.rt-stop.dragging { opacity: .5; }
.rt-stops.reordering .rt-stop::before, .rt-stops.reordering .rt-stop::after { display: none; }
.rt-stop-head { display: flex; align-items: center; gap: 8px; padding: 7px 0 7px 0; min-height: 40px; }
/* The marker IS the control. A stop's number is the fastest way to say where
   it belongs on a route of twenty-four, and a marker you can only read means
   dragging past eighteen rows. Styled to stay a marker — the outlined circle
   of a directions list, in the route's colour — no spinners, no box until
   you touch it. */
.rt-seq {
  position: relative; z-index: 1;
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  text-align: center; padding: 0;
  background: var(--surface); color: var(--rt-c); font-size: 11px; font-weight: 700;
  border: 2px solid var(--rt-c); -moz-appearance: textfield; appearance: textfield;
  cursor: text;
}
.rt-seq::-webkit-outer-spin-button,
.rt-seq::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.rt-seq:hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--rt-c) 25%, transparent); }
.rt-seq:focus { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent); border-color: var(--accent); }

/* The school: the destination pin. Not a stop — nothing to drag, type or ×. */
.rt-end .rt-stop-head { min-height: 36px; }
.rt-end .rt-stop-open { cursor: default; }
.rt-end .rt-stop-name { color: var(--text-dim); }
/* The lot: where the day starts. A grey pin, since the danger red is the school's. */
.rt-lot .rt-pin::before { background: var(--text-dim); box-shadow: inset 0 0 0 4px var(--text-dim), inset 0 0 0 6px #fff; }
.rt-pin {
  position: relative; z-index: 1; flex: none; width: 26px; height: 26px; display: grid; place-items: center;
  background: var(--surface);
}
.rt-pin::before {
  content: ""; width: 16px; height: 16px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg);
  background: var(--danger); box-shadow: inset 0 0 0 4px var(--danger), inset 0 0 0 6px #fff;
}

/* The grip: picked up at once, no hold. It is the one thing on the row that
   must not be read as a scroll first. */
.rt-grip {
  flex: none; width: 26px; height: 26px; display: grid; place-items: center;
  color: var(--muted); font-size: 18px; line-height: 1; cursor: grab;
  touch-action: none; user-select: none; -webkit-user-select: none;
}
.rt-grip:hover { color: var(--text); }
.rt-stops.reordering .rt-grip { cursor: grabbing; }
.rt-mini.rt-x { border-color: transparent; background: transparent; font-size: 17px; color: var(--muted); }
.rt-mini.rt-x:hover { color: var(--danger); border-color: transparent; }

/* Total trip: the line under the list, as the navigation app writes it. */
.rt-trip {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: 13px; color: var(--text-dim);
}
.rt-trip b { color: var(--text); }
.rt-trip em { font-style: normal; color: var(--muted); font-size: 11.5px; }
.rt-trip em.bad { color: var(--danger); font-weight: 700; font-size: 13px; }

/* Drawing an order is a MODE, so the bar that says so sits across the top of
   the list and carries the only two ways out of it. */
.rt-draw-bar { margin-bottom: 8px; }
.rt-stop-name { flex: 1 1 auto; font-size: 13px; font-weight: 600; }
.rt-manual { font-style: normal; font-size: 11px; color: var(--muted); font-weight: 400; }
/* The stop the measured road turns the bus round at. */
.rt-uturn {
  display: block; width: fit-content; margin-top: 3px;
  font-style: normal; font-size: 11px; font-weight: 700; color: var(--danger); white-space: normal;
  padding: 1px 6px; border-radius: 6px; background: color-mix(in srgb, var(--danger) 12%, transparent);
  border: 0; font-family: inherit; line-height: 1.5; text-align: left;
}
/* When the road offers the fix, the chip is the button that applies it. */
button.rt-uturn { cursor: pointer; border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent); }
button.rt-uturn:hover { background: var(--danger); color: #fff; }
.rt-stop-acts { display: flex; gap: 2px; flex: none; align-items: center; }
.rt-mini {
  width: 26px; height: 26px; border-radius: 7px; flex: none;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-dim); font-size: 13px; line-height: 1; cursor: pointer;
}
.rt-mini:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }
.rt-mini:disabled { opacity: .3; cursor: default; }


/* A stop is a BUTTON that opens its children. Collapsed it is one line — the
   corner and who waits there — because most edits are at stop level and a
   route of twenty-four stops with every child listed is two thousand pixels of
   scrolling to reach stop nine. */
.rt-stop-open {
  flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px;
  background: none; border: 0; padding: 0; text-align: left; cursor: pointer; color: inherit;
}
.rt-stop-who {
  font-size: 11px; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rt-stop.open .rt-stop-name { color: var(--rt-c); }
.rt-stop-open:hover .rt-stop-name { color: var(--accent); }

.rt-kids { list-style: none; margin: 0; padding: 0 10px 8px 36px; }
.rt-kids li {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 12px; color: var(--text-dim); padding: 2px 0;
}
.rt-kids em { font-style: normal; color: var(--muted); }

/* The move sheet: what taking this would do to each bus. */
.rt-movelist .stu-row[data-tight] .stu-sub b { color: var(--danger); }

@media (prefers-reduced-motion: reduce) {
  .rt-bus-card, .rt-fleet-bus { transition: none; }
  .rt-bus-card:hover, .rt-fleet-bus:hover { transform: none; }
  .rt-bus.busy .rt-bus-card, .rt-bus.busy .rt-bus-card::before, .rt-progress-dot,
  .rt-progress.indeterminate .rt-progress-bar i { animation: none; }
  .rt-bus.busy .rt-bus-card { box-shadow: 0 0 0 1.5px var(--rt-c), 0 6px 14px rgba(0,0,0,.16); }
}


/* ============================================================
 * THE FAMILY PAGE
 *
 * The brief was the opposite of the program this replaces: nine stacked grids
 * on one screen, every one dense, so everything was visible and almost nothing
 * was legible. Here each question is a titled block with room around it, the
 * ANSWER is the largest thing in the block, and anything merely true is not
 * drawn at all.
 *
 * Hebrew is a sibling of the Latin name, never a replacement: same line, one
 * step dimmer, its own direction. A name is one fact in two scripts.
 * ============================================================ */

.fam-back {
  display: block; width: fit-content; text-align: left;
  background: none; border: 0; padding: 4px 0; margin: 0 0 4px;
  color: var(--text-dim); font-size: 13px; cursor: pointer;
}
.fam-back:hover { color: var(--accent); }

/* ---- the hero: one card, the whole household ----
   The name is the biggest thing on the page, in BOTH scripts at the same
   weight — a family is one name written twice, not a name with a translation
   under it — and every fact about the household (parents, numbers, contacts,
   notes) is a cell in the grid below it, so the top of the page IS the family
   and the children start under a line. */
.fam-hero {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 18px 14px; margin-bottom: 6px;
}
.fam-hero-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.fam-hero-actions { display: flex; gap: 6px; flex: 0 0 auto; }

/* A move: planned is a standing notice (the driver goes to the OLD kerb until
   that day, so it must be seen every time); applied is folded, since where
   everybody landed is read once. Amber for the planned one — it is a thing
   with a date, not an error. */
.fam-move {
  margin-top: 10px; padding: 9px 12px; border-radius: var(--radius-sm);
  font-size: 13px; line-height: 1.4;
  background: var(--surface); border: 1px solid var(--border);
}
.fam-move.planned {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px;
  border-color: color-mix(in srgb, var(--warning) 55%, transparent);
  background: color-mix(in srgb, var(--warning) 9%, var(--surface));
}
.fam-move.planned .fam-cell-btn { margin-top: 0; }
.fam-move summary { cursor: pointer; }
.fam-move summary::marker { color: var(--text-dim); }
.fam-move-warn { color: var(--warning); }
.fam-move-lines { margin: 8px 0 2px; padding-inline-start: 18px; display: flex; flex-direction: column; gap: 4px; }
.fam-move-blurb { margin: 0 0 12px; font-size: 13px; color: var(--text-dim); line-height: 1.45; }
.fam-names { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 14px; min-width: 0; }
.fam-names h2 {
  margin: 0; font-size: 26px; font-weight: 800; letter-spacing: -0.3px; line-height: 1.15;
}
.fam-name-he { direction: rtl; unicode-bidi: isolate; font-size: 28px !important; }
/* No divider between the two scripts — a rule on the Hebrew half strands
   itself at the start of the line the moment a long surname wraps on a phone,
   and the gap already separates them. */
.fam-addr { margin: 6px 0 0; font-size: 13.5px; color: var(--text-dim); display: flex; align-items: center; gap: 6px; }
.fam-addr-pin { font-size: 12px; }
.fam-marks { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }

/* The Hebrew half of a name. Dimmer and a shade larger — the script sits
   smaller than Latin at the same point size, so equal weight needs unequal
   numbers. `unicode-bidi: isolate` keeps a name ending in a bracket or a comma
   from having that punctuation dragged to the wrong end of the line. */
.heb {
  font-size: 1.06em; color: var(--text-dim); font-weight: 500;
  direction: rtl; unicode-bidi: isolate; margin-inline-start: 6px;
}

.fam-info {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 8px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border);
  align-items: start;
}
.fam-cell {
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 9px 12px;
}
.fam-cell-wide { grid-column: 1 / -1; }
.fam-label {
  font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted);
}
.fam-val { font-size: 15px; font-weight: 600; }
.fam-notes { font-size: 13.5px; white-space: pre-wrap; }
.fam-none { margin: 0; font-size: 13px; color: var(--muted); }

/* Numbers and contacts: one line each inside the cell. The number is the
   thing being looked for, so it carries the weight; what it is for sits
   beside it, dim. */
.fam-cell-list { gap: 5px; }
.fam-line { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; }
.fam-line-what { font-size: 12px; color: var(--text-dim); }
.fam-num {
  font-size: 15px; font-weight: 600; color: var(--text);
  text-decoration: none; font-variant-numeric: tabular-nums;
}
.fam-num:hover { color: var(--accent); }
.fam-cell-btn {
  align-self: flex-start; margin-top: 3px; padding: 0; background: none; border: 0;
  font-size: 12px; font-weight: 600; color: var(--accent); cursor: pointer;
}
.fam-cell-btn:hover { text-decoration: underline; }

/* ---- the children ---- */
.fam-sec { margin-top: 18px; }
.fam-sec-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.fam-sec-head h4 { margin: 0; font-size: 15px; font-weight: 700; }
.fam-count {
  font-size: 11px; font-weight: 600; color: var(--text-dim);
  background: var(--surface-2); border-radius: 9px; padding: 1px 7px; margin-inline-start: 4px;
}

/* A gender is a BLOCK in its own colour, declared once: `--kid-c` is the hue
   and `--kid-wash` its tint, and the head, the grade tags and every card in
   the block read them — so a boy is blue on every part of the page by
   construction. Blue and pink are the same two the run chips already wear,
   because a boy's card and the boys run on it should be one colour. */
.fam-kgroup {
  --kid-c: var(--text-dim); --kid-wash: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 10px 12px 12px; margin-bottom: 10px;
  background: linear-gradient(var(--kid-wash), var(--kid-wash)), var(--surface);
}
.fam-kgroup.boys  { --kid-c: #63a4ff; --kid-wash: rgba(56,132,255,.10); }
.fam-kgroup.girls { --kid-c: #e878bd; --kid-wash: rgba(219,88,168,.10); }
:root[data-theme="light"] .fam-kgroup.boys  { --kid-c: #2563eb; --kid-wash: rgba(37,99,235,.08); }
:root[data-theme="light"] .fam-kgroup.girls { --kid-c: #c2418f; --kid-wash: rgba(194,65,143,.08); }
.fam-kgroup-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
  font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: var(--kid-c);
}
.fam-kgroup-head::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--kid-c); opacity: .35; }
.fam-kgroup-n {
  font-size: 11px; font-weight: 700; letter-spacing: 0;
  background: var(--kid-c); color: #fff; border-radius: 9px; padding: 1px 7px;
}

/* Inside a block, one row per grade: the grade as a tag down the left and
   the children in it as cards beside it. */
.fam-kgrade { display: flex; align-items: flex-start; gap: 10px; padding: 6px 0; }
.fam-kgrade + .fam-kgrade { border-top: 1px dashed color-mix(in srgb, var(--kid-c) 35%, transparent); }
.fam-kgrade-tag {
  flex: 0 0 auto; min-width: 64px; margin-top: 8px; text-align: center;
  font-size: 12px; font-weight: 700; padding: 3px 9px; border-radius: 999px;
  background: var(--surface); color: var(--kid-c);
  border: 1px solid color-mix(in srgb, var(--kid-c) 55%, transparent);
  unicode-bidi: isolate; white-space: nowrap;
}
.fam-kcards { display: flex; flex-wrap: wrap; gap: 8px; flex: 1 1 auto; min-width: 0; }

.kid {
  display: flex; flex-direction: column; gap: 6px;
  flex: 1 1 240px; max-width: 480px;
  background: var(--surface); border: 1px solid color-mix(in srgb, var(--kid-c) 40%, var(--border));
  border-inline-start: 4px solid var(--kid-c);
  border-radius: var(--radius-sm); padding: 10px 12px;
  text-align: left; cursor: pointer; color: inherit;
  transition: background .15s ease, border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.kid:hover {
  border-color: var(--kid-c); transform: translateY(-1px);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--kid-c) 22%, transparent);
}
.kid:hover .stu-chev { color: var(--kid-c); transform: translateX(2px); }
.kid.off { opacity: .6; }
.kid-top { display: flex; align-items: center; gap: 8px; }
.kid-name { font-size: 15px; font-weight: 700; flex: 1 1 auto; min-width: 0; }
.kid-name .heb { color: var(--text); font-weight: 600; }
.kid-sub { font-size: 12px; color: var(--text-dim); }
.kid .stu-chev { transition: color .15s ease, transform .15s ease; }
.kid-term { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.kid-warn { font-size: 12px; color: var(--warning); }

@media (max-width: 480px) {
  .fam-hero { padding: 14px 14px 12px; }
  .fam-names h2 { font-size: 23px; }
  .fam-name-he { font-size: 25px !important; }
  /* Column direction: stretch, or the card row shrinks to its content. */
  .fam-kgrade { flex-direction: column; align-items: stretch; gap: 6px; }
  .fam-kgrade-tag { margin-top: 0; align-self: flex-start; }
}

/* A run the child is on. Tinted by category, because boys and girls runs are
   never mixed and the colour says which half of the timetable you are reading
   without spelling it out again on every chip. */
.kid-runs { display: flex; flex-wrap: wrap; gap: 5px; }
.run-chip {
  font-size: 11px; font-weight: 600; padding: 3px 9px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border); color: var(--text-dim);
  white-space: nowrap;
}
.run-chip.boys  { border-color: rgba(56,132,255,.45);  color: #63a4ff; }
.run-chip.girls { border-color: rgba(219,88,168,.45);  color: #e878bd; }

/* THE BUS on a run. One line per run on the card: the run's chip, then the
   bus the child is actually on for it — its number bold in the route's own
   colour (the same colour the map draws it in), the direction, the time on
   the stop and the stop's name. That is the fact the page is opened for. */
.kid-runs { flex-direction: column; gap: 5px; }
.kid-run { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 8px; }
.kid-bus {
  --bus-c: var(--accent);
  display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0 6px;
  font-size: 12px; color: var(--text-dim);
  padding: 2px 8px 2px 0; border-inline-start: 3px solid var(--bus-c); padding-inline-start: 7px;
}
.kid-bus b { color: var(--bus-c); font-weight: 800; font-size: 12.5px; }
.kid-bus-dir { font-size: 10px; font-weight: 700; letter-spacing: .06em; color: var(--muted); }
.kid-bus-when { font-variant-numeric: tabular-nums; color: var(--text); }
.kid-bus-from { font-size: 11px; color: var(--muted); }

/* ---- ticking a child onto runs ---- */
.kd-runs { display: flex; flex-direction: column; gap: 12px; margin: 4px 0 8px; }
.kd-run-group { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.kd-run-cat {
  width: 100%; font-size: 10px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--accent);
}
.run-tick {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; padding: 5px 10px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-dim); cursor: pointer;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.run-tick:hover { border-color: var(--accent); color: var(--text); }
.run-tick input { width: 14px; height: 14px; margin: 0; }
.run-tick.on { background: var(--accent-soft); border-color: var(--accent); color: var(--text); font-weight: 600; }
/* A run the child is on wears an ✕ (tap takes them off it); the picker under
   "+ Add a run" is the same chip unlit. */
.run-tick { font-family: inherit; }
.run-tick-x { font-size: 10px; color: var(--text-dim); margin-inline-start: 2px; }
.run-tick.on:hover .run-tick-x { color: var(--danger); }
.kd-pick { padding: 8px 10px; border: 1px dashed var(--border); border-radius: var(--radius-sm); }

/* ---- the phone editor ---- */
.ph-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.ph-row .ph-label { flex: 0 1 130px; min-width: 0; }
.ph-row .ph-num { flex: 1 1 140px; min-width: 0; }
.ph-sms { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-dim); white-space: nowrap; }
.ph-sms input { width: 15px; height: 15px; }

@media (prefers-reduced-motion: reduce) {
  .kid, .fam-phone, .run-tick { transition: none; }
  .kid:hover { transform: none; }
  .kid:hover .stu-chev { transform: 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.
 * ============================================================ */

/* ============================================================
 * MESSAGES — the list and the conversation, side by side
 *
 * One markup, two layouts, no width read in JavaScript: `.msg-split` always
 * holds both panes and this decides which are shown. Narrow (a phone upright)
 * shows exactly one — the list, or the conversation once `.has-open` is on the
 * wrapper — which is the behaviour every messaging app falls back to at that
 * width. From tablet/landscape up they sit beside each other.
 *
 * The breakpoint is 768px and NOT the app's 1024px desktop one: a tablet held
 * upright is 768 across and has room for both panes, and a large phone turned
 * on its side (~850) is exactly when somebody expects to be given the second
 * one. It was 820 at first, which put a tablet upright on the phone side of
 * the line and drew one pane across a screen wide enough for two. The desktop
 * block that stands the tab bar up is a different question and keeps its own
 * number.
 * ============================================================ */
.msg-pane-thread { display: none; }
.msg-split.has-open .msg-pane-list { display: none; }
.msg-split.has-open .msg-pane-thread { display: block; }
/* "New message" is the screen's floating button (the FAB table in client.ts).
   With a conversation filling a phone it would sit on the composer's Send, and
   the list it starts things from is a back-arrow away — so it goes with the
   list. syncFab()'s inline display is only ever "" or "none", so this rule is
   not fighting it. Bounded above rather than undone in the split block: the
   desktop makes the button a pill with its own display, and a rule this
   specific restating one would flatten it. */
@media (max-width: 767.98px) {
  body:has(.msg-split.has-open) .fab { display: none; }
}
/* Only ever seen beside a list, so it never shows on a phone. */
.msg-none { display: none; }

/* The open row, which only means anything when the list is still on screen
   next to what it opened — a quiet ground rather than a brand band, because on
   a busy morning the list is mostly unread rows and a filled orange row would
   compete with every one of them. */
.chat-row.active { background: var(--surface-2); }

/* The channel, said once, on the row. */
.conv-ch {
  display: inline-block;
  margin-right: 5px;
  padding: 0 5px;
  border-radius: 4px;
  background: var(--bg-3);
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  vertical-align: 1px;
}

/* ============================================================
 * COLOUR HERE IS IDENTITY, NEVER STATUS.
 *
 * Every row, header and speaker carries --conv-c (and --conv-wash), set inline
 * from that person's own hue — the same number the roster tints their initials
 * circle with. ONE property, read by everything downstream, so somebody is one
 * colour in the list, in the header and over their words by construction rather
 * than by three rules happening to agree.
 *
 * The office and groups take the brand instead, being ours rather than a
 * person's. Nothing here borrows the status ramp (green / amber / red): those
 * mean something specific on every other screen in this app, and must not come
 * to mean "Chaim" on this one.
 * ============================================================ */

/* The open row gets a bar in its conversation's colour. Background is left to
   the rules that already own it — a filled band in a list that is mostly unread
   rows on a busy morning would shout over the thing it sits next to. */
.chat-row.active { box-shadow: inset 3px 0 0 var(--conv-c, var(--brand-600)); }

/* The conversation's header, washed in the colour of the row that opened it, so
   the two read as one thing rather than as a grey bar with a name on it. */
.chat-thread-head {
  gap: 10px;
  background: var(--conv-wash, transparent);
  border-bottom-color: color-mix(in srgb, var(--conv-c, var(--border)) 35%, var(--border));
}
.chat-thread-head .chat-av { width: 36px; height: 36px; font-size: 14px; }

/* Who is speaking, in their own colour. This line was already coloured — but
   always brand orange, so five people in a group were five identical orange
   names and the colour said nothing at all. */
.chat-msg-who { color: var(--conv-c, var(--brand-600)); }

/* A run of messages: the speaker's face in a column of its own, bubbles beside
   it. The gap element holds the continuation lines on the same left edge —
   without it every line after the first would step sideways. */
.chat-msg.has-face { flex-direction: row; align-items: flex-end; gap: 8px; }
.chat-msg.has-face .chat-msg-body { min-width: 0; max-width: 100%; }
.chat-av-gap { flex: none; width: 26px; }
/* An incoming bubble takes a breath of the speaker's colour — the base
   .chat-bubble rule reads --conv-c, and in a group each run sets its own on the
   .chat-msg (see chatBubble), so two people are two tints at a glance. */

/* The picker shows the same faces, so choosing somebody looks like the list you
   are about to find them in. */
.chat-pick .chat-av { width: 38px; height: 38px; font-size: 15px; }
.chat-member-who { display: inline-flex; align-items: center; gap: 8px; }


@media (min-width: 768px) {
  /* Below 1024 the tab bar is still across the BOTTOM of the window, so the
     split has to stop above it; the desktop block further down drops this to
     the padding alone, because there the bar has stood up into the left rail.
     The last 16px are the floating button: it hangs 92px + 56px up from the
     bottom edge, and the split's corner — where the composer's Send is — has
     to stop short of it. */
  :root { --msg-gutter: calc(var(--chat-nav-h) + 97px); }
  .msg-split {
    display: grid;
    grid-template-columns: minmax(260px, 320px) minmax(0, 1fr);
    /* A fixed frame with two scrollers inside it, so the page itself doesn't
       scroll and the composer stays put — the thing that makes a chat app feel
       like one. dvh for the same reason .chat-scroll uses it. */
    height: calc(100dvh - var(--topbar-h) - var(--subbar-h) - var(--msg-gutter, 62px));
    min-height: 420px;
    border: 1px solid var(--border);
    border-radius: 14px;
    overflow: hidden;
    background: var(--surface);
  }
  /* Both panes, always — .has-open is the phone's business. */
  .msg-split .msg-pane-list,
  .msg-split.has-open .msg-pane-list { display: flex; }
  .msg-split .msg-pane-thread,
  .msg-split.has-open .msg-pane-thread { display: flex; }
  .msg-pane-list {
    flex-direction: column;
    min-height: 0;
    border-right: 1px solid var(--border);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 12px;
  }
  .msg-pane-thread { flex-direction: column; min-height: 0; }

  .msg-pane-list .chat-row { padding: 10px 8px; border-radius: 10px; border-bottom: none; }
  .msg-pane-list .chat-row + .chat-row { margin-top: 2px; }
  /* Rounded rows here, so the shared rule's edge bar follows the corner; the
     ground is restated only because this selector outranks it. */
  .msg-pane-list .chat-row:hover {
    background: color-mix(in srgb, var(--conv-c, var(--surface-2)) 8%, var(--surface-2));
  }
  .msg-pane-list .chat-row.active { background: var(--bg-3); }
  /* The open row's ground here IS --bg-3, which is where the channel chip and
     the two neutral avatars live — the ones that are a KIND rather than a
     person, so repainting them argues with nothing. A face, an initials circle
     and the office keep their own colour on this row as on every other; the
     border they carry is what holds their edge against it. */
  .msg-pane-list .chat-row.active .conv-ch,
  .msg-pane-list .chat-row.active .chat-av-group,
  .msg-pane-list .chat-row.active .chat-av-sms { background: var(--surface-2); }
  /* Nothing to lift toward on the row that is already open. */
  .msg-pane-list .chat-row.active:hover .chat-av { transform: none; box-shadow: none; }

  .msg-pane-thread .chat-thread-head {
    padding: 12px 16px;
    margin: 0;
    background: var(--chrome-bg);
  }
  /* Nothing to go back TO — the list it came from is right there. */
  .msg-pane-thread .chat-back { display: none; }
  .msg-pane-thread .chat-scroll {
    height: auto;
    flex: 1;
    min-height: 0;
    padding: 8px 16px 4px;
  }
  /* Out of the viewport's bottom corner and into the pane it belongs to. */
  .msg-pane-thread .chat-composer {
    position: static;
    border-top: 1px solid var(--border);
    background: var(--surface);
  }
  .msg-pane-thread .sms-devnote { margin: 10px 16px 0; }

  .msg-none {
    display: flex;
    flex: 1;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    color: var(--text-dim);
  }
  .msg-none-mark { font-size: 34px; opacity: 0.5; }
  .msg-none-line { font-size: 14px; }
}

/* Nothing on a conversation row moves to say anything — the avatar's nudge is
   emphasis on grounds and colours that already carry the whole message. So it
   is the first thing to go, and nothing is lost with it. */
@media (prefers-reduced-motion: reduce) {
  .chat-row:hover .chat-av,
  .chat-pick:hover .chat-av { transform: none; }
}



/* ============================================================
   Sending a driver for a test — the clinic authorization
   (openTestAuthorizationModal, and the signature it goes out under).

   Almost all of this is the dialog's tick groups. It borrows the modal's own
   controls — `.check-row` for every tick, the notched label/field pair for the
   date and the location — so the only new shapes are the group heading, the
   one-of-N picker and the summary line.
   ============================================================ */

/* The button in the Drug Test drawer. Violet, because the four tints already in
   that row are spoken for and this is a fifth kind of act: not printing, not
   uploading, not downloading — sending something out of the building. */
.big-btn-authorize {
  background: rgba(139,92,246,0.15);
  border-color: rgba(139,92,246,0.45);
  color: #c4b5fd;
}
:root[data-theme="light"] .big-btn-authorize {
  background: rgba(139,92,246,0.13);
  border-color: rgba(139,92,246,0.5);
  color: #6d28d9;
}

/* Why it can't be sent yet, above the form rather than after it. */
.modal .ta-blocked {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: 10px;
  font-size: 13px;
  line-height: 1.45;
  background: rgba(245,158,11,0.12);
  border: 1px solid rgba(245,158,11,0.4);
  color: var(--text);
}
.modal .linkish {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--accent);
  font-weight: 700;
  cursor: pointer;
  text-decoration: underline;
}

/* Date and location: two facts, one row, stacked on a phone. */
.modal .ta-row2 { display: flex; gap: 12px; }
.modal .ta-row2 > div { flex: 1; min-width: 0; }
@media (max-width: 460px) { .modal .ta-row2 { flex-direction: column; gap: 0; } }

/* One group of ticks per kind of test. The rule above the heading is what
   separates them; a card each would be four boxes in a dialog that is already
   a box. */
.modal .ta-group { margin-top: 18px; padding-top: 12px; border-top: 1px solid var(--border); }
.modal .ta-group-h {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 8px;
}
/* Tighter than a free-standing check-row: these come six to a group. */
.modal label.check-row.ta-check { margin-top: 0; margin-bottom: 0; padding: 4px 0; }

/* One-of-N: the panel size, the alcohol kind. "—" is the first option and the
   default, so not choosing is a visible answer rather than an empty row. */
.modal .ta-picker { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.modal .ta-opt {
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.modal .ta-opt.on {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
/* "—" lit is still an ANSWER — the office has said this test isn't wanted — so
   it stays a filled pill rather than looking unchosen. But it is not orange:
   the same rule the Notifications screen draws, where "Off" is grey because
   brand orange on that row means something will happen. */
.modal .ta-opt[data-value=""].on {
  background: var(--bg-3);
  border-color: var(--border);
  color: var(--text);
}
.modal .ta-other { margin-top: 6px; }

/* What is on the form, in one sentence — the same sentence the email and the
   send log carry. It sits directly above the send button because it is the
   last thing to read before pressing it. */
.modal .ta-summary {
  margin-top: 16px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 13px;
  line-height: 1.45;
  color: var(--text);
}
.modal .ta-summary-empty { color: var(--text-dim); font-style: italic; }

/* Preview on the left, Cancel/Send on the right: one of these three is not
   like the other two, and putting it in the row anyway made it read as a third
   way to finish. */
.modal .modal-actions-split { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.modal .modal-actions-split > span { display: flex; gap: 8px; }

/* Settings → Organization → the signature on file. Shown at something near the
   size it is stamped, on white, because the one thing a "signature on file ✓"
   line cannot tell you is whether what's on file is legible. */
.org-sig { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--border); }
.org-sig-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.org-sig-btns { display: flex; gap: 8px; }
.org-sig-shown {
  margin: 10px 0 6px;
  padding: 10px 14px;
  background: #ffffff;
  border: 1px solid var(--border);
  border-radius: 10px;
  max-width: 420px;
}
.org-sig-shown img { display: block; max-width: 100%; max-height: 90px; }


/* ============================================================================
   Drivers → Tests — the test calendar (src/exam-appointments.ts)

   The outstanding-paperwork report's panel (.disp-body.treq) holding a WEEK:
   seven columns of the examiner's hours with the booked tests standing in
   them. One colour per STATE, declared once on the block's class and read by
   the block, the key, the row's pill and the sheet's pill alike — waiting is
   quiet, "needs a time" is the amber of something to do, confirmed is green,
   done is the blue a filed document wears elsewhere, missed is red. Brand
   orange stays on the controls. Under 768px the columns stack and the blocks
   stop being positioned: a phone reads a week as seven short lists.
   ============================================================================ */
.examq .rep-tone-lapsed { --rep-c: var(--danger);  --rep-fade: 1; }
.examq .rep-tone-ok     { --rep-c: var(--success); --rep-fade: 1; }

.exam-auto { display: inline-flex; align-items: center; gap: 6px; margin-left: 10px; font-size: 12.5px; color: var(--text-dim); cursor: pointer; white-space: nowrap; }
.exam-auto input { margin: 0; accent-color: var(--accent); }
.treq-blurb .exam-auto { display: flex; margin: 6px 0 0; }

/* ============================================================================
   DRIVERS → RANDOMS (0152). The paperwork report's panel and row recipe, and
   COLOUR THAT MEANS THE ACT (the office's taste, 2026-09-23 — colour-coded,
   but professional): one muted hue per job, declared once as `--c-*` on the
   screen and on its dialogs (`.rnd-modal`), and read by every button, tile,
   pill, chip and row dot so a colour says the same thing wherever it is.
     blue   = schedule / send the form      teal   = text them · Drug
     green  = tested / done                 violet = Drug + alcohol
     amber  = waiting on the driver · reopen
     red    = needs you · remove            slate  = the neutral door
   Buttons follow the Extra Work wizard's big answers (.ew-big): a wash of the
   hue, an edge mixed into the border, and the TEXT the page's own — colour in
   the text read too sharp (the office, 2026-09-23). They carry
   their icon — the office likes them; solid only
   for the one act a panel is for. Orange stays the brand's "do it" button a
   dialog ends on, and is never a status.
   ============================================================================ */
:root {
  --c-blue: #6b9ce8; --c-teal: #4fb3a9; --c-green: #4cb782; --c-amber: #d4a24c;
  --c-red: #e0706c;  --c-violet: #9d8fe0; --c-slate: #9aa4b1;
}
:root[data-theme="light"] {
  --c-blue: #2f5fae; --c-teal: #1f7a72; --c-green: #2d7a4f; --c-amber: #8a6214;
  --c-red: #b23b37;  --c-violet: #5b4ab0; --c-slate: #4b5563;
}

/* Buttons. Scoped through the screen and the dialog wrapper so they outrank
   `.modal button` (see the modal specificity note in memory). */
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn {
  --k: var(--c-slate);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 13px; border-radius: 10px; font-size: 13px; font-weight: 700; line-height: 1.2;
  color: var(--text); background: color-mix(in srgb, var(--k) 12%, var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--k) 45%, var(--border));
  cursor: pointer; white-space: nowrap;
  transition: transform .12s, box-shadow .12s, background .12s, border-color .12s;
}
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn:hover:not(:disabled) {
  transform: translateY(-1px); box-shadow: var(--shadow-control);
  background: color-mix(in srgb, var(--k) 22%, var(--surface));
}
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn:active { transform: none; }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn:disabled { opacity: .5; cursor: default; }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn:focus-visible { outline: 2px solid var(--k); outline-offset: 2px; }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-solid { border-color: var(--k); background: color-mix(in srgb, var(--k) 26%, var(--surface)); }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-solid:hover:not(:disabled) { background: color-mix(in srgb, var(--k) 32%, var(--surface)); }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-sm { padding: 5px 10px; font-size: 12.5px; }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-xs { padding: 3px 8px; font-size: 12px; border-radius: 6px; }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-blue { --k: var(--c-blue); }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-teal { --k: var(--c-teal); }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-green { --k: var(--c-green); }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-amber { --k: var(--c-amber); }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-red { --k: var(--c-red); }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-slate { --k: var(--c-slate); }
:is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn-violet { --k: var(--c-violet); }

/* A name's state, one hue each — the row's dot and edge, the pill, the tile. */
.randq .rnd-bad,  .rnd-modal .rnd-bad  { --rep-c: var(--c-red);   --rep-fade: 1; }
.randq .rnd-wait, .rnd-modal .rnd-wait { --rep-c: var(--c-amber); --rep-fade: 1; }
.randq .rnd-set,  .rnd-modal .rnd-set  { --rep-c: var(--c-blue);  --rep-fade: 1; }
.randq .rnd-done, .rnd-modal .rnd-done { --rep-c: var(--c-green); --rep-fade: 1; }
.randq .rnd-off,  .rnd-modal .rnd-off  { --rep-c: var(--c-slate); --rep-fade: .8; }
.randq .rep-row[class*="rnd-"] { box-shadow: inset 3px 0 0 var(--rep-c); }

.rnd-pill {
  --k: var(--c-slate);
  display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 700;
  color: var(--k); background: color-mix(in srgb, var(--k) 13%, transparent);
  border: 1px solid color-mix(in srgb, var(--k) 30%, transparent); white-space: nowrap;
}
.rnd-pill-bad  { --k: var(--c-red); }
.rnd-pill-wait { --k: var(--c-amber); }
.rnd-pill-set  { --k: var(--c-blue); }
.rnd-pill-done { --k: var(--c-green); }
.rnd-pill-off  { --k: var(--c-slate); }

/* Drug / Drug + alcohol — the same two chips on a row, in the sheet and on
   the picker's count. */
.rnd-kind {
  --k: var(--c-teal);
  display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 5px;
  font-size: 11px; font-weight: 700; letter-spacing: .01em; vertical-align: 1px;
  color: var(--k); background: color-mix(in srgb, var(--k) 12%, transparent);
}
.rnd-kind-drug { --k: var(--c-teal); }
.rnd-kind-alc  { --k: var(--c-violet); }
.rnd-count-row .rnd-kind:first-child, .rnd-sheet-state .rnd-kind { margin-left: 0; }

/* The list's head: its name, its deadline, and its two buttons. */
.randq .rnd-ghead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.treq.randq .rnd-ghead > span { background: none; border: 0; padding: 0; min-width: 0; margin: 0; font-size: inherit; color: inherit; }
.treq.randq .rnd-ghead > .rnd-due { font-size: 12px; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--text-dim); }
.treq.randq .rnd-ghead > .rnd-due.past { color: var(--c-red); }
.treq.randq .rnd-ghead > .rnd-acts { margin-left: auto; display: inline-flex; gap: 6px; }
.treq.randq .rep-ghead .rnd-pill { margin-left: auto; }

/* The four tiles are FILTERS: tap one to cut the rows to it, again to clear. */
.randq .rnd-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin: 12px 0; }
.randq .rnd-tile {
  --k: var(--c-slate);
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left;
  padding: 10px 12px; border-radius: 10px; cursor: pointer;
  background: color-mix(in srgb, var(--k) 7%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--k) 22%, var(--border));
  box-shadow: inset 3px 0 0 var(--k);
  transition: background .15s ease, border-color .15s ease;
}
.randq .rnd-tile:hover { background: color-mix(in srgb, var(--k) 13%, var(--surface)); }
.randq .rnd-tile.on { background: color-mix(in srgb, var(--k) 17%, var(--surface)); border-color: var(--k); }
.randq .rnd-tile.zero { opacity: .55; }
.randq .rnd-tile-n { font-size: 22px; font-weight: 800; line-height: 1.1; color: var(--k); font-variant-numeric: tabular-nums; }
.randq .rnd-tile-l { font-size: 12px; font-weight: 600; color: var(--text-dim); }
.randq .rnd-tile-bad  { --k: var(--c-red); }
.randq .rnd-tile-wait { --k: var(--c-amber); }
.randq .rnd-tile-set  { --k: var(--c-blue); }
.randq .rnd-tile-done { --k: var(--c-green); }
@media (max-width: 480px) { .randq .rnd-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.randq .rnd-filtered { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; font-size: 12.5px; color: var(--text-dim); }
/* A list of names and a few buttons: past this it is a band of whitespace
   between a name and its status. */
.disp-body.randq { max-width: 760px; }

/* The empty state: a quiet card with the one act on it and the paste route
   beside it. Tinted, not filled — the same weight as every other button here. */
.randq .rnd-empty {
  display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center;
  width: 100%; max-width: 580px; margin: 14px auto 4px; padding: 26px 20px 22px; border-radius: 14px;
  background: var(--surface); border: 1px dashed var(--border);
}
.randq .rnd-empty-badge {
  display: grid; place-items: center; width: 48px; height: 48px; border-radius: 50%; font-size: 22px;
  background: var(--surface-2); border: 1px solid var(--border);
}
.randq .rnd-empty-h { margin: 2px 0 6px; font-size: 15px; font-weight: 700; color: var(--text); }
.randq .rnd-empty-acts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; width: 100%; }
@media (max-width: 480px) { .randq .rnd-empty-acts { grid-template-columns: 1fr; } }
.randq .rnd-cta {
  --k: var(--c-blue);
  display: flex; align-items: center; gap: 10px; text-align: left; min-height: 58px;
  padding: 9px 14px 9px 10px; border-radius: 12px; cursor: pointer; color: var(--text);
  background: color-mix(in srgb, var(--k) 12%, var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--k) 45%, var(--border));
  transition: transform .12s, box-shadow .12s, background .12s;
}
.randq .rnd-cta-slate { --k: var(--c-slate); }
.randq .rnd-cta:hover { transform: translateY(-1px); box-shadow: var(--shadow-control); background: color-mix(in srgb, var(--k) 22%, var(--surface)); }
.randq .rnd-cta:focus-visible { outline: 2px solid var(--k); outline-offset: 2px; }
.randq .rnd-cta-ico {
  display: grid; place-items: center; flex-shrink: 0; width: 34px; height: 34px; border-radius: 9px;
  background: color-mix(in srgb, var(--k) 20%, var(--surface)); color: var(--k); font-size: 16px; font-weight: 800; line-height: 1;
}
.randq .rnd-cta-txt { display: flex; flex-direction: column; line-height: 1.25; }
.randq .rnd-cta-txt b { font-size: 13.5px; font-weight: 800; }
.randq .rnd-cta-txt small { font-size: 11.5px; font-weight: 500; color: var(--text-dim); }

@media (prefers-reduced-motion: reduce) {
  .randq .rnd-cta, :is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn { transition: none; }
  .randq .rnd-cta:hover, :is(.randq, .repq, .rnd-modal, .rfc, .rf-page-head, .ih) .rbtn:hover { transform: none; }
}
.randq .rep-gnote { display: flex; align-items: center; gap: 8px; }

/* The name's sheet. */
.rnd-modal .rnd-sheet-state { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 2px 0 8px; }
.rnd-modal .rnd-sheet-sub {
  margin: 0 0 8px; padding: 8px 10px; border-radius: 8px; font-size: 13px; color: var(--text);
  background: color-mix(in srgb, var(--rep-c) 8%, transparent); box-shadow: inset 3px 0 0 var(--rep-c);
}
.rnd-modal .rnd-sheet-day { margin: 12px 0 10px; }
.rnd-modal .rnd-day-row, .rnd-modal .rnd-excuse { display: flex; gap: 8px; align-items: stretch; }
.rnd-modal .rnd-day-row input, .rnd-modal .rnd-excuse input { flex: 1; min-width: 0; margin: 0; }
.rnd-modal .rnd-excuse { margin-top: 10px; flex-wrap: wrap; }
.rnd-modal .rnd-excuse[hidden] { display: none; }
.rnd-modal .rnd-excuse label { flex-basis: 100%; margin: 0; }
.rnd-modal .rnd-sheet-btns { display: flex; flex-wrap: wrap; gap: 8px; }
.rnd-modal .modal-actions { flex-wrap: wrap; gap: 8px; }

/* The entry dialog's picker: Drug in teal, Drug + alcohol in violet. */
.rnd-modal .rnd-paste { margin: 4px 0 10px; }
.rnd-modal .rnd-paste summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--c-blue); }
.rnd-modal .rnd-paste textarea { width: 100%; margin: 8px 0 8px; }
.rnd-modal .rnd-unmatched { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; font-size: 12.5px; }
.rnd-modal .rnd-note-ok  { color: var(--c-green); font-weight: 600; }
.rnd-modal .rnd-note-bad { color: var(--c-red); }
.rnd-modal .rnd-count-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 8px 0 6px; min-height: 26px; }
.rnd-modal .rnd-list { max-height: 44vh; overflow-y: auto; border: 1px solid var(--border); border-radius: 10px; }
.rnd-modal .rnd-pick { display: flex; align-items: center; gap: 10px; padding: 7px 10px; border-bottom: 1px solid var(--border); transition: background .12s ease; }
.rnd-modal .rnd-pick:last-child { border-bottom: 0; }
.rnd-modal .rnd-pick.picked-drug { background: color-mix(in srgb, var(--c-teal) 8%, transparent); box-shadow: inset 3px 0 0 var(--c-teal); }
.rnd-modal .rnd-pick.picked-alc  { background: color-mix(in srgb, var(--c-violet) 8%, transparent); box-shadow: inset 3px 0 0 var(--c-violet); }
.rnd-modal .rnd-pick-name { flex: 1; min-width: 0; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rnd-modal .rnd-seg { display: inline-flex; gap: 4px; flex-shrink: 0; }
.rnd-modal .rnd-seg button {
  --k: var(--c-teal);
  padding: 4px 10px; border-radius: 8px; font-size: 12.5px; font-weight: 700; cursor: pointer;
  color: var(--text-dim); background: var(--surface); border: 1.5px solid var(--border);
  transition: background .12s, color .12s, border-color .12s;
}
.rnd-modal .rnd-seg .rnd-seg-alc { --k: var(--c-violet); }
.rnd-modal .rnd-seg button:hover { color: var(--text); background: color-mix(in srgb, var(--k) 12%, var(--surface)); border-color: color-mix(in srgb, var(--k) 45%, var(--border)); }
.rnd-modal .rnd-seg button.on { color: var(--text); background: color-mix(in srgb, var(--k) 26%, var(--surface)); border-color: var(--k); }

.exam-weeknav { display: flex; align-items: center; gap: 8px; margin: 14px 0 8px; }
.exam-weeklabel { font-size: 13.5px; font-weight: 700; color: var(--text); }

/* The grid. `--exam-rows` is the number of hour rows; every column body is
   that many × the hour height, and the hour lines are painted on it. */
.exam-week {
  display: grid; grid-template-columns: 52px repeat(7, minmax(0, 1fr)); gap: 0;
  border: 1px solid var(--border); border-radius: 11px; overflow: hidden; background: var(--surface);
}
.exam-day, .exam-hours { min-width: 0; border-left: 1px solid var(--border); }
.exam-hours { border-left: 0; }
.exam-day-head {
  padding: 6px 4px; text-align: center; font-size: 11px; font-weight: 700; letter-spacing: 0.4px;
  text-transform: uppercase; color: var(--text-dim); border-bottom: 1px solid var(--border); background: var(--bg-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.exam-day.today .exam-day-head { color: var(--accent); }
.exam-day-d { font-size: 13px; color: var(--text); }
.exam-day.today .exam-day-d { color: var(--accent); }
.exam-day.past .exam-day-body { opacity: 0.6; }
.exam-day-body, .exam-hours-body {
  position: relative; height: calc(var(--exam-rows) * 44px);
  background-image: repeating-linear-gradient(to bottom, var(--border) 0, var(--border) 1px, transparent 1px, transparent 44px);
}
.exam-hours-body { background-image: none; }
.exam-hour { height: 44px; padding: 2px 6px 0 0; text-align: right; font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.exam-avail { position: absolute; left: 0; right: 0; background: rgba(16,185,129,0.09); border-top: 1px dashed rgba(16,185,129,0.45); border-bottom: 1px dashed rgba(16,185,129,0.45); pointer-events: none; }
.exam-day-none { position: absolute; left: 0; right: 0; top: 8px; text-align: center; font-size: 12px; color: var(--muted); }

.exam-blk {
  position: absolute; left: 3px; right: 3px; display: flex; flex-direction: column; gap: 0;
  padding: 3px 6px; overflow: hidden; text-align: left; cursor: pointer; font-family: inherit;
  border-radius: 7px; border: 1px solid var(--xs-c); border-left-width: 4px;
  background: color-mix(in srgb, var(--xs-c) 14%, var(--surface)); color: var(--text);
  transition: filter 120ms ease, transform 120ms ease;
}
.exam-blk:hover { filter: brightness(1.06); transform: translateY(-1px); }
.exam-blk-n { font-size: 12px; font-weight: 700; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.exam-blk-k { font-size: 10.5px; color: var(--text-dim); line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.exam-blk-k b { color: var(--xs-c); font-weight: 700; font-variant-numeric: tabular-nums; }

/* ONE colour per state. */
.xs-wait   { --xs-c: var(--muted); }
.xs-no     { --xs-c: var(--warning); }
.xs-conf   { --xs-c: var(--success); }
.xs-done   { --xs-c: #3b82f6; }
.xs-missed { --xs-c: var(--danger); }
.xs-cancel { --xs-c: var(--muted); }
.cpill.xs-wait, .cpill.xs-no, .cpill.xs-conf, .cpill.xs-done, .cpill.xs-missed, .cpill.xs-cancel {
  background: color-mix(in srgb, var(--xs-c) 16%, transparent); color: var(--xs-c); border: 1px solid color-mix(in srgb, var(--xs-c) 45%, transparent);
}
.cpill.cp-red   { background: rgba(239,68,68,0.16);  color: var(--danger); }
.cpill.cp-amber { background: rgba(245,158,11,0.16); color: var(--warning); }

.exam-key { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 8px 2px 18px; font-size: 11.5px; color: var(--text-dim); }
.xk { display: inline-flex; align-items: center; gap: 6px; }
.xk::before { content: ""; width: 10px; height: 10px; border-radius: 3px; background: color-mix(in srgb, var(--xs-c) 30%, var(--surface)); border: 1px solid var(--xs-c); }
.xk-avail::before { background: rgba(16,185,129,0.12); border: 1px dashed rgba(16,185,129,0.6); }

/* The two lists under the week, on the .paytable row recipe. */
.exam-due-row { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1.2fr) auto minmax(0, 1.4fr); gap: 8px 12px; align-items: center; padding: 10px 12px; border-bottom: 1px solid var(--border); }
.exam-due-row:last-child { border-bottom: none; }
.exam-due-row.tappable { cursor: pointer; }
.exam-due-row.tappable:hover { background: var(--surface-2); }
.exam-due-who, .exam-due-what { font-size: 13px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.exam-due-what { color: var(--text-dim); }
.exam-due-state { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; justify-content: flex-end; text-align: right; font-size: 12px; }
.examq .empty-mini { padding: 12px; font-size: 12.5px; color: var(--text-dim); }

/* The sheet. */
.exam-sheet-when { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: -4px 0 10px; font-size: 14px; font-weight: 600; }
.exam-sheet-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px 12px; font-size: 13px; margin-bottom: 10px; }
.exam-sides { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 4px 0 10px; }
.exam-side { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border: 1px solid var(--border); border-radius: 9px; background: var(--bg-2); }
.exam-side-who { font-size: 12.5px; font-weight: 700; }
.exam-side-ans { font-size: 13px; font-weight: 700; }
.exam-side-ans.yes { color: var(--success); }
.exam-side-ans.no { color: var(--warning); }
.exam-side-ans.wait { color: var(--muted); }
.exam-hist { margin: 0 0 10px; font-size: 12.5px; color: var(--text-dim); }
.exam-hist ul { margin: 6px 0 0; padding-left: 18px; }
.exam-sheet-act { border-top: 1px solid var(--border); padding-top: 10px; margin-top: 4px; }
.exam-sheet-btns { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.exam-hour-row { display: grid; grid-template-columns: 1fr 1fr auto 1fr auto; gap: 6px; align-items: center; margin-bottom: 6px; }
.modal .exam-hour-row select, .modal .exam-hour-row input { margin: 0; }
.exam-kind-row { padding: 8px 0; border-bottom: 1px solid var(--border); }
.exam-kind-row:last-of-type { border-bottom: none; }
.exam-kind-row .exam-auto { margin: 0 0 4px; font-size: 13.5px; color: var(--text); font-weight: 600; }

/* A phone reads the week as seven short lists. */
@media (max-width: 767px) {
  .exam-week { grid-template-columns: 1fr; }
  .exam-hours { display: none; }
  .exam-day { border-left: 0; border-top: 1px solid var(--border); }
  .exam-day:first-of-type { border-top: 0; }
  .exam-day-head { text-align: left; padding: 6px 10px; }
  .exam-day-body, .exam-hours-body { height: auto; min-height: 0; background-image: none; padding: 4px 6px 6px; }
  .exam-avail { display: none; }
  .exam-day-none { position: static; display: block; text-align: left; padding: 2px 4px; }
  .exam-blk { position: static !important; height: auto !important; width: 100%; margin: 4px 0 0; flex-direction: row; align-items: baseline; gap: 8px; }
  .exam-blk-n { flex: 1; }
  .exam-due-row { grid-template-columns: 1fr auto; }
  .exam-due-what { grid-column: 1 / -1; }
  .exam-due-state { grid-column: 1 / -1; justify-content: flex-start; text-align: left; }
  .exam-sides { grid-template-columns: 1fr; }
}

/* ---------- Extra Work (My Bus → Extra Work) ----------
   The driver's own additions to their pay: the runs they covered, swaps,
   shuttles, trips, and the trip times still waiting. Colour does the labelling
   here — the boys' blue and the girls' pink the whole app uses, amber for the
   morning, indigo for the afternoon — because the buttons are pressed with a
   thumb in a yard, and a thumb finds a colour before it reads a word. Brand
   orange stays the "run" job and the primary button; the rest are their own.
   ---------- */
:root {
  --ew-am: #f59e0b;
  --ew-pm: #6366f1;
  --ew-trip: #a855f7;
  --ew-swap: #14b8a6;
  --ew-shuttle: #34d399;
  --ew-mixed: #a855f7;
}

/* The doors on the home screen. The big one is the standby's. */
.ew-entry {
  display: flex; align-items: center; gap: 12px;
  width: 100%; text-align: left;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text);
  cursor: pointer; font: inherit;
  margin-bottom: 14px;
  transition: border-color .15s, transform .15s, box-shadow .15s;
}
.ew-entry:hover { border-color: var(--accent); transform: translateY(-1px); box-shadow: var(--shadow-control); }
.ew-entry-big {
  padding: 18px 16px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--cat-boys) 14%, var(--surface)), color-mix(in srgb, var(--cat-girls) 14%, var(--surface)));
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}
.ew-entry-icon { font-size: 34px; line-height: 1; flex-shrink: 0; }
.ew-entry-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.ew-entry-body b { font-size: 18px; font-weight: 800; letter-spacing: -.2px; }
.ew-entry-body small { font-size: 12.5px; color: var(--text-dim); }
.ew-entry-cats { display: flex; flex-direction: column; gap: 5px; flex-shrink: 0; }
.ew-entry-cats i { display: block; width: 14px; height: 14px; border-radius: 50%; }
.ew-entry-cats i.boys { background: var(--cat-boys); }
.ew-entry-cats i.girls { background: var(--cat-girls); }
.ew-entry-line { padding: 12px 14px; font-size: 14px; font-weight: 600; }
.ew-entry-more { margin-left: auto; color: var(--muted); font-size: 18px; }
.ew-entry-badge {
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px;
  background: var(--accent); color: #fff; font-size: 12px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}

/* The screen: four job tiles, then the lists. */
.ew-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 18px; }
.ew-act {
  --ew-c: var(--accent);
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  padding: 14px 14px 12px; min-height: 108px; text-align: left;
  border: 1px solid color-mix(in srgb, var(--ew-c) 45%, var(--border));
  border-top: 4px solid var(--ew-c);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--ew-c) 9%, var(--surface));
  color: var(--text); cursor: pointer; font: inherit;
  transition: transform .15s, box-shadow .15s, background .15s;
}
.ew-act:hover { transform: translateY(-1px); box-shadow: var(--shadow-control); background: color-mix(in srgb, var(--ew-c) 16%, var(--surface)); }
.ew-act:active { transform: translateY(0); }
.ew-act-icon { font-size: 26px; line-height: 1; }
.ew-act b { font-size: 15px; font-weight: 800; color: var(--text); }
.ew-act small { font-size: 12px; color: var(--text-dim); line-height: 1.3; }
.ew-act-run { --ew-c: var(--accent); }
.ew-act-trip { --ew-c: var(--ew-trip); }
.ew-act-swap { --ew-c: var(--ew-swap); }
.ew-act-shuttle { --ew-c: var(--ew-shuttle); }

.ew-section { margin-top: 18px; }
.ew-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px;
  background: var(--warning); color: #1a1d22; font-size: 12px; font-weight: 800; margin-left: 6px;
}
.ew-empty { padding: 6px 0; }
.ew-fold { margin-top: 8px; }
.ew-fold summary { cursor: pointer; font-size: 13px; color: var(--text-dim); padding: 6px 0; }

/* ---- the wizard ---- */
.ew-wiz h3 { margin: 0 0 12px; font-size: 20px; letter-spacing: -.2px; }
.ew-hint { margin: -6px 0 10px; }
.ew-err { color: var(--danger); font-size: 13px; margin: 6px 0 0; min-height: 1em; }

.ew-crumbs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.ew-crumb {
  padding: 5px 10px; border-radius: 999px; font: inherit; font-size: 12.5px; font-weight: 700;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-dim); cursor: pointer;
}
.ew-crumb:hover { border-color: var(--accent); color: var(--text); }
.ew-crumb-boys { border-color: var(--cat-boys); color: var(--cat-boys); background: color-mix(in srgb, var(--cat-boys) 14%, transparent); }
.ew-crumb-girls { border-color: var(--cat-girls); color: var(--cat-girls); background: color-mix(in srgb, var(--cat-girls) 14%, transparent); }
.ew-crumb-mixed { border-color: var(--ew-mixed); color: var(--ew-mixed); background: color-mix(in srgb, var(--ew-mixed) 14%, transparent); }
.ew-crumb-am { border-color: var(--ew-am); color: var(--ew-am); background: color-mix(in srgb, var(--ew-am) 14%, transparent); }
.ew-crumb-pm { border-color: var(--ew-pm); color: var(--ew-pm); background: color-mix(in srgb, var(--ew-pm) 14%, transparent); }

/* One big answer. */
.ew-big {
  --ew-c: var(--accent);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-height: 92px; padding: 14px 10px;
  border: 2px solid color-mix(in srgb, var(--ew-c) 50%, var(--border));
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--ew-c) 12%, var(--surface));
  color: var(--text); font: inherit; font-size: 18px; font-weight: 800; letter-spacing: -.2px;
  cursor: pointer; text-align: center;
  transition: transform .12s, box-shadow .12s, background .12s, border-color .12s;
}
.ew-big small { font-size: 12px; font-weight: 600; color: var(--text-dim); }
.ew-big:hover:not(:disabled) { transform: translateY(-1px); box-shadow: var(--shadow-control); background: color-mix(in srgb, var(--ew-c) 22%, var(--surface)); }
.ew-big.on { border-color: var(--ew-c); background: color-mix(in srgb, var(--ew-c) 26%, var(--surface)); }
.ew-big:disabled { opacity: .45; cursor: default; }
.ew-grid { display: grid; gap: 10px; margin-bottom: 12px; }
.ew-grid-1 { grid-template-columns: 1fr; }
.ew-grid-2 { grid-template-columns: 1fr 1fr; }
.ew-cat.boys { --ew-c: var(--cat-boys); }
.ew-cat.girls { --ew-c: var(--cat-girls); }
.ew-cat.mixed { --ew-c: var(--ew-mixed); }
.ew-half.am { --ew-c: var(--ew-am); }
.ew-half.pm { --ew-c: var(--ew-pm); }

.ew-days { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px; }
.ew-day { min-height: 78px; font-size: 16px; }
.ew-day-other { grid-column: 1 / -1; min-height: 0; padding: 10px 12px; flex-direction: row; justify-content: space-between; font-size: 15px; }
.ew-day-other input { width: auto; margin: 0; font-size: 14px; padding: 6px 8px; }

/* Runs and their buses. The run's own colour rides down --ew-c. */
.ew-run { margin-bottom: 12px; --ew-c: var(--accent); }
.ew-run.boys { --ew-c: var(--cat-boys); }
.ew-run.girls { --ew-c: var(--cat-girls); }
.ew-run.mixed { --ew-c: var(--ew-mixed); }
.ew-run-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; padding-left: 8px; border-left: 3px solid var(--ew-c); }
.ew-run-time { font-size: 12px; font-weight: 800; color: var(--ew-c); font-variant-numeric: tabular-nums; }
.ew-run-name { font-size: 14px; font-weight: 700; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ew-run-len { font-size: 11.5px; color: var(--muted); }
.ew-buses { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.ew-bus {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 10px 4px 8px; min-height: 66px;
  border: 2px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); font: inherit; cursor: pointer;
  transition: border-color .12s, background .12s, transform .12s;
}
.ew-bus b { font-size: 20px; font-weight: 800; letter-spacing: -.3px; font-variant-numeric: tabular-nums; }
.ew-bus small { font-size: 11px; color: var(--text-dim); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ew-bus:hover:not(:disabled) { border-color: var(--ew-c); transform: translateY(-1px); }
.ew-bus.open { border-color: color-mix(in srgb, var(--success) 55%, var(--border)); }
.ew-bus.on { border-color: var(--ew-c); background: color-mix(in srgb, var(--ew-c) 18%, var(--surface)); }
.ew-bus.on b { color: var(--ew-c); }
.ew-bus.dead { opacity: .45; cursor: default; }
.ew-bus-tag {
  font-style: normal; font-size: 9.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .3px;
  color: var(--success); margin-top: 1px;
}
.ew-bus-tag.dead { color: var(--muted); }
.ew-bus-check {
  position: absolute; top: -7px; right: -7px;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--ew-c); color: #fff; font-size: 12px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 1px 3px rgba(0,0,0,.35);
}
.ew-more { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 10px; }
.ew-chip {
  padding: 7px 12px; border-radius: 999px; font: inherit; font-size: 13px; font-weight: 700;
  border: 1px dashed var(--accent); background: var(--accent-soft); color: var(--accent); cursor: pointer;
}
.ew-chip:hover { background: color-mix(in srgb, var(--accent) 24%, transparent); }

/* The basket line: what's picked and the way to the review. */
.ew-basket {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 12px; margin: 8px 0 4px;
  border-radius: var(--radius-sm); background: var(--surface-2); border: 1px solid var(--border);
  font-size: 13px; font-weight: 700;
}
.ew-basket > span { flex: 1; min-width: 0; }

/* The review: one row per thing, with the hours boxes on it. */
.ew-review { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.ew-rev {
  --ew-c: var(--accent);
  display: flex; align-items: center; gap: 10px;
  padding: 10px 10px 10px 12px;
  border: 1px solid var(--border); border-left: 4px solid var(--ew-c); border-radius: var(--radius-sm);
  background: var(--surface);
}
.ew-rev.boys { --ew-c: var(--cat-boys); }
.ew-rev.girls { --ew-c: var(--cat-girls); }
.ew-rev.mixed { --ew-c: var(--ew-mixed); }
.ew-rev-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ew-rev-body b { font-size: 13.5px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ew-rev-body small { font-size: 11.5px; color: var(--text-dim); }
.ew-hm { display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0; }
.ew-hm input { width: 46px; margin: 0; padding: 7px 6px; text-align: center; font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.ew-hm span { font-size: 12px; color: var(--muted); font-weight: 700; }
/* The modal's own input rules (padding, width:100%) outrank a single class —
   see the modal specificity note — so the hours boxes are scoped through it. */
.modal .ew-hm input { width: 46px; padding: 7px 6px; }
.modal .ew-day-other input { width: auto; padding: 6px 8px; }

/* ---- the office: reported by drivers, on the attendance screen ---- */
.att-reports {
  margin: 0 0 14px;
  border: 1px solid color-mix(in srgb, var(--warning) 55%, var(--border));
  border-left: 4px solid var(--warning);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--warning) 7%, var(--surface));
  overflow: hidden;
}
.att-reports-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 10px 14px; border-bottom: 1px solid color-mix(in srgb, var(--warning) 30%, var(--border));
}
.att-reports-title { font-size: 13px; font-weight: 800; text-transform: uppercase; letter-spacing: .4px; }
.att-reports-count { font-size: 12px; font-weight: 700; color: var(--warning); }
.att-report {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; border-top: 1px solid color-mix(in srgb, var(--warning) 18%, var(--border));
}
.att-report:first-of-type { border-top: 0; }
.att-report-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.att-report-body b { font-size: 14px; }
.att-report-body small { font-size: 12px; color: var(--text-dim); }
.att-report-why b { font-size: 12px; color: var(--text); }
.att-report-acts { display: flex; gap: 6px; flex-shrink: 0; }
@media (max-width: 520px) {
  .att-report { flex-wrap: wrap; }
  .att-report-acts { width: 100%; justify-content: flex-end; }
  .ew-buses { grid-template-columns: repeat(3, 1fr); }
}


/* ---------- Rocket Fleet: the carrier switcher and the Carriers screen ----------
   Rocket Fleet is this stylesheet with its own name (brand parked 2026-10-07),
   so everything here is built from the tokens the fleet app already uses. */
svg.brand-logo.rf-mark { display: block; }
/* The ring takes the edge of the square, so the rocket inside it read small
   at the 46px every header mark is drawn at; 58 still sits inside the 75px bar. */
.topbar-app svg.brand-logo.rf-mark { height: 58px; width: 58px; margin: -6px 0; }
.rf-switch {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  background: none; border: 0; padding: 0; margin: 0; color: inherit; font: inherit;
  cursor: pointer; text-align: left; min-width: 0;
}
.rf-switch h1 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rf-switch-caret { font-size: 13px; color: var(--accent); transition: transform .15s; }
.rf-switch:hover .rf-switch-caret { transform: translateY(1px); }
.rf-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }
.rf-carriers { display: grid; gap: 8px; }
.rf-carrier {
  display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 2px 12px;
  width: 100%; text-align: left; padding: 12px 14px; border-radius: 12px;
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  font: inherit; cursor: pointer;
}
.rf-carrier:hover { background: var(--surface-2); }
.rf-carrier:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rf-carrier.on { border-color: var(--accent); box-shadow: inset 3px 0 0 var(--accent); }
.rf-carrier-name { font-weight: 700; font-size: 15px; min-width: 0; overflow-wrap: anywhere; }
.rf-carrier-meta { grid-column: 1; font-size: 13px; color: var(--text-dim); }
.rf-carrier-here { grid-column: 2; grid-row: 1 / span 2; font-size: 12px; font-weight: 700; color: var(--accent); }
.rf-carriers-page { display: grid; gap: 16px; max-width: 720px; }
.rf-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.rf-head h2 { margin: 0; font-size: 20px; }
.rf-note { margin: 4px 0 0; font-size: 13px; color: var(--text-dim); }

/* Today — the examiner's plate across every carrier, in ONE panel (the owner,
   2026-10-11: "nicer contained"): a head with the date and the doors out, the
   four counts, then the day and the carriers as ruled sections inside the same
   frame rather than cards floating on the page. Colours are the Randoms
   palette (--c-*), by ACT: blue the calendar, red overdue, amber due, teal sent
   in, violet randoms. Each carrier also wears its own colour (--cc, set
   inline) — identity, the same one it has on the Calendar. */
.rf-today { max-width: 980px; margin: 0 auto; }
.rf-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; overflow: hidden;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 6px 20px rgba(0, 0, 0, .04); }
.rf-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px 16px; flex-wrap: wrap;
  padding: 18px 20px 16px; border-bottom: 1px solid var(--border);
  background: linear-gradient(180deg, color-mix(in srgb, var(--c-blue) 6%, var(--surface)), var(--surface)); }
.rf-panel-title h2 { margin: 0; font-size: 21px; letter-spacing: -0.01em; }
.rf-panel-title p { margin: 3px 0 0; font-size: 13px; color: var(--text-dim); }
.rf-acts { display: flex; flex-wrap: wrap; gap: 8px; }
.rf-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; padding: 16px 20px; border-bottom: 1px solid var(--border); }
@media (max-width: 560px) { .rf-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.rf-tile {
  --k: var(--c-slate);
  display: grid; gap: 4px; text-align: left; font: inherit; color: var(--text); cursor: pointer;
  padding: 12px 14px; border-radius: 12px;
  background: color-mix(in srgb, var(--k) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--k) 24%, var(--border));
  box-shadow: inset 3px 0 0 var(--k);
  transition: background .15s, transform .12s, box-shadow .12s;
}
.rf-tile:hover { background: color-mix(in srgb, var(--k) 15%, var(--surface)); transform: translateY(-1px);
  box-shadow: inset 3px 0 0 var(--k), var(--shadow-control); }
.rf-tile:focus-visible { outline: 2px solid var(--k); outline-offset: 2px; }
.rf-tile-top { display: flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.rf-tile-top i { font-style: normal; font-size: 13px; }
.rf-tile b { font-size: 28px; line-height: 1.05; font-weight: 800; color: var(--k); font-variant-numeric: tabular-nums; }
.rf-tile.rf-k-zero { opacity: .6; }
.rf-tile.rf-k-zero b { color: var(--muted); }
.rf-k-blue { --k: var(--c-blue); } .rf-k-red { --k: var(--c-red); } .rf-k-amber { --k: var(--c-amber); }
.rf-k-teal { --k: var(--c-teal); } .rf-k-green { --k: var(--c-green); } .rf-k-violet { --k: var(--c-violet); }

.rf-sec { padding: 14px 20px 18px; display: grid; gap: 10px; }
.rf-sec + .rf-sec { border-top: 1px solid var(--border); }
.rf-sec-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rf-sec-head h3 { margin: 0; font-size: 12.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--accent); }
.rf-count { font-size: 11px; font-weight: 700; padding: 1px 8px; border-radius: 999px; background: var(--badge-bg, var(--surface-2)); color: var(--badge-fg, var(--text)); }
.rf-sec-note { margin-left: auto; font-size: 12.5px; color: var(--text-dim); }
.rf-empty { margin: 0; padding: 14px; border: 1px dashed var(--border); border-radius: 12px; color: var(--text-dim); font-size: 14px; }

/* The day: one ruled list, each line edged in its carrier's colour. */
.rf-agenda { display: grid; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.rf-agenda-row {
  display: grid; grid-template-columns: 84px minmax(0, 1fr) auto; align-items: center; gap: 12px;
  width: 100%; text-align: left; padding: 11px 14px 11px 16px; cursor: pointer;
  background: var(--surface); border: 0; color: var(--text); font: inherit;
  box-shadow: inset 4px 0 0 var(--cc, var(--border));
}
.rf-agenda-row + .rf-agenda-row { border-top: 1px solid var(--border); }
.rf-agenda-row:hover { background: color-mix(in srgb, var(--cc, var(--text)) 6%, var(--surface)); }
.rf-agenda-row:focus-visible, .rf-today-name:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rf-agenda-time { font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text); }
.rf-agenda-main { display: grid; min-width: 0; gap: 1px; }
.rf-agenda-main b { font-size: 14px; overflow-wrap: anywhere; }
.rf-agenda-main span { font-size: 13px; color: var(--text-dim); display: flex; align-items: center; gap: 6px; min-width: 0; }
.rf-dot { display: inline-block; flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--cc, var(--muted)); }
.rf-pill { font-size: 12px; font-weight: 700; white-space: nowrap; padding: 3px 10px; border-radius: 999px; color: var(--text);
  background: color-mix(in srgb, var(--k) 14%, var(--surface)); border: 1px solid color-mix(in srgb, var(--k) 40%, var(--border)); }
@media (max-width: 520px) {
  .rf-agenda-row { grid-template-columns: 70px minmax(0, 1fr); }
  .rf-agenda .rf-pill { grid-column: 2; justify-self: start; }
}

/* The carriers: a row each, name and a meter of who is up to date on the
   left, what is waiting there as colour-coded buttons on the right. */
.rf-co-list { display: grid; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.rf-today-carrier { display: grid; grid-template-columns: minmax(200px, 280px) minmax(0, 1fr); align-items: center; gap: 10px 18px;
  padding: 12px 14px 12px 16px; box-shadow: inset 4px 0 0 var(--cc, var(--border)); background: var(--surface); }
.rf-today-carrier + .rf-today-carrier { border-top: 1px solid var(--border); }
@media (max-width: 720px) { .rf-today-carrier { grid-template-columns: minmax(0, 1fr); } }
.rf-today-name { display: grid; gap: 3px; background: none; border: 0; padding: 0; font: inherit; color: var(--text); text-align: left; cursor: pointer; min-width: 0; }
.rf-today-name b { font-size: 15px; display: flex; align-items: center; gap: 7px; }
.rf-today-name > span { font-size: 12.5px; color: var(--text-dim); }
.rf-today-name:hover b { color: var(--accent); }
.rf-meter { display: block; height: 5px; border-radius: 999px; background: var(--surface-2); overflow: hidden; margin-top: 3px; max-width: 220px; }
.rf-meter i { display: block; height: 100%; border-radius: inherit; background: var(--c-green); }
.rf-chips { display: flex; flex-wrap: wrap; gap: 6px; }
@media (min-width: 721px) { .rf-chips { justify-content: flex-end; } }
.rf-clear { font-size: 12.5px; font-weight: 700; padding: 5px 10px; border-radius: 10px; color: var(--text);
  background: color-mix(in srgb, var(--c-green) 12%, var(--surface)); border: 1.5px solid color-mix(in srgb, var(--c-green) 40%, var(--border)); }
.rf-carriers-page .rf-head { align-items: center; }
.rf-carrier-all { margin-bottom: 8px; border-style: dashed; }
/* Settings → Carriers: each carrier edged in its own colour, as on Today. */
.rf-carrier.rf-carrier-cc { box-shadow: inset 4px 0 0 var(--cc); }
.rfc .rf-carrier-here.rbtn { justify-self: end; align-self: center; }

/* ---------- Rocket Fleet: the examiner's calendar (src/rocket-exams.ts) ----------
   Built like an appointment app: a timeline read top to bottom, the month
   and the lists beside it. Each carrier has its own colour (--cc, set
   inline), used for the edge of an appointment, its wash and its key —
   identity, never status. Status is the pill: green confirmed, amber
   waiting, red needs a new time or missed. */
.rx { display: grid; gap: 14px; }
.rx-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.rx-title h2 { margin: 0; font-size: 22px; letter-spacing: -0.01em; }
.rx-title p { margin: 2px 0 0; font-size: 13px; color: var(--text-dim); }
.rx-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rx-nav { display: inline-flex; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface); }
.rx-nav .rx-btn { border: 0; border-radius: 0; }
.rx-nav .rx-btn + .rx-btn { border-left: 1px solid var(--border); }
.rx-btn {
  font: inherit; font-size: 13.5px; font-weight: 600; padding: 7px 12px; border-radius: 10px; cursor: pointer;
  background: var(--surface); color: var(--text); border: 1px solid var(--border);
}
.rx-btn:hover { background: var(--surface-2); }
.rx-btn:focus-visible, .rx-seg button:focus-visible, .rx-appt:focus-visible, .rx-md:focus-visible, .rx-item:focus-visible, .rx-key:focus-visible, .rx-wd:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rx-seg { display: inline-flex; background: var(--surface-2); border-radius: 10px; padding: 3px; }
.rx-seg button { font: inherit; font-size: 13px; font-weight: 600; border: 0; background: none; color: var(--text-dim); padding: 5px 12px; border-radius: 8px; cursor: pointer; }
.rx-seg button.on { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }

.rx-body { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 16px; align-items: start; }
@media (max-width: 900px) { .rx-body { grid-template-columns: minmax(0, 1fr); } }
.rx-main { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; overflow: hidden; min-width: 0; }
.rx-off { margin: 0; padding: 8px 14px; font-size: 13px; color: var(--text-dim); background: var(--surface-2); border-bottom: 1px solid var(--border); }
.rx-scroll { max-height: min(72vh, 760px); overflow: auto; }
.rx-grid { display: grid; grid-template-columns: 64px minmax(0, 1fr); position: relative; }
.rx-gutter { border-right: 1px solid var(--border); }
.rx-hour { position: relative; border-top: 1px solid var(--border); box-sizing: border-box; }
.rx-hour:first-child { border-top: 0; }
.rx-hour span { position: absolute; top: 4px; right: 8px; font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.rx-cols { display: grid; grid-template-columns: minmax(0, 1fr); position: relative; --rx-px: 72px;
  background: repeating-linear-gradient(to bottom, var(--border) 0, var(--border) 1px, transparent 1px, transparent var(--rx-px)); }
.rx-week .rx-cols { --rx-px: 48px; }
.rx-cols7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.rx-col { position: relative; background: color-mix(in srgb, var(--surface-2) 70%, transparent); }
.rx-cols7 .rx-col + .rx-col { border-left: 1px solid var(--border); }
.rx-cols7 .rx-col.today { background: color-mix(in srgb, var(--accent) 5%, var(--surface-2)); }
.rx-appt-l1 { display: flex; align-items: center; gap: 8px; min-width: 0; }
.rx-appt-l1 b { flex: 1; min-width: 0; }
.rx-appt-l1 .rx-pill { margin: 0; flex: none; }
.rx-appt .rx-appt-l2 { display: block; }
.rx-open-band { position: absolute; left: 0; right: 0; background: color-mix(in srgb, var(--surface) 92%, transparent); }
.rx-appt {
  position: absolute; z-index: 1; display: grid; align-content: start; gap: 1px; text-align: left; overflow: hidden;
  font: inherit; color: var(--text); cursor: pointer; padding: 6px 9px 6px 11px; border-radius: 8px;
  border: 1px solid color-mix(in srgb, var(--cc) 30%, transparent);
  border-left: 4px solid var(--cc);
  background: color-mix(in srgb, var(--cc) 11%, var(--surface));
  box-shadow: 0 1px 2px rgba(0, 0, 0, .06);
  transition: transform .12s, box-shadow .12s;
}
.rx-appt:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 0, 0, .12); z-index: 2; }
.rx-appt.rx-done { opacity: .6; }
.rx-appt b { font-size: 13.5px; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rx-appt span { font-size: 12px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rx-appt .rx-appt-time { font-weight: 700; color: color-mix(in srgb, var(--cc) 80%, var(--text)); font-variant-numeric: tabular-nums; }
.rx-cols7 .rx-appt { padding: 3px 5px 3px 7px; border-left-width: 3px; }
.rx-cols7 .rx-appt b { font-size: 11.5px; }
.rx-cols7 .rx-appt span { font-size: 11px; }
.rx-pill { justify-self: start; margin-top: 3px; font-size: 11px !important; font-weight: 700; font-style: normal; padding: 2px 8px; border-radius: 999px; color: var(--c) !important;
  background: color-mix(in srgb, var(--c) 13%, var(--surface)); }
.rx-ok { --c: var(--success); } .rx-wait { --c: var(--warning); } .rx-bad { --c: var(--danger); } .rx-done { --c: var(--muted); }
.rx-ok-t { color: var(--success); } .rx-wait-t { color: var(--warning); } .rx-bad-t { color: var(--danger); }
.rx-now { position: absolute; left: 0; right: 0; height: 2px; background: var(--danger); z-index: 3; pointer-events: none; }
.rx-now i { position: absolute; left: -5px; top: -4px; width: 10px; height: 10px; border-radius: 50%; background: var(--danger); }
.rx-weekhead { display: grid; grid-template-columns: 64px repeat(7, minmax(0, 1fr)); position: sticky; top: 0; z-index: 4; background: var(--surface); border-bottom: 1px solid var(--border); }
.rx-wd { font: inherit; background: none; border: 0; padding: 8px 4px; cursor: pointer; display: grid; justify-items: center; color: var(--text-dim); }
.rx-wd span { font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; }
.rx-wd b { font-size: 18px; color: var(--text); width: 32px; height: 32px; display: grid; place-items: center; border-radius: 50%; }
.rx-wd.today b { background: var(--accent); color: #fff; }
.rx-wd:hover b { background: var(--surface-2); }
.rx-wd.today:hover b { background: var(--accent); }
@media (max-width: 640px) { .rx-week .rx-grid, .rx-weekhead { min-width: 640px; } }

.rx-side { display: grid; gap: 12px; min-width: 0; }
.rx-card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 12px 14px; display: grid; gap: 8px; }
.rx-card h3 { margin: 0; font-size: 12.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); display: flex; align-items: center; gap: 6px; }
.rx-count { font-size: 11px; font-weight: 700; color: var(--text); background: var(--surface-2); border-radius: 999px; padding: 1px 7px; letter-spacing: 0; }
.rx-month-head { display: flex; align-items: center; justify-content: space-between; }
.rx-month-head b { font-size: 14.5px; }
.rx-icon { font: inherit; font-size: 16px; width: 30px; height: 30px; border-radius: 8px; border: 0; background: none; cursor: pointer; color: var(--text-dim); }
.rx-icon:hover { background: var(--surface-2); color: var(--text); }
.rx-month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; text-align: center; }
.rx-dow { font-size: 11px; color: var(--muted); font-weight: 600; padding: 2px 0; }
.rx-md { position: relative; font: inherit; font-size: 13px; aspect-ratio: 1; border: 0; border-radius: 50%; background: none; cursor: pointer; color: var(--text); font-variant-numeric: tabular-nums; max-width: 100%; padding: 0; }
.rx-md:hover { background: var(--surface-2); }
.rx-md.out { color: var(--muted); }
.rx-md.today { color: var(--accent); font-weight: 800; }
.rx-md.sel { background: var(--accent); color: #fff; font-weight: 700; }
.rx-md i { position: absolute; left: 50%; bottom: 3px; width: 4px; height: 4px; margin-left: -2px; border-radius: 50%; background: var(--accent); }
.rx-md.sel i { background: #fff; }
.rx-keys { display: flex; flex-wrap: wrap; gap: 6px; }
.rx-key { font: inherit; font-size: 12.5px; font-weight: 600; display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--cc) 35%, transparent); background: color-mix(in srgb, var(--cc) 9%, var(--surface)); color: var(--text); }
.rx-key i { width: 8px; height: 8px; border-radius: 50%; background: var(--cc); }
.rx-key.off { opacity: .45; background: none; text-decoration: line-through; }
.rx-list { display: grid; gap: 4px; }
button.rx-item { grid-template-columns: 8px minmax(0, 1fr); }
.rx-item em { display: block; margin-top: 1px; }
.rx-item { display: grid; grid-template-columns: 8px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 7px 8px; border-radius: 10px;
  font: inherit; text-align: left; color: var(--text); background: none; border: 0; width: 100%; }
button.rx-item { cursor: pointer; }
button.rx-item:hover, .rx-due:hover { background: var(--surface-2); }
.rx-item > i { width: 8px; height: 8px; border-radius: 50%; background: var(--cc); align-self: start; margin-top: 6px; }
.rx-item > span { display: grid; min-width: 0; }
.rx-item b { font-size: 13.5px; }
.rx-item small { font-size: 12px; color: var(--text-dim); }
.rx-item small.rx-bad-t { color: var(--danger); } .rx-item small.rx-wait-t { color: var(--warning); }
.rx-item em { font-style: normal; font-size: 11.5px; font-weight: 700; color: var(--c); white-space: nowrap; }
.rx-none { margin: 0; font-size: 13px; color: var(--text-dim); }

.rx-sheet { display: grid; gap: 10px; min-width: min(420px, 82vw); }
.rx-sheet h3 { margin: 0; font-size: 20px; }
.rx-sheet-band { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 12px; border-radius: 10px;
  background: color-mix(in srgb, var(--cc, var(--accent)) 12%, var(--surface)); border-left: 4px solid var(--cc, var(--accent)); font-weight: 700; font-size: 13.5px; }
.rx-sheet-band .rx-pill { margin: 0; }
.rx-sheet-who { margin: 0; font-size: 15px; font-weight: 600; }
.rx-sheet-who small { font-weight: 400; color: var(--text-dim); font-size: 13px; }
.rx-facts { margin: 0; display: grid; gap: 8px; }
.rx-facts div { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 8px; }
.rx-facts dt { font-size: 12.5px; color: var(--text-dim); }
.rx-facts dd { margin: 0; font-size: 14px; }
.rx-answers { display: grid; gap: 4px; padding: 10px 12px; border-radius: 10px; background: var(--surface-2); }
.rx-ans { display: flex; justify-content: space-between; gap: 10px; font-size: 13.5px; }
.rx-ans span { color: var(--text-dim); }
.rx-move, .rx-cancel { display: grid; gap: 6px; padding: 10px 12px; border: 1px dashed var(--border); border-radius: 10px; }
.rx-move[hidden], .rx-cancel[hidden], .rx-pick[hidden] { display: none; }
.rx-sheet-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; padding-top: 4px; }
.rx-when { border: 1px solid var(--border); border-radius: 10px; padding: 8px 12px 10px; display: grid; gap: 6px; margin: 0; }
.rx-when legend { font-size: 12.5px; color: var(--text-dim); padding: 0 4px; }
.modal label.rx-radio { display: flex; align-items: center; gap: 8px; font-size: 14px; margin: 0;
  text-transform: none; letter-spacing: 0; font-weight: 500; color: var(--text); }
.modal .rx-radio input { width: auto; margin: 0; }
.rx-pick { display: flex; gap: 8px; flex-wrap: wrap; }
.rx-hrow { display: grid; grid-template-columns: 130px auto auto auto; align-items: center; gap: 8px; }
.rx-hrow span { font-size: 13px; color: var(--text-dim); }
.modal .rx-hrow input[type="time"] { width: auto; margin: 0; }
@media (max-width: 520px) { .rx-hrow { grid-template-columns: 1fr auto auto auto; } }

/* The calendar is ONE framed panel (the owner, 2026-10-11: "nicer
   contained"): the head is the panel's top band, the timeline and the side
   column sit inside the same frame divided by a rule, and the side column's
   cards are sections of it rather than cards of their own. On a desk the
   panel is exactly the window's height, so the timeline and the side column
   each scroll inside it and the page itself never does. */
.rx { gap: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; overflow: hidden;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 6px 20px rgba(0, 0, 0, .04); }
.rx-head { align-items: center; padding: 14px 18px; border-bottom: 1px solid var(--border);
  background: linear-gradient(180deg, color-mix(in srgb, var(--c-blue) 6%, var(--surface)), var(--surface)); }
.rx-nav { background: color-mix(in srgb, var(--c-slate) 8%, var(--surface)); border: 1.5px solid color-mix(in srgb, var(--c-slate) 35%, var(--border)); }
.rx-nav .rx-btn { background: none; font-weight: 700; }
.rx-nav .rx-btn:hover { background: color-mix(in srgb, var(--c-slate) 16%, var(--surface)); }
.rx-seg { background: color-mix(in srgb, var(--c-slate) 10%, var(--surface)); }
.rx-seg button.on { background: color-mix(in srgb, var(--c-blue) 18%, var(--surface)); color: var(--text);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--c-blue) 55%, var(--border)); }
.rx-body { gap: 0; grid-template-columns: minmax(0, 1fr) 312px; align-items: stretch; min-height: 0; }
.rx-main { border: 0; border-radius: 0; display: flex; flex-direction: column; min-height: 0; }
.rx-main .rx-scroll { flex: 1; }
.rx-side { gap: 0; border-left: 1px solid var(--border); background: color-mix(in srgb, var(--surface-2) 45%, var(--surface)); align-content: start; }
.rx-card { border: 0; border-radius: 0; padding: 14px 16px; background: none; }
.rx-card + .rx-card { border-top: 1px solid var(--border); }
.rx-card h3 { color: var(--accent); }
@media (max-width: 900px) {
  .rx-body { grid-template-columns: minmax(0, 1fr); }
  .rx-side { border-left: 0; border-top: 1px solid var(--border); }
}
@media (min-width: 1024px) {
  .rx { height: calc(100dvh - var(--topbar-h) - 90px); min-height: 560px; grid-template-rows: auto minmax(0, 1fr); }
  .rx-body { height: 100%; }
  .rx-main .rx-scroll { max-height: none; }
  .rx-side { overflow-y: auto; }
}
/* Waiting for an answer / due, not booked: a row edged in its carrier. */
.rx-list .rx-item { border-radius: 10px; }
.rx-due { grid-template-columns: 8px minmax(0, 1fr) auto; }
/* The appointment sheet's acts wrap as a block of equal buttons. */
.rx-sheet-actions .rbtn { flex: 1 1 auto; }
.rx-move .rbtn, .rx-cancel .rbtn { justify-self: start; margin-top: 4px; }

/* Rocket Fleet: a driver's texts as the Conversation tab of their file.
   Framed like the other panes; the bubbles are the Conversations screen's own
   (.chat-msg / .chat-bubble), the composer is pinned to the pane rather than
   the viewport. Scoped through .modal — `.modal textarea` would win otherwise. */
.dd-convo { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; border: 1px solid var(--border); border-radius: 14px; overflow: hidden; background: var(--surface); margin-top: 6px; }
.dd-convo[hidden] { display: none; }
.dd-convo-head { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--c-teal) 8%, var(--surface)); }
.dd-convo-head > div { display: grid; min-width: 0; }
.dd-convo-head b { font-size: 14.5px; }
.dd-convo-head span { font-size: 12.5px; color: var(--text-dim); }
.dd-convo-scroll { height: min(52vh, 480px); overflow-y: auto; padding: 8px 12px 12px; background: var(--bg); }
.modal .dd-convo-form { display: flex; align-items: flex-end; gap: 8px; padding: 8px 10px; border-top: 1px solid var(--border); background: var(--surface); margin: 0; }
.modal .dd-convo-form textarea { flex: 1; min-width: 0; margin: 0; resize: none; 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; font-size: 16px; line-height: 1.3; }
.modal .dd-convo-form textarea:focus { outline: none; border-color: var(--c-teal); }
.modal .dd-convo-form .chat-send { width: 40px; height: 40px; padding: 0; margin: 0; border: 0; border-radius: 50%; flex-shrink: 0; }
.modal .dd-convo-form .chat-send:disabled { opacity: .5; }
.dd-convo .sms-noconsent { margin: 0; border-radius: 0; border: 0; border-top: 1px solid var(--border); }

/* Today → Progress: the roster ring, the four counts that move it, and the
   drivers closest to done. */
.rf-prog-grid { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 16px; align-items: center; }
@media (max-width: 520px) { .rf-prog-grid { grid-template-columns: minmax(0, 1fr); justify-items: center; } }
.rf-ring { width: 124px; height: 124px; display: block; }
.rf-ring-bg { fill: none; stroke: var(--surface-2); stroke-width: 9; }
.rf-ring-fg { fill: none; stroke: var(--c-green); stroke-width: 9; stroke-linecap: round; transition: stroke-dasharray .6s ease; }
.rf-ring-n { font-size: 19px; font-weight: 800; fill: var(--text); text-anchor: middle; dominant-baseline: middle; font-variant-numeric: tabular-nums; }
.rf-ring-l { font-size: 9.5px; font-weight: 600; fill: var(--text-dim); text-anchor: middle; text-transform: uppercase; letter-spacing: .06em; }
.rf-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; width: 100%; }
@media (max-width: 380px) { .rf-stats { grid-template-columns: minmax(0, 1fr); } }
.rf-stat { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 8px; align-items: baseline; padding: 9px 12px; border-radius: 10px;
  background: color-mix(in srgb, var(--k) 7%, var(--surface)); border: 1px solid color-mix(in srgb, var(--k) 22%, var(--border)); }
.rf-stat i { font-style: normal; grid-row: span 2; align-self: center; font-size: 16px; }
.rf-stat b { font-size: 19px; font-weight: 800; color: var(--k); font-variant-numeric: tabular-nums; line-height: 1.15; }
.rf-stat b small { font-size: 13px; font-weight: 600; color: var(--text-dim); }
.rf-stat span { font-size: 12px; color: var(--text-dim); }
.rf-sub-head { margin-top: 4px; font-size: 12px; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: .06em; }
.rf-closest { display: grid; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.rf-close-row { display: grid; grid-template-columns: minmax(0, 1fr) auto 90px; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 9px 14px 9px 16px; font: inherit; color: var(--text); cursor: pointer; border: 0; background: var(--surface);
  box-shadow: inset 4px 0 0 var(--cc, var(--border)); }
.rf-close-row + .rf-close-row { border-top: 1px solid var(--border); }
.rf-close-row:hover { background: color-mix(in srgb, var(--cc, var(--text)) 6%, var(--surface)); }
.rf-close-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rf-close-main { display: grid; min-width: 0; }
.rf-close-main b { font-size: 14px; }
.rf-close-main span { font-size: 12.5px; color: var(--text-dim); display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rf-close-left { font-size: 12px; font-weight: 700; padding: 2px 9px; border-radius: 999px; white-space: nowrap;
  background: color-mix(in srgb, var(--c-amber) 14%, var(--surface)); border: 1px solid color-mix(in srgb, var(--c-amber) 40%, var(--border)); }
.rf-meter-sm { margin: 0; height: 6px; }
@media (max-width: 520px) { .rf-close-row { grid-template-columns: minmax(0, 1fr) auto; } .rf-close-row .rf-meter { display: none; } }
.rf-today-name b.rf-pct { display: inline; font-size: inherit; color: var(--text); }

/* Rocket Fleet → Settings: one panel in three parts — the firm's, a block per
   carrier, this device's. A carrier block wears its carrier's colour (the
   same one Today and the Calendar give it); its buttons open that carrier's
   setting. */
.rf-settings, .rf-wide { max-width: 1100px; margin: 0 auto; }
.rf-set-grid.admin-menu { margin: 0; }
.rf-set-cos { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 12px; }
@media (max-width: 400px) { .rf-set-cos { grid-template-columns: minmax(0, 1fr); } }
.rf-set-co { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: var(--surface);
  box-shadow: inset 0 3px 0 var(--cc); }
.rf-set-co-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; padding: 12px 14px 8px;
  background: color-mix(in srgb, var(--cc) 7%, var(--surface)); border-bottom: 1px solid var(--border); }
.rf-set-co-head b { font-size: 15px; display: flex; align-items: center; gap: 7px; }
.rf-set-co-head > span { font-size: 12.5px; color: var(--text-dim); }
.rf-set-co-btns { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; padding: 10px; }
.rf-set-btn { display: flex; align-items: center; gap: 8px; text-align: left; font: inherit; color: var(--text); cursor: pointer;
  padding: 8px 10px; border-radius: 10px; background: color-mix(in srgb, var(--cc) 6%, var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--cc) 25%, var(--border)); transition: background .12s, transform .12s, box-shadow .12s; }
.rf-set-btn:hover { background: color-mix(in srgb, var(--cc) 15%, var(--surface)); transform: translateY(-1px); box-shadow: var(--shadow-control); }
.rf-set-btn:focus-visible { outline: 2px solid var(--cc); outline-offset: 2px; }
.rf-set-btn span { font-size: 16px; }
.rf-set-btn b { font-size: 13px; font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (prefers-reduced-motion: reduce) { .rf-set-btn { transition: none; } .rf-set-btn:hover { transform: none; } }

/* Settings → Buses: a section per carrier inside one panel. */
.rfb.rf-wide { display: block; }
.rfb-co-sec .rfb-wrap { border-radius: 12px; box-shadow: inset 4px 0 0 var(--cc); }
.rfb-co-name { display: flex; align-items: center; gap: 7px; text-transform: none !important; letter-spacing: 0 !important; font-size: 15px !important; color: var(--text) !important; }
.rfb-flag { font-size: 11px; font-weight: 700; padding: 1px 7px; border-radius: 999px; margin-left: 6px;
  background: color-mix(in srgb, var(--c-amber) 15%, var(--surface)); border: 1px solid color-mix(in srgb, var(--c-amber) 40%, var(--border)); }
.rfb-gapcount { color: var(--c-amber); font-weight: 700; }
.rfb-co-sec .rfb-table td:first-child { padding-left: 16px; }
.rfb-co-sec .rfb-table th:first-child { padding-left: 16px; }
/* Every carrier's table on the same columns, so the sections line up. */
@media (min-width: 761px) {
  .rfb-co-sec .rfb-table { table-layout: fixed; }
  .rfb-co-sec .rfb-table th:nth-child(1) { width: 17%; } .rfb-co-sec .rfb-table th:nth-child(2) { width: 7%; }
  .rfb-co-sec .rfb-table th:nth-child(3) { width: 20%; } .rfb-co-sec .rfb-table th:nth-child(4) { width: 21%; }
  .rfb-co-sec .rfb-table th:nth-child(5) { width: 14%; } .rfb-co-sec .rfb-table th:nth-child(6) { width: 10%; }
  .rfb-co-sec .rfb-table th:nth-child(7) { width: 11%; }
  .rfb-co-sec .rfb-table td { overflow: hidden; text-overflow: ellipsis; }
}

/* Today → Waiting for a course: one column per course. */
.rf-wait-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
@media (max-width: 640px) { .rf-wait-grid { grid-template-columns: minmax(0, 1fr); } }
.rf-wait-col { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: var(--surface); align-self: start; }
.rf-wait-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--c-violet) 8%, var(--surface)); }
.rf-wait-head b { font-size: 14px; flex: 1; }
.rf-wait-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; text-align: left;
  font: inherit; color: var(--text); cursor: pointer; border: 0; background: var(--surface); padding: 8px 14px 8px 16px;
  box-shadow: inset 4px 0 0 var(--cc, var(--border)); }
.rf-wait-row + .rf-wait-row { border-top: 1px solid var(--border); }
.rf-wait-row:hover { background: color-mix(in srgb, var(--cc, var(--text)) 6%, var(--surface)); }
.rf-wait-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rf-wait-row > span:first-child { display: grid; min-width: 0; }
.rf-wait-row b { font-size: 13.5px; }
.rf-wait-row small { font-size: 12px; color: var(--text-dim); display: flex; align-items: center; gap: 6px; }
.rf-none { margin: 0; padding: 12px 14px; font-size: 13px; color: var(--text-dim); }

/* The New hire dialog. Scoped through .modal — `.modal label` / `.modal input`
   would otherwise win (the modal specificity trap). */
.modal:has(.rf-hire) { max-width: 640px; }
.rf-hire { display: grid; gap: 6px; min-width: 0; }
.rf-hire-2 > div { min-width: 0; }
.modal .rf-hire input:not([type="checkbox"]), .modal .rf-hire select:not(#rh-phys-kind) { width: 100%; box-sizing: border-box; }
.rf-hire h3 { margin: 0; }
.rf-hire-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px; }
@media (max-width: 520px) { .rf-hire-2 { grid-template-columns: 1fr; } }
.rf-hire-list { display: grid; gap: 6px; margin-top: 10px; }
.modal .rf-hire-item { display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto; align-items: center; gap: 10px; margin: 0;
  padding: 9px 12px; border-radius: 10px; cursor: pointer; text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--text);
  background: color-mix(in srgb, var(--c-green) 7%, var(--surface)); border: 1.5px solid color-mix(in srgb, var(--c-green) 30%, var(--border)); }
.modal .rf-hire-item:has(input:not(:checked)) { background: var(--surface); border-color: var(--border); opacity: .7; }
.modal .rf-hire-fixed { grid-template-columns: auto minmax(0, 1fr); cursor: default; background: color-mix(in srgb, var(--c-slate) 7%, var(--surface)); border-color: var(--border); }
.modal .rf-hire-item input[type="checkbox"] { width: 18px; height: 18px; margin: 0; accent-color: var(--c-green); }
.modal .rf-hire-item > span:nth-of-type(1) { font-size: 18px; }
.modal .rf-hire-item b { display: block; font-size: 14px; }
.modal .rf-hire-item small { display: block; font-size: 12px; color: var(--text-dim); }
.modal .rf-hire-item select { width: auto; margin: 0; padding: 5px 8px; font-size: 13px; }
.rf-hire-fp summary { cursor: pointer; font-size: 12.5px; font-weight: 600; color: var(--c-blue); padding: 2px 4px; }
.modal .rf-hire-fp textarea { margin: 6px 0 0; font-size: 13.5px; }

/* All carriers: whose a row is, and "which carrier?" for what belongs to one. */
.rf-co-h { text-transform: none; letter-spacing: 0; font-size: 16px; color: var(--text); }
.rf-co-group:first-of-type .rf-co-h { margin-top: 4px; }
.rf-co-h { cursor: pointer; list-style: none; user-select: none; }
.rf-co-h::-webkit-details-marker { display: none; }
.rf-co-h:hover { color: var(--accent); }
.rf-co-h:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.rf-co-chev { font-style: normal; display: inline-block; width: 14px; color: var(--text-dim); transition: transform .15s; }
.rf-co-group[open] .rf-co-chev { transform: rotate(90deg); }
.rf-co-group:not([open]) .rf-co-h { margin-bottom: 0; }
@media (prefers-reduced-motion: reduce) { .rf-co-chev { transition: none; } }
.rf-co-h span { background: var(--badge-bg); color: var(--badge-fg); }
.pfp-co { font-size: 11.5px; color: var(--text-dim); margin-top: 2px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rf-pick { display: grid; gap: 10px; max-width: 560px; }
.rf-pick p { margin: 0; font-size: 15px; font-weight: 600; }

/* Settings → Buses: the details the forms are filled from. */
.rfb { display: grid; gap: 12px; }
.rfb-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.rfb-wrap { overflow-x: auto; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
.rfb-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.rfb-table th { text-align: left; font-size: 12px; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: .04em; padding: 10px 12px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.rfb-table td { padding: 10px 12px; border-bottom: 1px solid var(--border); white-space: nowrap; font-variant-numeric: tabular-nums; }
.rfb-table tr:last-child td { border-bottom: 0; }
.rfb-table tbody tr { cursor: pointer; }
.rfb-table tbody tr:hover { background: var(--surface-2); }
.rfb-table tbody tr:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rfb-table tr.rfb-gap td:first-child { box-shadow: inset 3px 0 0 var(--warning); }
.rfb-co { color: var(--text-dim); }
.rfb-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; }
.rfb-form { display: grid; gap: 4px; }

/* All drivers, every carrier. */
.rf-alldrv { display: grid; gap: 10px; max-width: 860px; }
.rf-alldrv-bar { display: grid; gap: 6px; }
.rf-alldrv-list { display: grid; gap: 6px; }
.rf-drv {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  width: 100%; text-align: left; padding: 10px 14px; border-radius: 12px; cursor: pointer;
  background: var(--surface); border: 1px solid var(--border); color: var(--text); font: inherit;
}
.rf-drv:hover { background: var(--surface-2); }
.rf-drv:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rf-drv-main { display: grid; min-width: 0; }
.rf-drv-main b { font-size: 14.5px; }
.rf-drv-main span { font-size: 13px; color: var(--text-dim); }
.rf-drv-nick { font-weight: 500; color: var(--text-dim); }
.rf-drv .rf-chip { cursor: inherit; }

/* ============================================================================
   ROCKET FLEET'S PAGE FRAME (rfFrame in src/client.ts). Every page is the same
   object: one width, centred, one panel, its own colour (--pg, one of the --c-*
   hues) as a band across the top and a wash behind the heading. The same hue
   sits on the page's tab, so the bar and the page agree on where you are. A
   screen that drew its own panel inside is flattened, so there is one edge and
   not a card in a card.
   ============================================================================ */
.rf-page {
  /* ONE width for every page on every screen, and not the window's (the owner,
     2026-10-12: "uniform and not too stretched"). */
  --rf-page-w: 980px;
  --pg: var(--c-slate);
  max-width: var(--rf-page-w); margin: 4px auto 28px;
  background: var(--surface); border: 1px solid var(--border);
  border-top: 4px solid var(--pg); border-radius: 16px;
  box-shadow: 0 1px 2px rgba(0,0,0,.06), 0 6px 20px rgba(0,0,0,.05);
  overflow: clip;
}
.rf-page-head {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 20px 12px;
  background: color-mix(in srgb, var(--pg) 10%, var(--surface));
  border-bottom: 1px solid color-mix(in srgb, var(--pg) 28%, var(--border));
}
.rf-page-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 9px; font-size: 17px;
  background: color-mix(in srgb, var(--pg) 20%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--pg) 40%, var(--border));
}
.rf-page-title { font-size: 17px; font-weight: 750; letter-spacing: -.1px; }
/* Training: two contained sections — the refresher courses, then the school
   safety drills filed by carrier (hydrateCoursesPanel). */
/* Each section wears its own hue (--sec) — a band on top, a washed head, the
   title in that colour — so the two read as two things at a glance. The cards
   inside are flat: one edge per thing, not a card in a card in a card. */
.rf-page-body .trn-refresher { --sec: var(--c-blue); }
.rf-page-body .trn-drills { --sec: var(--c-amber); }
.rf-page-body .trn-section {
  border: 1px solid color-mix(in srgb, var(--sec) 30%, var(--border));
  border-top: 3px solid var(--sec);
  border-radius: 14px; padding: 0 16px 16px; margin: 0 0 26px;
  background: var(--surface); overflow: clip;
}
.rf-page-body .trn-section:last-child { margin-bottom: 0; }
.rf-page-body .trn-section > .section-head {
  margin: 0 -16px 16px; padding: 12px 16px;
  border-bottom: 1px solid color-mix(in srgb, var(--sec) 25%, var(--border));
  background: color-mix(in srgb, var(--sec) 9%, var(--surface));
}
.rf-page-body .trn-section > .section-head .section-title {
  margin: 0; padding-left: 0; border-left: 0; color: var(--sec); font-weight: 750; letter-spacing: .05em;
}
/* The refresher courses side by side — two cycles of one requirement. */
.rf-page-body .trn-refresher .course-list {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 14px;
}
.rf-page-body .trn-section .course-card {
  box-shadow: none; padding: 14px 14px 12px;
  border-color: color-mix(in srgb, var(--sec) 22%, var(--border));
  display: flex; flex-direction: column;
}
.rf-page-body .trn-section .course-card::before { display: none; }
.rf-page-body .trn-section .course-card-active { border-color: var(--sec);
  box-shadow: inset 0 3px 0 var(--sec); }
.rf-page-body .trn-section .course-card:hover { transform: none; box-shadow: none; border-color: var(--sec); }
.rf-page-body .trn-section .course-card-active:hover { box-shadow: inset 0 3px 0 var(--sec); }
.rf-page-body .trn-section .course-card-head { margin-bottom: 12px; flex-direction: column; align-items: stretch; gap: 10px; }
.rf-page-body .trn-section .cc-head-btns { display: flex; gap: 6px; flex-wrap: wrap; }
@media (max-width: 640px) {
  .rf-page-body .trn-section > .section-head { flex-wrap: wrap; }
  .rf-page-body .trn-section > .section-head .section-title { width: 100%; }
}
.rf-page-body .trn-section .course-card-head h3 { font-size: 16.5px; }
.rf-page-body .trn-section .cc-icon { width: 34px; height: 34px; font-size: 17px; border-radius: 9px;
  background: color-mix(in srgb, var(--sec) 14%, var(--surface));
  border-color: color-mix(in srgb, var(--sec) 35%, var(--border)); }
.rf-page-body .trn-section .course-card .empty-mini {
  margin-top: 12px; padding: 0; text-align: left; font-size: 12.5px; font-weight: 500; color: var(--text-dim);
}
.rf-page-body .trn-section .course-foot { margin-top: auto; padding-top: 10px; border-top: 0; }
.trn-head-btns { display: flex; flex-wrap: wrap; gap: 6px; }
.trn-carrier { border: 1px solid color-mix(in srgb, var(--cc) 35%, var(--border)); border-left: 4px solid var(--cc);
  border-radius: 12px; padding: 10px 12px 12px; margin-bottom: 12px; }
.trn-carrier:last-child { margin-bottom: 0; }
.trn-carrier-head { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 14.5px; margin-bottom: 10px; }
.trn-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--cc); }
.trn-count { font-size: 11.5px; font-weight: 700; padding: 1px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--cc) 16%, var(--surface)); }

/* Rocket Fleet's Reports menu (openReportsMenu): one row per report, each in
   its hue. Scoped through .modal so it outranks `.modal button`. */
.modal .rep-menu-list { display: grid; gap: 8px; margin: 4px 0 6px; }
.modal .rep-menu-scope { display: grid; gap: 7px; margin: 0 0 14px; }
.modal .rep-scope-label { font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--text-dim); }
.modal .rep-scope { display: flex; flex-wrap: wrap; gap: 7px; }
.modal .rep-scope-opt {
  display: inline-flex; align-items: center; gap: 7px; width: auto; margin: 0;
  padding: 7px 13px; border-radius: 999px; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  color: var(--text-dim); background: var(--surface);
  border: 1.5px solid var(--border);
  transition: background .12s, border-color .12s, color .12s, box-shadow .12s;
}
.modal .rep-scope-opt:hover { border-color: color-mix(in srgb, var(--cc) 50%, var(--border)); color: var(--text); }
.modal .rep-scope-opt.on {
  color: var(--text);
  background: color-mix(in srgb, var(--cc) 14%, var(--surface));
  border-color: color-mix(in srgb, var(--cc) 70%, var(--border));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cc) 14%, transparent);
}
.modal .rep-scope-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--cc); flex: 0 0 auto; }
.modal .rep-scope-opt.on .rep-scope-dot { box-shadow: 0 0 0 2px var(--surface), 0 0 0 3.5px var(--cc); }
.modal .rep-menu-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 12px 14px; border-radius: 12px; cursor: pointer; font: inherit; color: var(--text);
  background: color-mix(in srgb, var(--k) 8%, var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--k) 38%, var(--border));
  transition: background .12s, transform .12s;
}
.modal .rep-menu-row:hover { background: color-mix(in srgb, var(--k) 15%, var(--surface)); transform: translateY(-1px); }
.modal .rep-menu-ico {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: 10px; font-size: 18px;
  background: color-mix(in srgb, var(--k) 20%, var(--surface));
}
.modal .rep-menu-txt { display: grid; gap: 2px; min-width: 0; flex: 1; }
.modal .rep-menu-txt b { font-size: 14.5px; }
.modal .rep-menu-txt span { font-size: 12.5px; color: var(--text-dim); }
.modal .rep-menu-chev { color: var(--text-dim); font-size: 18px; }

/* The screen's own buttons, lifted into the head (rfLiftHead). */
.rf-page-head { flex-wrap: wrap; }
.rf-page-acts { margin-left: auto; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
@media (max-width: 640px) { .rf-page-acts { margin-left: 0; width: 100%; } }
.rf-page-body { padding: 18px 20px 22px; min-height: 50vh; }
/* The panels screens bring with them, flattened into the frame. */
.rf-page-body > .disp-body,
.rf-page-body > .disp-body.treq,
.rf-page-body > .admin-menu,
.rf-page-body > .settings-pane { max-width: none; margin: 0; background: none; border: 0; box-shadow: none; padding: 0; }
/* Screens that ARE a panel (Today, the Calendar, Settings) become the frame's
   body: their sections run edge to edge under the page's head instead of a
   second card sitting inside the first. Their own head loses its blue wash —
   the page's colour is the head above it. */
.rf-page-body:has(> .rf-today, > .rx, > .rf-settings) { padding: 0; }

/* ============================================================================
   IDEA HUB (src/rocket-ideas.ts, 0157). Contained and colour-coded like the
   rest of Rocket: everything sits in the page frame; `--k` is ONE hue per
   thing — a door, a state, a speaker — and its edge, wash and pill read it.
   Amber idea, violet assistant, blue owner/send, green approve, red decline
   and record, teal attach, slate neutral. The .rbtn palette is the buttons'.
   ============================================================================ */
.ih { display: grid; gap: 14px; }
.ih-loading, .ih-empty { padding: 28px 10px; text-align: center; color: var(--text-dim); font-size: 14px; }

.ih-doors { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.ih-door {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: 12px; align-items: center;
  text-align: left; padding: 14px 16px; border-radius: 14px; cursor: pointer; font: inherit; color: var(--text);
  background: color-mix(in srgb, var(--k) 9%, var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--k) 40%, var(--border));
  transition: transform .12s, background .12s, box-shadow .12s;
}
.ih-door:hover { transform: translateY(-1px); background: color-mix(in srgb, var(--k) 16%, var(--surface)); box-shadow: var(--shadow-control); }
.ih-door-ico { grid-row: span 2; display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px;
  border-radius: 11px; font-size: 20px; background: color-mix(in srgb, var(--k) 22%, var(--surface)); }
.ih-door b { font-size: 15px; }
.ih-door span:last-child { font-size: 12.5px; color: var(--text-dim); }

.ih-seg { display: inline-flex; gap: 4px; padding: 3px; border-radius: 11px; background: var(--bg-2, var(--bg)); border: 1px solid var(--border); justify-self: start; }
.ih-seg button { font: inherit; font-size: 13px; font-weight: 700; padding: 6px 14px; border: 0; border-radius: 8px; cursor: pointer; background: none; color: var(--text-dim); }
.ih-seg button.on { color: var(--text); background: color-mix(in srgb, var(--c-blue) 18%, var(--surface)); box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--c-blue) 50%, var(--border)); }
.ih-count { display: inline-block; min-width: 18px; padding: 0 5px; border-radius: 999px; font-size: 11px; line-height: 18px; text-align: center;
  background: var(--c-red); color: #fff; }

.ih-list { display: grid; gap: 8px; }
.ih-row {
  position: relative; display: grid; grid-template-columns: auto 1fr auto; gap: 12px; align-items: center;
  width: 100%; text-align: left; font: inherit; color: var(--text); cursor: pointer;
  padding: 11px 14px; border-radius: 12px; background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--k) 30%, var(--border)); border-left: 4px solid var(--k);
  transition: background .12s, transform .12s;
}
.ih-row:hover { background: color-mix(in srgb, var(--k) 7%, var(--surface)); transform: translateY(-1px); }
.ih-row.unread .ih-row-title { font-weight: 800; }
.ih-row-ico { font-size: 20px; }
.ih-row-main { display: grid; gap: 2px; min-width: 0; }
.ih-row-title { font-weight: 650; font-size: 14.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ih-row-sub { font-size: 12.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ih-row-side { display: grid; gap: 4px; justify-items: end; }
.ih-row-meta { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; }
.ih-dot { position: absolute; top: 9px; right: 9px; width: 9px; height: 9px; border-radius: 50%; background: var(--c-red); }
.ih-pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 700; white-space: nowrap;
  background: color-mix(in srgb, var(--k) 16%, var(--surface)); border: 1px solid color-mix(in srgb, var(--k) 45%, var(--border)); }

.ih-thread { display: grid; gap: 14px; }
.ih-thread-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ih-thread-head h3 { margin: 0; font-size: 17px; flex: 1 1 200px; min-width: 0; }
.ih-thread-ico { font-size: 22px; }
.ih-from { font-size: 12.5px; color: var(--text-dim); }
.ih-card { border: 1px solid var(--border); border-radius: 14px; padding: 14px 16px; background: var(--surface); display: grid; gap: 12px; }
.ih-original { border-left: 4px solid var(--k); background: color-mix(in srgb, var(--k) 5%, var(--surface)); }
.ih-original-text { font-size: 14px; line-height: 1.5; }

.ih-plan { border: 1.5px solid color-mix(in srgb, var(--k) 45%, var(--border)); border-radius: 14px; overflow: clip;
  background: color-mix(in srgb, var(--k) 5%, var(--surface)); }
.ih-plan-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 16px;
  background: color-mix(in srgb, var(--k) 13%, var(--surface)); border-bottom: 1px solid color-mix(in srgb, var(--k) 30%, var(--border)); }
.ih-plan-body { padding: 4px 16px 6px; font-size: 14px; line-height: 1.5; }
.ih-plan-body h4 { margin: 12px 0 4px; font-size: 13px; text-transform: uppercase; letter-spacing: .3px; color: var(--text-dim); }
.ih-plan-body p { margin: 6px 0; } .ih-plan-body ul { margin: 4px 0 8px; padding-left: 20px; }
.ih-plan-empty { display: grid; gap: 4px; padding: 12px 16px; }
.ih-plan-empty span { font-size: 13px; color: var(--text-dim); }
.ih-plan-acts { display: flex; flex-wrap: wrap; gap: 8px; padding: 10px 16px 14px; }
.ih-plan-acts:empty { display: none; }
.ih-note { margin: 4px 16px 0; padding: 8px 12px; border-radius: 10px; font-size: 13.5px;
  background: color-mix(in srgb, var(--c-blue) 10%, var(--surface)); border: 1px solid color-mix(in srgb, var(--c-blue) 35%, var(--border)); }
.ih-note-built { background: color-mix(in srgb, var(--c-teal) 10%, var(--surface)); border-color: color-mix(in srgb, var(--c-teal) 40%, var(--border)); }

.ih-chan { border: 1px solid color-mix(in srgb, var(--k) 30%, var(--border)); border-radius: 14px; overflow: clip; background: var(--surface); }
.ih-chan-head { margin: 0; padding: 10px 16px; font-size: 14px; background: color-mix(in srgb, var(--k) 10%, var(--surface));
  border-bottom: 1px solid color-mix(in srgb, var(--k) 25%, var(--border)); }
.ih-msgs { display: grid; gap: 10px; padding: 14px 16px; max-height: 60vh; overflow-y: auto; }
.ih-msg { max-width: 86%; justify-self: start; padding: 9px 13px; border-radius: 14px 14px 14px 4px;
  background: color-mix(in srgb, var(--k) 9%, var(--surface)); border: 1px solid color-mix(in srgb, var(--k) 30%, var(--border)); }
.ih-msg.me { justify-self: end; border-radius: 14px 14px 4px 14px; }
.ih-msg-who { font-size: 11.5px; font-weight: 700; color: var(--text-dim); margin-bottom: 3px; }
.ih-msg-body { font-size: 14px; line-height: 1.5; }
.ih-msg-body p { margin: 4px 0; } .ih-msg-body ul { margin: 4px 0; padding-left: 20px; } .ih-msg-body h4 { margin: 8px 0 2px; font-size: 13.5px; }
.ih-sys { justify-self: center; font-size: 12px; color: var(--text-dim); padding: 3px 10px; border-radius: 999px; background: var(--bg); }
.ih-fold > summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--c-violet); padding: 4px 2px; }
.ih-fold[open] > summary { margin-bottom: 10px; }

.ih-files { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.ih-shot img { display: block; max-width: 220px; max-height: 160px; border-radius: 10px; border: 1px solid var(--border); object-fit: cover; }
.ih-audio { width: 100%; max-width: 360px; height: 36px; }

.ih-compose { display: grid; gap: 8px; padding: 12px 16px 14px; border-top: 1px solid var(--border); }
.ih-card > .ih-compose { padding: 0; border-top: 0; }
.ih-compose.sending { opacity: .75; }
.ih-field { display: grid; gap: 4px; font-size: 12.5px; font-weight: 700; color: var(--text-dim); }
.ih-field input, .ih-field textarea { font: inherit; font-size: 14px; font-weight: 400; color: var(--text); width: 100%; box-sizing: border-box;
  padding: 9px 11px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg); resize: vertical; }
.ih-compose-bar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.ih-compose-bar .ih-send { margin-left: auto; }
.ih-pick { cursor: pointer; }
.ih-rec { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 10px; font-size: 13px;
  background: color-mix(in srgb, var(--c-red) 10%, var(--surface)); border: 1px solid color-mix(in srgb, var(--c-red) 40%, var(--border)); }
.ih-rec[hidden] { display: none; }
.ih-rec [data-live] { color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ih-rec-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--c-red); animation: ih-pulse 1s ease-in-out infinite; flex: 0 0 auto; }
@keyframes ih-pulse { 50% { opacity: .3; } }
.ih-pending { display: flex; flex-wrap: wrap; gap: 8px; }
.ih-pending:empty { display: none; }
.ih-chip { position: relative; }
.ih-chip img { display: block; width: 72px; height: 56px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border); }
.ih-chip .ih-chip-x { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--c-red); color: #fff; font-size: 13px; line-height: 20px; padding: 0; }
.ih-voice-chip { flex: 1 1 100%; display: grid; gap: 6px; padding: 10px 12px; border-radius: 10px;
  background: color-mix(in srgb, var(--c-red) 6%, var(--surface)); border: 1px solid color-mix(in srgb, var(--c-red) 30%, var(--border)); }
.ih-voice-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 13px; font-weight: 700; }
.ih-voice-chip audio { width: 100%; height: 34px; }

.modal .ih-modal { display: grid; gap: 12px; }
.modal .ih-modal h3 { margin: 0; }
.ih-hint { margin: 0; font-size: 13px; color: var(--text-dim); }
.ih-modal-acts { display: flex; justify-content: flex-end; gap: 8px; }
@media (max-width: 640px) {
  .ih-row { grid-template-columns: auto 1fr; }
  .ih-row-side { grid-column: 2; justify-items: start; grid-auto-flow: column; align-items: center; }
  .ih-msg { max-width: 94%; }
}
@media (prefers-reduced-motion: reduce) {
  .ih-door, .ih-row { transition: none; } .ih-door:hover, .ih-row:hover { transform: none; }
  .ih-rec-dot { animation: none; }
}

/* People look like people: a face is a picture, or initials on the person's
   own hue (--h, id × 47 — the roster's recipe). The assistant is violet. */
.ih-av { --s: 36px; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: var(--s); height: var(--s); border-radius: 50%; overflow: hidden; vertical-align: middle;
  font-size: calc(var(--s) * .4); font-weight: 750; color: #fff; letter-spacing: .3px;
  box-shadow: 0 0 0 2px var(--surface), 0 1px 3px rgba(0,0,0,.18); }
.ih-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ih-av-art { background: none; }
.ih-av-art svg { width: 100%; height: 100%; display: block; }
/* The hub's three characters — the idea, Rocket Assist, the owner — each wear
   a light RING in their own colour, with a hairline of surface inside it; no
   background disc behind any drawing (the owner: "the ring is too dark — no
   background and more contrast on all three"). */
.ih-av-art { background: var(--surface); }
.ih-av-ring { --ring: var(--c-slate); box-shadow: 0 0 0 1.5px var(--surface), 0 0 0 3px color-mix(in srgb, var(--ring) 45%, var(--surface)); }
.ih-ring-violet { --ring: #8b6cf0; } .ih-ring-blue { --ring: #3b82f6; } .ih-ring-amber { --ring: #f5a524; }
.ih-av-init { background: hsl(var(--h) 48% 46%); }
.ih-av-ai { background: color-mix(in srgb, var(--c-violet) 28%, var(--surface)); font-size: calc(var(--s) * .55); }
.ih-av-sm { --s: 24px; } .ih-av-door { --s: 40px; } .ih-av-lg { --s: 54px; } .ih-av-xl { --s: 96px; }

.ih-hello { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.ih-me { position: relative; padding: 0; border: 0; background: none; cursor: pointer; border-radius: 50%; }
.ih-me-cam { position: absolute; right: -3px; bottom: -3px; width: 22px; height: 22px; border-radius: 50%; font-size: 12px;
  display: inline-flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--border); }
.ih-hello-txt { display: grid; gap: 2px; flex: 1 1 220px; }
.ih-hello-txt b { font-size: 18px; }
.ih-hello-txt span { font-size: 13.5px; color: var(--text-dim); }
.ih-door-face { background: none !important; }

.ih-row-face { position: relative; }
.ih-row-kind { position: absolute; right: -5px; bottom: -4px; font-size: 13px; line-height: 1; }
.ih-row-sub b { color: var(--text); font-weight: 650; }

.ih-msg-row { display: flex; align-items: flex-end; gap: 8px; justify-self: start; max-width: 92%; }
.ih-msg-row.me { justify-self: end; flex-direction: row-reverse; }
.ih-msg-row .ih-msg { max-width: none; }
.ih-msg-row .ih-av { --s: 32px; }
.ih-msg-who { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.ih-msg-who b { color: var(--text); font-size: 12.5px; }
.ih-tag { font-size: 10.5px; font-weight: 700; padding: 0 6px; border-radius: 999px; color: var(--text-dim);
  background: color-mix(in srgb, var(--k, var(--c-slate)) 12%, var(--surface)); border: 1px solid color-mix(in srgb, var(--k, var(--c-slate)) 30%, var(--border)); }
.ih-chan-head { display: flex; align-items: center; gap: 8px; }
.ih-by { display: flex; align-items: center; gap: 10px; }
.ih-by b { display: block; font-size: 14px; }
.ih-by span { font-size: 12px; color: var(--text-dim); }

/* The capture bar floats over every screen until the shot is taken. */
.ih-capbar { position: fixed; z-index: 9000; left: 50%; bottom: calc(84px + env(safe-area-inset-bottom)); transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: center; max-width: calc(100vw - 24px);
  padding: 10px 14px; border-radius: 14px; background: var(--surface); color: var(--text);
  border: 2px solid var(--c-blue); box-shadow: 0 10px 30px rgba(0,0,0,.25); }
.ih-capbar-txt { font-size: 13.5px; font-weight: 600; }
@media (min-width: 1024px) { .ih-capbar { bottom: 24px; } }

.modal .ih-persona, .modal .ih-photo { display: grid; gap: 14px; }
.modal .ih-photo-big { display: flex; justify-content: center; }
.ih-persona-card { display: flex; gap: 16px; align-items: flex-start; padding: 14px; border-radius: 14px;
  background: color-mix(in srgb, var(--k) 7%, var(--surface)); border: 1.5px solid color-mix(in srgb, var(--k) 35%, var(--border)); }
.ih-persona-fields { display: grid; gap: 8px; flex: 1; min-width: 0; }
.ih-persona-acts { display: flex; flex-wrap: wrap; gap: 8px; }
@media (max-width: 520px) { .ih-persona-card { flex-direction: column; align-items: center; } .ih-persona-fields { width: 100%; } }
/* Inside a dialog, .modal label (0,1,1) would uppercase a picture button that is
   a <label> and shrink the field captions — the modal specificity trap. */
.modal .ih label.rbtn { display: inline-flex; margin: 0; text-transform: none; letter-spacing: 0; font-size: 12.5px; font-weight: 700; color: var(--text); }
.modal .ih .ih-field { margin: 0; text-transform: none; letter-spacing: 0; font-size: 12.5px; }

/* The markup sheet: a wide dialog, the picture as large as the window allows,
   the tools along the top. A finger on the picture draws, never scrolls. */
.modal:has(> .ih-markup) { max-width: min(1180px, 96vw); width: 96vw; padding: 14px; }
.modal .ih-markup { display: grid; gap: 10px; }
.ih-mk-bar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.ih-mk-group { display: inline-flex; gap: 4px; padding: 3px; border-radius: 11px; background: var(--bg); border: 1px solid var(--border); }
.modal .ih-mk-tool { display: inline-flex; align-items: center; gap: 5px; padding: 6px 10px; border: 0; border-radius: 8px; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 700; color: var(--text-dim); background: none; margin: 0; width: auto; }
.modal .ih-mk-tool.on { color: var(--text); background: color-mix(in srgb, var(--c-red) 16%, var(--surface)); box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--c-red) 50%, var(--border)); }
.modal .ih-mk-ink { width: 26px; height: 26px; padding: 0; margin: 0; border-radius: 50%; cursor: pointer; background: var(--ink);
  border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--border); }
.modal .ih-mk-ink.on { box-shadow: 0 0 0 2.5px var(--ink); }
.modal .ih-mk-text { width: 200px; margin: 0; padding: 6px 10px; border-radius: 9px; font-size: 14px; }
.ih-mk-spacer { flex: 1; }
.ih-mk-stage { display: flex; justify-content: center; background: repeating-conic-gradient(var(--bg) 0 25%, var(--surface) 0 50%) 0 0 / 18px 18px;
  border: 1px solid var(--border); border-radius: 12px; padding: 8px; }
.ih-mk-stage canvas { max-width: 100%; max-height: 50vh; height: auto; touch-action: none; cursor: crosshair; border-radius: 6px;
  box-shadow: 0 2px 10px rgba(0,0,0,.15); }
.ih-mk-say { display: grid; gap: 8px; }
.modal .ih-mk-say textarea { margin: 0; }
.ih-mk-voice { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ih-mk-live { font-size: 13px; color: var(--text-dim); min-width: 0; }
@media (max-width: 640px) { .modal .ih-mk-tool span { display: none; } .modal .ih-mk-text { width: 100%; } }
/* The ✏️ on a picture waiting to be sent opens it in the markup sheet. */
/* The split: conversations beside the open one (≥768px, the Messages split's
   width); below it, the list, then the conversation alone with its back arrow. */
.ih-split { display: flex; flex-direction: column; gap: 16px; }
.ih-split .ih-main { order: 0; min-width: 0; }
.ih-split .ih-side { order: 1; display: grid; gap: 8px; align-content: start; min-width: 0; }
.ih-split.has-open .ih-side { display: none; }
@media (min-width: 768px) {
  .ih-split { display: grid; grid-template-columns: 270px minmax(0, 1fr); gap: 0; align-items: start;
    margin: -18px -20px -22px; min-height: 62vh; }
  .ih-split .ih-side, .ih-split.has-open .ih-side { order: 0; display: grid; padding: 14px 12px; border-right: 1px solid var(--border);
    background: color-mix(in srgb, var(--c-amber) 4%, var(--surface)); align-self: stretch; position: sticky; top: calc(var(--topbar-h) + var(--subbar-h));
    max-height: calc(100vh - var(--topbar-h) - var(--subbar-h) - 40px); overflow-y: auto; }
  .ih-split .ih-main { padding: 18px 20px 22px; }
  .ih-split .ih-thread-head [data-back] { display: none; }
}
.ih-welcome { display: grid; gap: 14px; }
.ih-side-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ih-side-head b { font-size: 14px; }
.ih-side-new { display: inline-flex; gap: 4px; }
.ih-new { width: 32px; height: 32px; border-radius: 9px; cursor: pointer; font-size: 15px; padding: 0;
  background: color-mix(in srgb, var(--k) 12%, var(--surface)); border: 1.5px solid color-mix(in srgb, var(--k) 40%, var(--border)); }
.ih-new:hover { background: color-mix(in srgb, var(--k) 22%, var(--surface)); }
.ih-new { border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; }
.ih-av-new { --s: 26px; box-shadow: none; }
/* Action cards under Rocket Assist's messages: a proposal, its Do it. */
.ih-acts { display: grid; gap: 8px; margin-top: 10px; }
.ih-act { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border-radius: 12px; background: var(--surface);
  border: 1.5px solid color-mix(in srgb, var(--k) 40%, var(--border)); border-left: 4px solid var(--k); }
.ih-act-ico { font-size: 18px; line-height: 1.2; }
.ih-act-txt { display: grid; gap: 3px; min-width: 0; flex: 1; }
.ih-act-txt b { font-size: 13.5px; }
.ih-act-txt > span { font-size: 12.5px; color: var(--text-dim); overflow-wrap: anywhere; }
.ih-act-btns { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.ih-act-state { margin-top: 4px; font-size: 12.5px; font-weight: 700; }
.ih-act-done { color: var(--c-green); } .ih-act-failed { color: var(--c-red); } .ih-act-declined { color: var(--text-dim); }

/* ROCKET ASSIST'S THEME IS THE APP'S (the owner: "change the Assist theme to
   the app's theme"): his conversations wear the page frame's own head — a
   band washed in his hue (--c-violet, the palette every Rocket page uses) over
   a hairline — and his bubbles are that hue's tint, like every other coloured
   thing in the app. No colours of his own. */
.ih-assist > .ih-thread-head {
  padding: 10px 14px; border-radius: 14px;
  background: color-mix(in srgb, var(--c-violet) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--c-violet) 30%, var(--border)); }
.ih-assist .ih-chan { border-color: color-mix(in srgb, var(--c-violet) 30%, var(--border)); }
.ih-assist .ih-chan-head { display: none; }
.ih-msg-ai { background: color-mix(in srgb, var(--c-violet) 9%, var(--surface));
  border-color: color-mix(in srgb, var(--c-violet) 35%, var(--border)); }
.ih-assist .ih-round[data-mode="send"] { background: var(--c-violet); }

/* ASSIST IN COLOUR (the owner: "change the colours to more exciting"). The
   rest of the app keeps its muted palette; this one screen is where people
   come to talk, so each of its three characters wears a vivid GRADIENT —
   the idea a sunset (amber → tangerine), Rocket Assist violet → pink, the
   owner ocean blue → teal — on its door, its bubbles, its Send, its chips.
   Scoped through #ih-root, so it outranks the base rules wherever they sit,
   and tints mixed into --surface, so dark mode stays dark. */
#ih-root, .rf-page:has(#ih-root) {
  --ih-sun-a: #f59e0b; --ih-sun-b: #ff6a3d;
  --ih-rk-a: #7c3aed; --ih-rk-b: #ec4899;
  --ih-sea-a: #2563eb; --ih-sea-b: #14b8a6;
  --ih-go-a: #10b981; --ih-go-b: #06b6d4;
}
.rf-page:has(#ih-root) { border-top: 0; }
.rf-page:has(#ih-root) .rf-page-head {
  background: linear-gradient(100deg, color-mix(in srgb, var(--ih-rk-a) 16%, var(--surface)), color-mix(in srgb, var(--ih-rk-b) 12%, var(--surface)) 55%, color-mix(in srgb, var(--ih-sun-b) 12%, var(--surface)));
  border-bottom: 0; box-shadow: inset 0 4px 0 0 transparent; position: relative; }
.rf-page:has(#ih-root) .rf-page-head::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 4px;
  background: linear-gradient(90deg, var(--ih-sun-a), var(--ih-sun-b), var(--ih-rk-b), var(--ih-rk-a), var(--ih-sea-a), var(--ih-sea-b)); }
/* The page icon is a round badge like the avatars (the owner: "make it in
   a circle"): the rocket on a soft wash, a light violet ring around it. */
.rf-page:has(#ih-root) .rf-page-ico { border-radius: 50%; width: 34px; height: 34px; font-size: 18px; border: 0;
  background: color-mix(in srgb, var(--ih-rk-a) 10%, var(--surface));
  box-shadow: 0 0 0 1.5px var(--surface), 0 0 0 3px color-mix(in srgb, var(--ih-rk-a) 45%, var(--surface)); }

/* The three doors. */
#ih-root .ih-door { border: 0; position: relative; overflow: hidden; box-shadow: 0 2px 10px color-mix(in srgb, var(--g-a) 22%, transparent); }
#ih-root .ih-door::before { content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1.5px;
  background: linear-gradient(135deg, var(--g-a), var(--g-b));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none; }
#ih-root .ih-door[data-new="idea"]    { --g-a: var(--ih-sun-a); --g-b: var(--ih-sun-b); }
#ih-root .ih-door[data-new="help"]    { --g-a: var(--ih-rk-a);  --g-b: var(--ih-rk-b); }
#ih-root .ih-door[data-new="contact"] { --g-a: var(--ih-sea-a); --g-b: var(--ih-sea-b); }
#ih-root .ih-door { background: linear-gradient(135deg, color-mix(in srgb, var(--g-a) 16%, var(--surface)), color-mix(in srgb, var(--g-b) 14%, var(--surface))); }
#ih-root .ih-door:hover { background: linear-gradient(135deg, color-mix(in srgb, var(--g-a) 26%, var(--surface)), color-mix(in srgb, var(--g-b) 22%, var(--surface)));
  box-shadow: 0 6px 18px color-mix(in srgb, var(--g-b) 32%, transparent); }
#ih-root .ih-hello-txt b { background: linear-gradient(90deg, var(--ih-rk-a), var(--ih-rk-b) 60%, var(--ih-sun-b));
  -webkit-background-clip: text; background-clip: text; color: transparent; font-size: 20px; }

/* The side list: the open conversation glows in its own colours. */
#ih-root .ih-side { background: linear-gradient(180deg, color-mix(in srgb, var(--ih-rk-a) 6%, var(--surface)), color-mix(in srgb, var(--ih-sea-b) 5%, var(--surface))); }
#ih-root .ih-crow.on { background: linear-gradient(90deg, color-mix(in srgb, var(--ih-rk-a) 18%, var(--surface)), color-mix(in srgb, var(--ih-rk-b) 10%, var(--surface)));
  border-left-color: var(--ih-rk-b); }
#ih-root .ih-new { border: 0; box-shadow: 0 0 0 2px color-mix(in srgb, var(--k) 45%, transparent); }

/* Rocket Assist's chat. */
#ih-root .ih-assist > .ih-thread-head {
  background: linear-gradient(110deg, color-mix(in srgb, var(--ih-rk-a) 20%, var(--surface)), color-mix(in srgb, var(--ih-rk-b) 16%, var(--surface)));
  border-color: color-mix(in srgb, var(--ih-rk-b) 40%, var(--border)); }
#ih-root .ih-msg-ai { background: linear-gradient(135deg, color-mix(in srgb, var(--ih-rk-a) 13%, var(--surface)), color-mix(in srgb, var(--ih-rk-b) 10%, var(--surface)));
  border-color: color-mix(in srgb, var(--ih-rk-b) 40%, var(--border)); }
#ih-root .ih-msg-ai .ih-msg-who b { color: var(--ih-rk-a); }
#ih-root .ih-msg-row.me .ih-msg { background: linear-gradient(135deg, color-mix(in srgb, var(--ih-sea-a) 14%, var(--surface)), color-mix(in srgb, var(--ih-sea-b) 12%, var(--surface)));
  border-color: color-mix(in srgb, var(--ih-sea-a) 35%, var(--border)); }

/* Send: each chat's own gradient. */
#ih-root .ih-round:not(:disabled) { background: linear-gradient(135deg, var(--ih-sea-a), var(--ih-sea-b)); box-shadow: 0 4px 14px color-mix(in srgb, var(--ih-sea-a) 40%, transparent); }
#ih-root .ih-assist .ih-round:not(:disabled) { background: linear-gradient(135deg, var(--ih-rk-a), var(--ih-rk-b)); box-shadow: 0 4px 14px color-mix(in srgb, var(--ih-rk-b) 40%, transparent); }
#ih-root .ih-compose[style*="--c-amber"] .ih-round:not(:disabled) { background: linear-gradient(135deg, var(--ih-sun-a), var(--ih-sun-b)); box-shadow: 0 4px 14px color-mix(in srgb, var(--ih-sun-b) 40%, transparent); }
#ih-root .ih-box:focus-within { border-color: var(--ih-rk-b); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ih-rk-b) 18%, transparent); }

/* Examples: the tasks go, the questions ask. */
/* The examples whisper: a soft wash of his own colours, dark text (the
   owner: "the examples are too much… the colours"). */
#ih-root .ih-ex-task { color: var(--text); font-weight: 600; box-shadow: none;
  background: linear-gradient(135deg, color-mix(in srgb, var(--ih-rk-a) 9%, var(--surface)), color-mix(in srgb, var(--ih-rk-b) 7%, var(--surface)));
  border: 1px solid color-mix(in srgb, var(--ih-rk-b) 30%, var(--border)); }
#ih-root .ih-ex-task:hover { background: linear-gradient(135deg, color-mix(in srgb, var(--ih-rk-a) 16%, var(--surface)), color-mix(in srgb, var(--ih-rk-b) 12%, var(--surface))) !important; filter: none; }
#ih-root .ih-ex-q:not(.ih-ex-task) { background: linear-gradient(135deg, color-mix(in srgb, var(--ih-rk-a) 12%, var(--surface)), color-mix(in srgb, var(--ih-rk-b) 10%, var(--surface)));
  border-color: color-mix(in srgb, var(--ih-rk-b) 40%, var(--border)); }
#ih-root .ih-ex-label { color: var(--ih-rk-a); }

/* Action cards: Do it in green → cyan, with a coloured edge per kind. */
#ih-root .ih-act { box-shadow: 0 2px 10px color-mix(in srgb, var(--k) 18%, transparent); }
#ih-root .ih-act-btns .rbtn-solid { border: 0; color: #fff; background: linear-gradient(135deg, var(--ih-go-a), var(--ih-go-b)); }

/* Writing to the owner: try Rocket Assist first. */
.ih-tryfirst { display: flex; align-items: center; gap: 8px; padding: 5px 6px 5px 8px; border-radius: 999px; font-size: 12.5px; justify-self: start; width: fit-content; max-width: 100%;
  background: color-mix(in srgb, var(--c-violet) 9%, var(--surface)); border: 1.5px dashed color-mix(in srgb, var(--c-violet) 45%, var(--border)); }
.ih-tryfirst span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Ask me anything: Rocket Assist's hello, and questions to tap. */
.ih-ama { display: grid; gap: 10px; }
.ih-ex { display: flex; flex-wrap: wrap; gap: 8px; padding-left: 44px; }
.ih-ex-q { font: inherit; font-size: 13px; font-weight: 600; color: var(--text); cursor: pointer; padding: 7px 12px; border-radius: 999px;
  background: color-mix(in srgb, var(--c-violet) 9%, var(--surface)); border: 1.5px solid color-mix(in srgb, var(--c-violet) 35%, var(--border));
  transition: background .12s, transform .12s; }
.ih-ex-label { padding-left: 44px; font-size: 11px; font-weight: 750; text-transform: uppercase; letter-spacing: .4px; color: var(--text-dim); margin-bottom: -2px; }
.ih-ex-task { background: color-mix(in srgb, var(--c-green) 12%, var(--surface)); border-color: color-mix(in srgb, var(--c-green) 50%, var(--border)); }
@media (max-width: 640px) { .ih-ex-label { padding-left: 0; } }
.ih-ex-task:hover { background: color-mix(in srgb, var(--c-green) 17%, var(--surface)) !important; }
.ih-ex-q:hover { background: color-mix(in srgb, var(--c-violet) 18%, var(--surface)); transform: translateY(-1px); }
@media (max-width: 640px) { .ih-ex { padding-left: 0; } }
.ih-search { display: flex; align-items: center; gap: 6px; padding: 0 10px; border-radius: 10px; border: 1px solid var(--border); background: var(--surface); }
.ih-search input { flex: 1; min-width: 0; border: 0; background: none; font: inherit; font-size: 13.5px; padding: 7px 0; color: var(--text); outline: none; }
.ih-side-list { display: grid; gap: 2px; }
.ih-group { font-size: 11px; font-weight: 750; text-transform: uppercase; letter-spacing: .4px; color: var(--text-dim); padding: 10px 6px 4px; }
.ih-crow { position: relative; display: flex; align-items: center; gap: 9px; width: 100%; text-align: left; font: inherit; color: var(--text);
  padding: 7px 8px; border-radius: 10px; border: 0; border-left: 3px solid transparent; background: none; cursor: pointer; }
.ih-crow:hover { background: color-mix(in srgb, var(--k) 8%, var(--surface)); }
.ih-crow.on { background: color-mix(in srgb, var(--k) 14%, var(--surface)); border-left-color: var(--k); }
.ih-crow-ico { width: 28px; text-align: center; font-size: 17px; flex: 0 0 auto; }
.ih-crow-main { display: grid; gap: 3px; min-width: 0; flex: 1; }
.ih-crow-title { font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ih-crow.unread .ih-crow-title { font-weight: 800; }
.ih-crow-sub { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: 11.5px; color: var(--text-dim); }
.ih-crow-who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ih-crow .ih-dot { top: 50%; right: 8px; transform: translateY(-50%); }
.ih-pill-xs { font-size: 10.5px; padding: 0 7px; }
.ih-av-sm2 { --s: 28px; }
.ih-title { cursor: default; }
.ih-rename { border: 0; background: none; cursor: pointer; font-size: 14px; padding: 2px 4px; border-radius: 6px; opacity: .7; }
.ih-rename:hover { opacity: 1; background: var(--bg); }
.ih-title-input { flex: 1 1 200px; font: inherit; font-size: 17px; font-weight: 700; padding: 4px 8px; border-radius: 8px;
  border: 1.5px solid var(--c-amber); background: var(--surface); color: var(--text); }
/* The WhatsApp box: one rounded field, picture icons inside on the right, one
   round button beside it (mic / send / stop). */
.ih-box-row { display: flex; align-items: flex-end; gap: 8px; }
.ih-box { flex: 1; min-width: 0; display: flex; align-items: flex-end; gap: 4px; padding: 5px 6px 5px 14px;
  border-radius: 24px; background: var(--bg); border: 1px solid var(--border); transition: border-color .12s, box-shadow .12s; }
.ih-box:focus-within { border-color: color-mix(in srgb, var(--sendk, var(--c-blue)) 55%, var(--border));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--sendk, var(--c-blue)) 15%, transparent); }
.ih-box textarea { flex: 1; min-width: 0; resize: none; border: 0; outline: none; background: none; box-shadow: none;
  font: inherit; font-size: 15px; line-height: 1.4; color: var(--text); padding: 7px 0; margin: 0; max-height: 168px; }
.ih-box-icons { display: inline-flex; gap: 2px; align-items: center; padding-bottom: 1px; }
.ih-ic { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: 50%;
  border: 0; background: none; cursor: pointer; color: var(--text-dim); padding: 0; margin: 0; }
.ih-ic:hover { color: var(--text); background: color-mix(in srgb, var(--text) 8%, transparent); }
.ih-ic svg { width: 21px; height: 21px; fill: currentColor; }
.ih-round { flex: 0 0 auto; width: 46px; height: 46px; border-radius: 50%; border: 0; cursor: pointer; padding: 0;
  display: inline-flex; align-items: center; justify-content: center; color: #fff;
  background: var(--c-green); box-shadow: 0 2px 6px rgba(0,0,0,.18); transition: transform .12s, background .12s; }
.ih-round:hover:not(:disabled) { transform: scale(1.06); }
.ih-round svg { width: 22px; height: 22px; fill: currentColor; }
.ih-round[data-mode="send"] { background: var(--sendk, var(--c-blue)); }
.ih-round:disabled { background: color-mix(in srgb, var(--text) 18%, var(--surface)) !important; color: var(--surface); cursor: default; box-shadow: none; }
.ih-round:disabled:hover { transform: none; }
.ih-mic.on { color: #fff; background: var(--c-red); animation: ih-pulse 1.2s ease-in-out infinite; }
.ih-round[data-mode="send"] svg { margin-left: 3px; }
.ih-round[data-mode="stop"] { background: var(--c-red); animation: ih-pulse 1.2s ease-in-out infinite; }
.ih-round.spin { opacity: .7; }
.ih-round.spin svg { animation: ih-spin .9s linear infinite; }
@keyframes ih-spin { to { transform: rotate(360deg); } }
.ih-working { font-size: 12.5px; color: var(--text-dim); padding-left: 14px; }
.ih-working[hidden] { display: none; }
/* The modal trap again: .modal label would restyle the picture icon. */
.modal .ih label.ih-ic { display: inline-flex; margin: 0; text-transform: none; }
.ih-chip-pic { position: relative; display: block; padding: 0; border: 0; background: none; cursor: pointer; }
.ih-chip-pen { position: absolute; left: 3px; bottom: 3px; font-size: 12px; line-height: 1; padding: 2px 3px; border-radius: 6px; background: rgba(255,255,255,.85); }
.rf-page-body > .rf-today, .rf-page-body > .rf-settings, .rf-page-body > .rf-carriers-page { max-width: none; margin: 0; }
.rf-page-body .rf-panel, .rf-page-body > .rx { border: 0; border-radius: 0; box-shadow: none; background: none; }
.rf-page-body .rf-panel-head, .rf-page-body .rx-head { background: none; }
/* Settings' own heading would say "Settings" under "Settings". */
.rf-page-body > .rf-settings .rf-panel-title h2 { display: none; }
.rf-page-body > .rf-settings .rf-panel-title p { margin: 0; }
/* A setting's back link sits in the frame, not over it. */
.rf-page-body > .admin-back:first-child { margin: -4px 0 8px; }
/* The Calendar fills the window on a desk; the page's head comes out of that. */
@media (min-width: 1024px) {
  .rf-page-body > .rx { height: calc(100dvh - var(--topbar-h) - 160px); }
}
/* One width whatever the page: a scrollbar arriving must not narrow it. */
html:has(.rf-page) { scrollbar-gutter: stable; }
@media (max-width: 640px) {
  .rf-page { margin: 2px 0 20px; border-radius: 14px; }
  .rf-page-head { padding: 11px 14px 10px; }
  .rf-page-body { padding: 14px 12px 18px; }
}

/* The tab you're on wears its page's colour, as the bubble behind the icon
   every tab already has — no outline, nothing on the tabs you're not on. */
.nav-item.nav-hued.active .ni-icon { background: color-mix(in srgb, var(--pg) 22%, transparent); }
.more-row-hued .more-row-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 10px;
  background: color-mix(in srgb, var(--pg) 16%, var(--surface));
}

@media (min-width: 1024px) {
  /* The split's own gutter: how much of the window is chrome ABOVE and BELOW it
     that --topbar-h and --subbar-h don't already account for — main's own
     padding, which is 16px at phone/tablet width and 22px+ here, and the pill
     the floating button becomes at this width, which the composer's Send must
     stop short of. Measured, like --chat-chrome above it, and for the same
     reason: nothing in CSS can ask an element how far down the page it starts. */
  :root { --msg-gutter: 96px; }

  :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); }
  /* Rocket Fleet: the rail row wears its page's colour (see the page frame). */
  .nav-item.nav-hued.active { background: color-mix(in srgb, var(--pg) 16%, transparent); }
  .nav-item.nav-hued.active .ni-label { color: var(--text); }

  /* 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; }
}
