/* Shared styles for the Sammuki Enterprises fleet app.
   Design tokens: Apple/iOS/macOS HIG palette (system grays + accent blue),
   light/dark via prefers-color-scheme. Token NAMES are unchanged from the
   pre-redesign "spec sheet" palette (--amber/--line/--panel-2 etc still
   exist and mean the same structural role) even though several values now
   point at very different colours (e.g. --amber is the app's primary
   accent and is now Apple system blue, not literal amber) - every page's
   own inline style="color:var(--amber)" etc still works unmodified this
   way. Don't rename tokens without grepping every page first. */

/* ---- Cross-page navigation transitions ----
   This app has no SPA router - every sidebar click is a real full-page
   navigation to a separate .html file (see each page's own
   requireAuth()/renderHeader() boot block), so a same-page content-swap
   fade (like the Dashboard's stage-pill transition) isn't possible between
   pages the way it is within one. The native View Transitions API is the
   right tool for this instead: `navigation: auto` opts every same-origin
   navigation into an automatic cross-fade of the outgoing/incoming page,
   entirely browser-driven, with zero per-page JS. Unsupported browsers
   (older Firefox, older Safari) just navigate instantly as they already
   do today - this is additive, never a regression. Duration shortened
   from the ~0.25s browser default to roughly match the Dashboard pill
   transition's own feel. */
@view-transition {
  navigation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
  /* Confirmed working at 3s (visible in Sam's own browser, cranked up
     deliberately to make that unambiguous - see git history/session notes),
     dialed back from there rather than from the original 0.18s guess. Same
     duration as the Dashboard's stage-pill thumb slide (.stage-segmented
     .thumb in this file) so the two motion cues feel consistent. */
  animation-duration: 0.28s;
  animation-timing-function: ease;
}

:root {
  --bg:         #F2F2F7;
  --panel:      #FFFFFF;
  --panel-2:    #F2F2F7;
  --line:       #D8D8DE;
  --line-hair:  rgba(60,60,67,0.16);
  --text:       #1C1C1E;
  --text-dim:   #6E6E73;
  --amber:      #007AFF;   /* primary accent (Apple system blue) */
  --amber-dim:  #B9DBFF;
  --amber-wash: rgba(0,122,255,0.12);
  /* A more visibly-tinted faint ring than --amber-wash's own 12% - used
     where a ring specifically needs to read as "this is the same blue as
     the solid accent fill nearby", not just a generic soft shadow (e.g.
     the stage-track's current-stage dot and its previewed-stage
     counterpart - Sam flagged that at --amber-wash's opacity the ring
     around a previewed dot looked plain grey, not recognisably blue).
     color-mix() against var(--amber) directly (not a hardcoded rgba pair)
     means this auto-follows whichever --amber value is active - no
     separate dark-mode override needed, unlike most tokens here. */
  --amber-ring: color-mix(in srgb, var(--amber) 35%, transparent);
  --green:      #248A3D;
  --red:        #D70015;
  --blue:       #5856D6;   /* secondary/informational accent (system indigo) - distinct from --amber so the two meanings never look identical */
  --orange:     #C56A00;   /* caution tier (e.g. the EV/fuel reconciliation window) - distinct from --amber now that --amber IS the primary accent, or this tier would visually read as "the same thing as the accent colour" instead of "a warning" */
  --orange-wash: rgba(255,149,0,0.14);
  --purple:     #AF52DE;   /* Personal Vehicle Usage cost-recovery tier (worker-profile.html, payroll-worker.html) - its own hue rather than reusing --red's generic "deduction" meaning, so it reads at a glance as "vehicle personal use" specifically on a worker's pay card. Apple systemPurple, same "distinct so it doesn't read as the same thing" reasoning as --blue/--orange above. */
  --purple-wash: rgba(175,82,222,0.12);
  --bg-chrome:  #F5F5F9;  /* sidebar + toolbar background. The mockup's own token (rgba(246,246,249,0.78) over its #F2F2F7 page bg) actually composites to something slightly LIGHTER than the page, not darker - confirmed by sampling the live mockup directly. Two earlier attempts (#E8E8ED, #ECECF1) overcorrected into a visibly darker grey chasing "not pure white", when the real fix was just squashing the inline-style bug (see git history/session notes) that was forcing actual #FFFFFF - the intended tint was always this subtle. */
  --pill-track: #E5E5EA;  /* .stage-segmented/.mini-segmented's resting track - deliberately its OWN token, not --panel-2, after Sam flagged the pill bars barely showing up against the page. Root cause: --panel-2 (#F2F2F7) is byte-identical to --bg (#F2F2F7) in light mode, so the track had genuinely zero contrast - only the white active thumb's shadow gave the control any definition at all. #E5E5EA is Apple's systemGray5 (one step down from --bg's systemGray6), a real HIG token rather than an arbitrary guess. Reusing --panel-2 itself for the fix was deliberately avoided - it's used broadly for hover states/badges/disabled backgrounds elsewhere, and darkening it globally would have changed all of those too. */
  --fy-selector-bg: var(--pill-track);  /* Sam's direct ask: the FY selector pill read visibly lighter than the Admin/Sign-out .icon-btn circles next to it in the header. First diagnosis was wrong - a getComputedStyle check on #fy-selector itself came back identical to --pill-track and was misread as "so it's just an optical illusion, darken it further" (#D1D1D6, one notch past --pill-track), but that check was reading the WRONG element - enhancePrettySelect() (app.js) makes the real <select> opacity:0 and renders through a sibling .pretty-select-trigger button instead, which was actually still on plain .pretty-select-trigger's own var(--panel-2) (the exact zero-contrast token this file already warns about). Once .fy-selector-trigger (below) pointed the CORRECT element at this token, matching --pill-track exactly turned out to be the right answer after all - the #D1D1D6 guess was overcorrecting for a gap that wasn't really there, confirmed live once actually fixed. Own dedicated token (not applying --pill-track directly) purely so this one control's background stays independently adjustable if it ever needs to diverge again. */
  --radius:     18px;   /* matches the mockup's --radius-lg, used for every elevated card there (stat-card/chart-card/policy-card/hs-strip/card-section/etc) - .panel/.gauge/.trip-list/.infringement-box/.infringement-row are the live equivalents, all sharing this one token so bumping it here is a safe, uniform correction */
  --shadow:     0 1px 2px rgba(30,30,40,0.06), 0 8px 24px -12px rgba(30,30,40,0.14);
  --sidebar-w:  260px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:         #1C1C1E;
    --panel:      #2C2C2E;
    --panel-2:    rgba(120,120,128,0.24);
    --line:       #48484A;
    --line-hair:  rgba(84,84,88,0.6);
    --text:       #F2F2F7;
    --text-dim:   #98989F;
    --amber:      #0A84FF;
    --amber-dim:  #1E3A5C;
    --amber-wash: rgba(10,132,255,0.18);
    --green:      #32D74B;
    --red:        #FF453A;
    --blue:       #7A79E8;
    --orange:     #FF9F0A;
    --orange-wash: rgba(255,159,10,0.18);
    --purple:     #BF5AF2;
    --purple-wash: rgba(191,90,242,0.18);
    --bg-chrome:  #202022;  /* mirrors the light-mode correction - a subtle lift off --bg (#1C1C1E), not the heavier jump tried earlier. The mockup's own dark-mode token happened to mathematically collapse to zero distinction (same RGB as its bg, just lower alpha - blending a colour with itself changes nothing), so there's no real reference value to match here; this follows the same restrained proportion as the corrected light-mode value instead. */
    --pill-track: rgba(120,120,128,0.32);  /* dark mode's --panel-2 (rgba(120,120,128,0.24)) already differed from --bg, unlike light mode's exact collision, so this isn't fixing a zero-contrast bug here - just bumped the opacity a bit further (0.24→0.32) for the same "pill bars should read as clearly recessed" standard as the light-mode fix, and given its own token for the same reuse-avoidance reason. */
    --fy-selector-bg: var(--pill-track);  /* see the light-mode comment above - matching --pill-track exactly is correct, not one notch darker. */
    --shadow:     0 1px 2px rgba(0,0,0,0.3), 0 8px 24px -12px rgba(0,0,0,0.5);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.mono {
  font-family: ui-monospace, "SF Mono", "Menlo", "Cascadia Code", Consolas, monospace;
  font-variant-numeric: tabular-nums;
}

/* ---- Sidebar (was: top header bar) ----
   #app-header is the one shared DOM slot every page already includes
   (renderHeader() in app.js fills it) - repurposed here as a fixed-left
   navigation column instead of a full-width top bar, so no page's HTML
   needed to change. Removed from normal flow via position:fixed; `body`
   reserves the width via padding-left below. Collapses to the existing
   hamburger+drawer pattern under the phone breakpoint (unchanged
   mechanism, just restyled - see the phone media query at the bottom). */
#app-header {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: var(--sidebar-w);
  background: var(--bg-chrome);
  backdrop-filter: blur(24px) saturate(1.6);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  border-right: 1px solid var(--line-hair);
  display: flex;
  flex-direction: column;
  z-index: 30;
}

.sidebar-brand {
  padding: 20px 20px 14px;
  flex-shrink: 0;
}
.sidebar-brand .name {
  font-size: 15.5px;
  font-weight: 750;
  letter-spacing: -0.01em;
}

/* Real filter, not a decorative box - typing narrows the visible nav links
   to ones whose label matches (see filterSidebarNav() in app.js), and
   auto-expands any collapsed group that still has a match so it's never
   hiding a result. */
.sidebar-search {
  display: flex; align-items: center; gap: 7px;
  /* Not var(--panel-2) - that's a flat #F2F2F7 in light mode, which is
     lighter than the sidebar's own --bg-chrome background, so the field
     read as raised instead of recessed. A black/white overlay always
     darkens (light mode) or lightens (dark mode) relative to whatever's
     actually underneath it, so it stays "inset" regardless of the
     sidebar's exact tone. */
  background: rgba(0,0,0,0.05);
  border-radius: 9px;
  padding: 7px 10px;
  margin: 2px 12px 12px;
  color: var(--text-dim);
}
@media (prefers-color-scheme: dark) {
  .sidebar-search { background: rgba(255,255,255,0.08); }
}
.sidebar-search svg { flex-shrink: 0; }
.sidebar-search input {
  border: none; background: none; outline: none;
  font-size: 13px; font-family: inherit; color: var(--text);
  width: 100%;
}
.sidebar-search input::placeholder { color: var(--text-dim); }

.sidebar-section-label {
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 12px 10px 2px;
}
.sidebar-section-label:first-child { padding-top: 2px; }

.sidebar-scroll {
  flex: 1;
  overflow-y: auto;
  padding: 4px 12px 12px;
}

/* The clickable group header itself carries all the caption styling
   (rather than a wrapping label + an inheriting button) since browsers
   reset a <button>'s text styling by default - relying on inheritance
   into a nested button silently drops text-transform/letter-spacing. */
.sidebar-group-toggle {
  display: flex; align-items: center; width: 100%;
  background: none; border: none; cursor: pointer;
  font-family: inherit; padding: 14px 10px 4px; margin: 0;
  font-size: 11px; font-weight: 650; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--text-dim);
}
.sidebar-group-toggle:first-of-type { padding-top: 2px; }
.sidebar-group-toggle .nav-caret {
  margin-left: auto; font-size: 14px; color: var(--text-dim);
  transition: transform 0.15s ease;
}
.sidebar-group-toggle[aria-expanded="false"] .nav-caret { transform: rotate(-90deg); }
.sidebar-group-items[data-collapsed="true"] { display: none; }

/* Only present when that group's own NAV_PAGES node sets an `icon` (Expenses
   Log/Sammuki Enterprises) - icon-less groups (Admin) are entirely
   unaffected, since there's no svg element for this rule to match. */
.sidebar-group-toggle svg { flex-shrink: 0; width: 15px; height: 15px; color: var(--text-dim); margin-right: 8px; }

/* A group nested inside another group reads as a clickable row at the same
   visual weight as its sibling links, not as another shouty small-caps
   section caption - indentation (set inline per node, since depth is
   dynamic) is what shows the hierarchy instead. Equal selector specificity
   to the base rule above, so source order (this rule coming later) is what
   makes it win. Currently unused - every group is depth-0 as of the
   Financial Hub flattening (see app.js) - but kept for whenever a group
   next ends up nested inside another one. */
.sidebar-group-toggle.nested {
  padding-top: 8px; padding-bottom: 8px;
  font-size: 13.5px; font-weight: 500; letter-spacing: normal;
  text-transform: none; color: var(--text);
  border-radius: 8px;
}
.sidebar-group-toggle.nested:hover { background: var(--panel-2); }

.mobile-nav-subheading {
  padding: 10px 16px 4px 32px;
  font-size: 11px; font-weight: 650; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--text-dim);
}

.sidebar-link {
  display: block;
  padding: 8px 10px;
  border-radius: 8px;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text);
  text-decoration: none;
  margin-bottom: 2px;
}
.sidebar-link:hover { background: var(--panel-2); }
.sidebar-link.active { background: var(--amber); color: #fff; }
.search-hidden { display: none !important; }

.sidebar-link-icon {
  display: flex; align-items: center; gap: 11px;
  padding-top: 9px; padding-bottom: 9px;
}
.sidebar-link-icon svg { flex-shrink: 0; color: var(--text-dim); width: 17px; height: 17px; }
.sidebar-link-icon.active svg { color: #fff; }

.sidebar-badge {
  margin-left: auto;
  font-size: 11px; font-weight: 700;
  background: var(--red); color: #fff;
  border-radius: 999px; padding: 1px 6px;
  min-width: 16px; text-align: center;
}
.sidebar-link.active .sidebar-badge { background: rgba(255,255,255,0.9); color: var(--amber); }

/* filterSidebarNav()'s deep-link hint (app.js) - e.g. "→ Tolls" appended
   to the "Expenses Log" link when a search matched one of its pill tabs
   rather than the page's own label, so it's clear why that result
   surfaced and which tab the link will actually land on. Same
   margin-left:auto right-alignment as .sidebar-badge above, within the
   same flex row. */
.sidebar-search-hint {
  margin-left: auto;
  font-size: 11px; font-weight: 600; color: var(--text-dim);
  white-space: nowrap;
}
.sidebar-link.active .sidebar-search-hint { color: rgba(255,255,255,0.85); }

.sidebar-footer {
  flex-shrink: 0;
  border-top: 1px solid var(--line-hair);
  padding: 12px 16px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sidebar-footer .clock { font-size: 11.5px; color: var(--text-dim); }
.sidebar-footer .last-import { font-size: 10.5px; color: var(--text-dim); }

/* ---- Per-page toolbar (was: horizontal tab bar) ----
   #app-nav is the other shared DOM slot every page already includes,
   sitting right after #app-header in every page's markup - once the
   header becomes a fixed sidebar removed from flow, this div naturally
   becomes the first in-flow element, i.e. a bar across the top of the
   remaining (right-hand) content column. Repurposed as a sticky per-page
   title bar (title auto-derived from NAV_PAGES via currentNavLabel(), no
   page needed a title of its own) + the FY selector. */
#app-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg-chrome);
  backdrop-filter: blur(20px) saturate(1.6);
  -webkit-backdrop-filter: blur(20px) saturate(1.6);
  border-bottom: 1px solid var(--line-hair);
  padding: 18px 32px 14px !important;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.toolbar-title {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.015em;
}

.toolbar-right {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}

.btn-ghost {
  background: var(--pill-track);
  border: none;
  color: var(--text);
  padding: 7px 14px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}
.btn-ghost:hover { background: var(--line-hair); }

/* A row of .btn-ghost-style filter/source toggle buttons (Review's EV
   Charging/Tolls/Odometer source picker and Pending/Rejected tabs, Admin/
   Expenses Log/Sammuki Enterprises's Open/Done/All-style tabs, Trips'
   Needs Review/Acknowledged billing-discrepancy tabs) - promoted here once
   a fourth page needed the identical class, after noticing three separate
   copies of this exact rule (review.html/admin.html/expenses-log.html)
   were all setting `background: var(--panel-2)`, which is byte-identical
   to `--bg` in light mode (see the --pill-track note above) - so the
   "active" tab was rendering with NO visible distinction from the
   inactive ones next to it, only a `border-color` that never actually
   showed since .btn-ghost has `border: none` (a color with no border
   style/width to apply to is a no-op). trips.html referenced this same
   class in its own JS (`classList.toggle('tab-active', ...)`) with no
   style rule for it AT ALL - its active tab has never had any visual
   indicator. Real fix, not just restoring the old (also-broken) look:
   `--panel` (white) + `--shadow`, the same "elevated pill" language
   already used for `.stage-tile.active`/`.mini-segmented .thumb`, so a
   selected filter genuinely reads as selected. */
.tab-active { background: var(--panel) !important; color: var(--text) !important; box-shadow: var(--shadow); }

/* Small round icon-only button - Sign Out (#logout-btn, a <button>) and
   Admin (a plain <a href="/admin.html">, renderHeader()) both use this,
   rarely used enough that neither needs a full text pill taking up
   toolbar space at any width. `text-decoration:none` only matters for the
   anchor case - buttons ignore it. */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  background: var(--pill-track); border: none; border-radius: 50%;
  color: var(--text-dim); cursor: pointer; text-decoration: none;
}
.icon-btn:hover { background: var(--line-hair); color: var(--text); }
.icon-btn.active { background: var(--amber); color: #fff; }

/* Small pill action button - an equal-weight tappable option (e.g. a
   labour-task variant choice on Trip Workflow, a policy-card action on
   Insurance) rather than a full form-style button. */
.chip-btn {
  border: 1px solid var(--line); background: var(--panel); color: var(--text);
  font-size: 12px; font-weight: 600; padding: 6px 12px; border-radius: 999px;
  cursor: pointer; font-family: inherit;
}
.chip-btn.accent { border-color: var(--amber); color: var(--amber); background: var(--amber-wash); }
.chip-btn.ghost { color: var(--text-dim); }

/* ---- Shared card patterns ----
   Promoted here once a THIRD page (Infringements) needed the exact same
   components Trip Workflow and Insurance had each defined locally -
   .card-head/.kv-row/.task-row/.avatar-bubble (from trip-workflow.html)
   and .mini-segmented/.dropzone/.plate-chip (from insurance.html). Each
   page's own local <style> block keeps only what's genuinely specific to
   it now (e.g. Trip Workflow's .location-card, Insurance's .policy-card). */

.plate-chip {
  font-family: ui-monospace, "SF Mono", "Menlo", "Cascadia Code", Consolas, monospace;
  font-size: 11.5px; font-weight: 700; background: var(--panel-2); border-radius: 7px;
  padding: 4px 8px; letter-spacing: 0.01em;
}

/* Card-internal heading row, with room for a right-aligned action - a
   card-section's title (e.g. "Guest Details", "ODO") or, on a list of
   cards (Infringements), the record's own identifying info + status +
   actions. */
.card-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px 4px; gap: 10px; }
.card-head h3 { font-size: 12.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-dim); margin: 0; }

/* Plain label/value row, full-width divided (edge-to-edge dividers reach
   the card's own rounded corners since the card itself carries zero
   padding - see .card-section below). */
.kv-row { display: flex; align-items: center; padding: 11px 18px; border-top: 1px solid var(--line-hair); gap: 16px; }
.kv-row:first-of-type { border-top: none; }
.kv-row .kv-label { flex: 0 0 170px; font-size: 13.5px; color: var(--text-dim); }
.kv-row .kv-value { font-size: 13.5px; font-weight: 550; }

.task-row { display: flex; align-items: center; gap: 12px; padding: 13px 18px; border-top: 1px solid var(--line-hair); }
.task-row:first-of-type { border-top: none; }

/* A small round badge in front of a task/step row - a worker's initial on
   a green fill once done/assigned (Trip Workflow), a step number or
   checkmark (Infringements); neutral grey while still pending. */
.avatar-bubble {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: #fff;
}
.avatar-bubble.unassigned { background: var(--panel-2); color: var(--text-dim); }

/* A card that supplies zero padding of its own, relying on .card-head/
   .kv-row/.task-row above to each carry their own horizontal padding so
   divider lines run edge-to-edge to the rounded corners. */
.card-section { background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }

/* ---- Multi-stage flow card components ----
   Promoted here once a THIRD page (Infringements) needed the exact same
   stage-tracker/history-timeline/advance-panel components Damage claims
   and the Maintenance Hub's Servicing/Tyres/Repairs job cards had each
   defined identically in their own local <style> blocks. */
.stage-track { display: flex; align-items: flex-start; gap: 0; padding: 16px 18px 14px; overflow-x: auto; }
.stage-step { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 1; min-width: 78px; position: relative; }
.stage-step:not(:last-child)::after {
  content: ''; position: absolute; top: 13px; left: 54%; width: 92%; height: 2px;
  background: var(--line); z-index: 0;
}
.stage-step.done:not(:last-child)::after { background: var(--green); }
.stage-dot {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0; position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; color: #fff; background: var(--panel-2);
}
.stage-step.done .stage-dot { background: var(--green); }
.stage-step.current .stage-dot { background: var(--amber); box-shadow: 0 0 0 4px var(--amber-ring); }
.stage-step.future .stage-dot { color: var(--text-dim); }
/* The dot being previewed (trip-workflow.html's ‹/› browse control) gets
   the exact same ring as .stage-step.current above - one shared rule, not
   two copies of the same box-shadow value, so the "this faint ring is the
   same blue as the trip's real current stage's own solid fill" (Sam's
   explicit ask) can't silently drift out of sync between the two. */
.stage-dot.previewing { box-shadow: 0 0 0 4px var(--amber-ring); }
.stage-name { font-size: 10.5px; font-weight: 650; color: var(--text-dim); text-align: center; }
.stage-step.done .stage-name { color: var(--text-dim); }
.stage-step.current .stage-name { color: var(--text); font-weight: 750; }
.supplement-pill {
  margin-top: 2px; font-size: 9.5px; font-weight: 650; color: var(--blue);
  background: rgba(88,86,214,0.12); padding: 2px 7px; border-radius: 999px; white-space: nowrap;
}

.advance-panel { margin: 0 18px 18px; padding: 14px 16px; background: var(--panel-2); border-radius: 12px; }
.advance-panel-head { font-size: 12px; font-weight: 700; color: var(--amber); margin-bottom: 10px; }
/* The shared .field input/select is var(--panel-2) - same colour as this
   panel's own background, so without this override every input here
   renders with no visible box at all. .advance-panel .field input is
   needed ON TOP OF the plain .advance-panel input rule below - .field
   input (line ~967) has the exact same specificity (0,1,1) as .advance-
   panel input and comes later in this file, so it silently won the
   cascade and the override never actually applied to any .field-wrapped
   input (only a bare, non-.field input/select was ever actually fixed).
   Confirmed live on Trip Workflow's Guest Details/Start ODO/End ODO
   forms, all .field-wrapped and sitting inside .advance-panel - their
   text inputs were genuinely invisible, not just low-contrast. textarea
   has the same collision (Trip Workflow's Delivery/Collection "why is
   this different than as-booked" note box, also var(--panel-2)) - caught
   the same way, live, not by reading the CSS. Same again for
   .pretty-select-trigger (the custom dropdown's own visible button,
   see app.js) - its base rule is also var(--panel-2), so a select
   rendered inside an .advance-panel blended straight into it.
   .advance-panel input needs the SAME :not([type=...]) chain as the
   general bare-input rule below (not just plain `input`) - each :not()
   argument counts toward specificity, so the general rule's three :not()s
   ((0,3,1) total) actually out-specifies a plain `.advance-panel input`
   ((0,1,1)) regardless of source order, silently winning the cascade and
   re-breaking Start ODO/End ODO's inputs the moment the general rule was
   added - caught live, not by reading the CSS, same as every other
   instance of this exact trap this session. */
.advance-panel input:not([type=checkbox]):not([type=radio]):not([type=file]),
.advance-panel select,
.advance-panel .field input,
.advance-panel textarea,
.advance-panel .pretty-select-trigger {
  background: var(--panel);
}

.timeline { padding: 4px 18px 6px; }
.timeline-item { display: flex; gap: 12px; padding: 9px 0; border-top: 1px solid var(--line-hair); }
.timeline-item:first-child { border-top: none; }
.timeline-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); margin-top: 5px; flex-shrink: 0; }
.timeline-dot.supplement { background: var(--blue); }
.timeline-body { flex: 1; min-width: 0; }
.timeline-when { font-size: 10.5px; color: var(--text-dim); font-weight: 650; text-transform: uppercase; letter-spacing: 0.02em; }
.timeline-what { font-size: 13px; font-weight: 600; margin-top: 1px; }
.timeline-detail { font-size: 12px; color: var(--text-dim); margin-top: 2px; }

/* Compact toolbar filter (e.g. Insurance's Active/All, Infringements'
   All/Open/Closed) - same sliding-thumb mechanic as the Dashboard's stage
   segmented control, scaled down for a toolbar-sized toggle. */
.mini-segmented {
  position: relative; display: flex;
  background: var(--pill-track); border-radius: 999px; padding: 3px;
  box-shadow: var(--shadow);
}
.mini-segmented .thumb {
  position: absolute; top: 3px; bottom: 3px; left: 3px;
  width: var(--w, 0px); transform: translateX(var(--x, 0px));
  background: var(--panel); border-radius: 999px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.06);
  transition: transform 0.28s cubic-bezier(.4,0,.2,1), width 0.28s cubic-bezier(.4,0,.2,1);
  z-index: 0;
}
.mini-segmented button {
  position: relative; z-index: 1;
  border: none; background: none; cursor: pointer;
  padding: 6px 14px; font-size: 12.5px; font-weight: 600; color: var(--text-dim);
  border-radius: 999px; font-family: inherit;
}
.mini-segmented button.active { color: var(--text); }

/* Upload dropzone (drag/drop file target) - .dragover is toggled by JS on
   dragover/dragleave/drop for real visual feedback, which the old plain
   dashed-box upload-zone pattern never had. */
.dropzone {
  border: 2px dashed var(--line); border-radius: var(--radius);
  padding: 28px 20px; text-align: center; color: var(--text-dim);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  background: var(--panel-2); cursor: pointer;
}
.dropzone.dragover { background: var(--amber-wash); border-color: var(--amber); }
.dropzone-icon {
  width: 44px; height: 44px; border-radius: 50%; background: var(--amber-wash); color: var(--amber);
  display: flex; align-items: center; justify-content: center;
}
.dropzone .dz-title { font-size: 13.5px; font-weight: 600; color: var(--text); }
.dropzone .dz-sub { font-size: 12px; }

/* "Boarding pass" style divided strip (e.g. Trip Workflow's Vehicle/Guest/
   Start/End/Tasks row, Loan Detail's Balance/Rate/Payment/Remaining row) -
   a flex row of key/value cells separated by hairlines. */
.header-strip {
  display: flex; flex-wrap: wrap;
  background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden;
}
.header-strip .hs-item { padding: 14px 20px; flex: 1; min-width: 130px; border-right: 1px solid var(--line-hair); }
.header-strip .hs-item:last-child { border-right: none; }
.header-strip .hs-item .k { color: var(--text-dim); font-size: 10.5px; font-weight: 650; text-transform: uppercase; letter-spacing: 0.04em; display: block; margin-bottom: 4px; }
.header-strip .hs-item .v { font-size: 14.5px; font-weight: 550; }

/* Plain text "Edit"/"Add details" actions - no button chrome, since these
   sit right next to already-quiet content and a filled pill would
   out-compete it for attention that a simple accent-coloured link doesn't. */
.edit-link { border: none; background: none; color: var(--amber); font-size: 13px; font-weight: 600; cursor: pointer; padding: 4px; font-family: inherit; text-decoration: none; }
.edit-link:hover { text-decoration: underline; }

/* Small outlined pill tag (e.g. Insurance's billing-type tag, Loans' "no
   specific vehicle" tag) - lighter-weight than .status-tag's filled
   background, for a label that isn't a status/urgency indicator. */
.badge-outline {
  font-size: 10.5px; font-weight: 650; text-transform: uppercase; letter-spacing: 0.03em;
  padding: 3px 8px; border-radius: 6px; border: 1px solid var(--line); color: var(--text-dim);
  white-space: nowrap; flex-shrink: 0;
}

/* #logout-btn-mobile now lives inside #mobile-topbar (renderHeader(),
   app.js), not as its own floating body-level element any more - hiding
   #mobile-topbar itself on desktop (above) is enough, no separate rule
   needed here. */

#fy-selector, #fy-selector-mobile {
  appearance: none;
  -webkit-appearance: none;
  background: var(--fy-selector-bg) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236E6E73' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right 10px center;
  background-size: 11px;
  border: none;
  border-radius: 999px;
  padding: 7px 28px 7px 14px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
}

/* #mobile-topbar/#mobile-nav-backdrop (renderHeader(), app.js) - plain
   <div>s appended to <body>, so without this they'd render as an
   unstyled block at the very bottom of the page on desktop. Dormant here;
   the phone breakpoint below gives them their real `display` and
   positioning. */
#mobile-topbar, #mobile-nav-backdrop { display: none; }

main {
  max-width: min(1400px, 94vw);
  margin: 0 auto;
  padding: 32px 32px 60px;
}

/* For pages with wide, data-dense tables (e.g. Trips) that genuinely
   benefit from more room on large displays - still capped well short of
   edge-to-edge on an ultrawide monitor, since very long table rows are
   easier to scan than a single sprawling line. */
main.wide {
  max-width: min(1900px, 96vw);
}

body { padding-left: var(--sidebar-w); }

/* ---- Fleet totals gauge strip ---- */
.gauge-strip {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
  margin-bottom: 32px;
}

.gauge {
  background: var(--panel);
  border-radius: var(--radius);
  padding: 18px 20px;
  position: relative;
  box-shadow: var(--shadow);
  overflow: hidden;
}

.gauge::before {
  content: '';
  position: absolute;
  top: 0; left: 0;
  width: 3px;
  height: 100%;
  background: var(--amber-dim);
}

.gauge.positive::before { background: var(--green); }
.gauge.negative::before { background: var(--red); }

.gauge .label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
  margin-bottom: 6px;
}

.gauge .value {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* Optional "vs prior FY" line under a gauge's value (currently Financial
   Overview's income/expense/profit/margin gauges) - .positive/.negative
   reflect whether the change is favourable for THAT metric, not just
   whether the number went up (an expenses increase is .negative even
   though the figure itself rose). Empty/absent when there's no prior FY
   to compare against (All-time view, or the very first FY on record). */
.gauge .delta { font-size: 11.5px; font-weight: 650; margin-top: 6px; }
.gauge .delta.positive { color: var(--green); }
.gauge .delta.negative { color: var(--red); }

/* ---- Charts (Chart.js canvases) ---- */
.chart-container {
  position: relative;
  height: 260px;
  width: 100%;
}

.chart-toggle {
  display: inline-flex;
  gap: 2px;
  margin-bottom: 12px;
  background: var(--panel-2);
  border-radius: 999px;
  padding: 3px;
}

.chart-toggle button {
  background: none;
  border: none;
  border-radius: 999px;
  padding: 7px 16px;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  color: var(--text-dim);
  font-family: inherit;
}

.chart-toggle button.active {
  background: var(--panel);
  color: var(--text);
  box-shadow: 0 1px 3px rgba(0,0,0,0.14);
}

/* ---- Section headers ---- */
.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 36px 0 14px;
}

.section-head:first-of-type { margin-top: 0; }

.section-head .heading {
  display: flex;
  align-items: center;
  gap: 8px;
}

.section-head .tick {
  width: 6px; height: 6px;
  background: var(--amber);
  border-radius: 50%;
}

.section-head h2 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  color: var(--text-dim);
  margin: 0;
}

.section-head a.section-link {
  font-size: 12.5px;
  color: var(--amber);
  text-decoration: none;
  font-weight: 600;
}
.section-head a.section-link:hover { text-decoration: underline; }

/* ---- Open infringements callout (Dashboard) ----
   Deliberately its own box, not part of the trip board's tile/list
   mechanic - a compliance deadline is a different kind of urgency from
   the tiered flags in the Needs attention list below it. */
.infringement-box {
  background: rgba(255,59,48,0.1);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 24px;
}

.infringement-box-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--red);
  font-weight: 650;
  margin-bottom: 8px;
}

.infringement-row {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--panel);
  border-radius: var(--radius);
  padding: 11px 14px;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  box-shadow: var(--shadow);
}
.infringement-row + .infringement-row { margin-top: 8px; }
.infringement-row:hover { background: var(--panel-2); }

.infringement-row-plate {
  font-weight: 700;
  font-size: 11.5px;
  letter-spacing: 0.01em;
  background: var(--panel-2);
  border-radius: 7px;
  padding: 4px 8px;
  flex-shrink: 0;
}

/* ---- Needs attention priority group headers (Dashboard trip list) ---- */
.trip-group-head {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 700;
  padding: 9px 16px 7px;
}
.trip-group-head.ev-fuel { color: var(--orange); background: var(--orange-wash); }
.trip-group-head.tolls { color: var(--red); background: rgba(255,59,48,0.1); }
.trip-group-head.other { color: var(--text-dim); background: var(--panel-2); }
/* Low Margin Trip flag's own tier (lowest priority - see $groupRank in
   compute_trip_board()) - --blue, the app's existing "informational,
   distinct from red/orange" accent (already used by .status-info), since
   this is a financial observation rather than an operational task. */
.trip-group-head.margin { color: var(--blue); background: rgba(88,86,214,0.1); }
.trip-group-head.bne-parking { color: var(--amber); background: var(--amber-wash); }

/* A Low Margin trip's row gets an extra sibling Acknowledge button
   outside the row's own link - a <button> can't validly nest inside an
   <a>, and this one needs to dismiss the flag in place without
   navigating anywhere. .trip-row-wrap carries the row's usual border/
   padding-adjacent styling instead of the inner .trip-row, which becomes
   border-less and fills the wrapper via flex so the two still read as
   one visual row with no doubled divider. */
.trip-row-wrap { display: flex; align-items: stretch; border-top: 1px solid var(--line-hair); }
.trip-row-wrap:first-child { border-top: none; }
.trip-row-wrap .trip-row { flex: 1; border-top: none; }
.ack-low-margin {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; margin: auto 16px auto 0; border-radius: 50%;
  border: 1px solid var(--line); background: var(--panel); color: var(--text-dim);
  font-size: 12px; cursor: pointer; flex-shrink: 0;
}
.ack-low-margin:hover { border-color: var(--green); color: var(--green); background: rgba(52,199,89,0.1); }
.ack-low-margin:disabled { opacity: 0.5; cursor: default; }

/* ---- Trip board (Dashboard) ----
   Main area (stage columns) then Reminders/Fleet status stacked below -
   always, not just at phone width. A 1fr/300px side-by-side grid used to
   run all the way down to the phone breakpoint, but the 300px rail eating
   into the main column's width meant the segmented control got squeezed
   below a comfortable size well before the page was narrow enough to
   justify actually stacking - producing a real, jarring horizontal
   scrollbar in that middle zone rather than either a comfortable
   side-by-side layout or a clean stacked one. Always stacking removes
   that squeeze entirely: the segmented control now always gets the full
   main-column width to work with. */
.trip-board {
  display: flex;
  flex-direction: column;
  gap: 28px;
}

/* A real segmented control (sliding pill), not a 4-box tile grid - the
   thumb's position/width is measured and set by positionStageThumb() in
   index.html after every render, since each segment's width varies with
   its label/count text. */
.stage-segmented {
  position: relative;
  display: flex;
  background: var(--pill-track);
  border-radius: 999px;
  padding: 4px;
  margin-bottom: 18px;
  box-shadow: var(--shadow);
}

.stage-segmented .thumb {
  position: absolute;
  top: 4px; bottom: 4px; left: 4px;
  width: var(--w, 0px);
  transform: translateX(var(--x, 0px));
  background: var(--panel);
  border-radius: 999px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.06);
  transition: transform 0.28s cubic-bezier(.4,0,.2,1), width 0.28s cubic-bezier(.4,0,.2,1);
  z-index: 0;
}

.stage-tile {
  position: relative; z-index: 1;
  flex: 1;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: none; border: none; cursor: pointer;
  padding: 11px 12px;
  border-radius: 999px;
  font-family: inherit;
}

.stage-tile-label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-dim);
  white-space: nowrap;
}
.stage-tile.active .stage-tile-label { color: var(--text); }

/* Plain text by default - only the active segment's count gets a pill
   treatment (below), so the control doesn't read as four equally-weighted
   badges when only one is actually the current filter. */
.stage-tile-count {
  font-size: 11px;
  font-weight: 700;
  color: var(--text-dim);
}
.stage-tile.active .stage-tile-count {
  padding: 1px 7px;
  border-radius: 999px;
}
.stage-tile.active .stage-tile-count { background: var(--stage-accent); color: #fff; }

.trip-list {
  overflow: hidden;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  transition: opacity 0.16s ease, transform 0.16s ease;
}
/* Applied briefly by selectStage() (index.html) while swapping content
   between stage tiles - a quick fade+rise instead of the list content
   hard-cutting the instant a pill is clicked. */
.trip-list.stage-transitioning {
  opacity: 0;
  transform: translateY(4px);
}

.trip-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border-top: 1px solid var(--line-hair);
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
.trip-row:first-child { border-top: none; }
.trip-row:hover { background: var(--panel-2); }

.trip-row-plate {
  font-weight: 700;
  font-size: 11.5px;
  letter-spacing: 0.01em;
  background: var(--panel-2);
  border-radius: 7px;
  padding: 4px 8px;
  flex-shrink: 0;
}

.trip-row-info { flex: 1; min-width: 0; }

.trip-row-right {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.flag-tag {
  font-size: 11px;
  font-weight: 600;
  color: var(--red);
  background: rgba(255,59,48,0.12);
  padding: 3px 8px;
  border-radius: 6px;
  white-space: nowrap;
}

/* Trip Notes flag (trips.html ledger + Dashboard needs-attention) - blue,
   not red: an unresolved note is a reminder, not a problem, so it gets its
   own informational colour rather than borrowing the base .flag-tag's
   "something's wrong" red. Shared here (not page-local) since both pages
   need it, unlike .flag-tag-orange (trips.html-only, no Dashboard use). */
.flag-tag-blue { color: var(--blue); background: rgba(88,86,214,0.12); }

/* Dashboard trip-row flags: a 2+-flag trip renders BOTH the individual
   tags and a collapsed "Multiple issues (N)" tag, and CSS shows only one
   depending on width - individual tags on a normal-width row (plenty of
   room), collapsed to one tag once the row's too narrow for several to
   sit on one line without wrapping into an ugly multi-row stack (see the
   dedicated breakpoint below). A trip with 0 or 1 flags never gets a
   .flags-collapsed alternate at all - nothing to collapse. */
.flags-collapsed { display: none; }

/* This used to just be part of the phone breakpoint (720px), but Sam hit
   real clipping on an 11" iPad - several individual flag tags on one
   trip-row genuinely don't fit even at that width, and 720px is far
   narrower than any iPad, tablet or phone. Given its own breakpoint here,
   deliberately wider than the 1024px tablet one too (which keeps the
   sidebar/main layout otherwise unchanged) - 1400px comfortably covers
   every standard iPad in both orientations, including the 12.9" Pro's
   1366px landscape width, not just an 11" model. */
@media (max-width: 1400px) {
  .flags-individual { display: none; }
  .flags-collapsed { display: inline-block; }
}

.trip-rail .panel { margin-bottom: 24px; }
.trip-rail .panel:last-child { margin-bottom: 0; }

/* Standalone status dot for the Fleet status rail - bare .status-dot has
   no size/shape of its own, needs a real rule to render as anything. */
.fleet-status-list .status-dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
}

.tag {
  display: inline-block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--amber-wash);
  color: var(--amber);
}

/* Shared 3-colour status badge - two sets of modifier names on the same
   base: .status-ok/soon/overdue (renewal-type urgency, via renewalBadge()
   in app.js) and .status-approved/pending/rejected (review-queue status). */
.status-tag { font-size: 10px; font-weight: 650; text-transform: uppercase; letter-spacing: 0.04em; padding: 3px 8px; border-radius: 999px; }
.status-ok, .status-approved { background: rgba(52,199,89,0.14); color: var(--green); }
.status-soon, .status-pending { background: var(--orange-wash); color: var(--orange); }
.status-overdue, .status-rejected { background: rgba(255,59,48,0.12); color: var(--red); }
.status-neutral { background: var(--panel-2); color: var(--text-dim); }
.status-info { background: rgba(88,86,214,0.12); color: var(--blue); }

/* ---- Panels + forms ---- */
.panel {
  background: var(--panel);
  border-radius: var(--radius);
  padding: 22px 24px;
  box-shadow: var(--shadow);
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}

.field label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin-bottom: 6px;
}

/* Every text-ish <input> app-wide, not just ones inside a .field wrapper -
   mirrors the same reasoning already established for the bare `select {}`
   rule below: several pages build a compact inline input outside the
   .field/form-grid pattern (e.g. Trip Workflow's expense-entry row -
   Total ($), Litres, Location - previously rendered with zero styling at
   all, plain browser-default borders, next to an already-styled select in
   the same row). No width:100% here for the same inline-compactness
   reason .field opts into it separately below; checkbox/radio/file/date
   keep their own native appearance (a styled checkbox/radio needs a
   different treatment entirely, and file/date pickers have their own
   OS-native chrome no background/border override touches anyway). */
input:not([type=checkbox]):not([type=radio]):not([type=file]) {
  background: var(--panel-2);
  border: none;
  color: var(--text);
  padding: 10px 11px;
  border-radius: 9px;
  font-size: 13px;
  font-family: inherit;
  box-sizing: border-box;
}
.field input {
  width: 100%;
}

/* Every <select> app-wide, not just ones inside a .field wrapper - several
   pages (trip-workflow.html's worker/location pickers, loans.html's forms)
   use a bare <select> outside that pattern, and a native select keeps its
   OS dropdown chrome (arrow box, extra padding/height baked into the
   widget) even when given the same background/padding/radius as a sibling
   <input> - appearance:none strips that, with a custom chevron standing in
   for the native arrow (same approach already used for #fy-selector). */
select {
  /* No width:100% here - unlike .field's stacked layout where full-width
     makes sense, plenty of selects app-wide are meant to stay compact and
     inline (e.g. a worker picker sitting next to a row of text), and
     forcing them to stretch would be a new bug, not a fix. .field select
     opts into full width separately, below. */
  background: var(--panel-2);
  border: none;
  color: var(--text);
  padding: 10px 11px;
  border-radius: 9px;
  font-size: 13px;
  font-family: inherit;
  box-sizing: border-box;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236E6E73' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 12px;
  padding-right: 32px;
}
.field select { width: 100%; }
select:focus { outline: 2px solid var(--amber); outline-offset: 1px; }

/* App-wide custom dropdown (Sam's explicit ask, see enhancePrettySelect()
   in app.js) - the <select> itself already looks right via the rule
   above, but its OPEN option list is rendered natively by the browser and
   can't be restyled with CSS in any browser. app.js wraps every <select>
   in a .pretty-select-wrap and keeps it in the DOM (invisible, absolutely
   positioned, pointer-events:none) purely as the value/name source of
   truth - .pretty-select-trigger is the visible control, styled to look
   identical to a plain select above (same background/padding/radius/
   chevron), and .pretty-select-menu is the custom-styled replacement for
   the native option popup. */
.pretty-select-wrap { position: relative; }
.pretty-select-wrap select { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; pointer-events: none; }
.pretty-select-trigger {
  width: 100%;
  /* A <button> and a sibling <input>/<select> with byte-identical padding
     (10px top/bottom) still render 2px apart in height - browsers don't
     resolve line-height: normal to the same pixel value for a <button> as
     for a text-entry control, even at the same font-size. Padding alone
     can't fix that, so this pins the trigger to the exact height a plain
     input/select actually computes to (10px padding + border:none +
     18px line content = 38px, confirmed via getComputedStyle - both use
     the same shared padding/font-size/border rule app-wide, so this one
     fixed number is safe everywhere, not just on the page that surfaced
     it - see Business Use Log's Vehicle field, 2026-07-19). */
  height: 38px;
  background: var(--panel-2);
  border: none;
  color: var(--text);
  padding: 10px 32px 10px 11px;
  border-radius: 9px;
  font-size: 13px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236E6E73' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 12px;
}
.pretty-select-trigger:focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }
.pretty-select-trigger:disabled { opacity: 0.6; cursor: not-allowed; }
/* FY selector only - see enhancePrettySelect()'s id check in app.js. The
   real visible control behind #fy-selector/#fy-selector-mobile (both
   opacity:0, wrapped like every other <select> in the app) is this
   trigger button, which otherwise inherits .pretty-select-trigger's
   var(--panel-2) background - the same low-contrast tone flagged
   elsewhere in this file's --pill-track comment, just never fixed here
   since nothing else needed a darker FY pill until Sam asked for it to
   match the Admin/Sign-out .icon-btn circles beside it. */
.fy-selector-trigger { background: var(--fy-selector-bg); }
/* position: fixed (not absolute) - top/left/width are set inline by
   enhancePrettySelect()'s positionMenu(), computed from the trigger's own
   getBoundingClientRect(). A relative/absolute menu anchored to
   .pretty-select-wrap would get clipped the moment the select sits inside
   any ancestor with overflow:hidden - e.g. .card-section, used everywhere
   in this app purely to round its own corners - cutting the open menu off
   at that ancestor's edge instead of floating above the rest of the page.
   Fixed positioning escapes that entirely, since none of this app's
   containers set transform/filter/contain (the only things that would
   make a fixed descendant clip to an ancestor instead of the viewport).
   overscroll-behavior: contain stops scroll CHAINING - without it,
   scrolling this list one tick past its own top/bottom passes the
   remaining scroll delta up to the page, firing a real window 'scroll'
   event (target = document, not this menu) that the JS's outside-scroll
   listener correctly treats as "the page moved, close the menu" and
   closes it - which felt like the menu "flicking away" the moment you
   reached either end of a short list. */
.pretty-select-menu {
  display: none;
  position: fixed;
  background: var(--panel);
  border-radius: 12px;
  box-shadow: var(--shadow);
  max-height: 280px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 6px;
  z-index: 500;
}
.pretty-select-menu.open { display: block; }
.pretty-select-optgroup { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-dim); padding: 8px 10px 4px; }
.pretty-select-option { padding: 8px 10px; border-radius: 8px; font-size: 13px; cursor: pointer; }
.pretty-select-option:hover { background: var(--panel-2); }
.pretty-select-option.selected { background: var(--amber-wash); color: var(--amber); font-weight: 600; }
.pretty-select-option.disabled { color: var(--text-dim); cursor: default; opacity: 0.5; }

/* Delivery/Collection's parking prompt - raised the moment a leg resolves
   to a location flagged locations.requires_parking (see migration 066).
   .parking-prompt is the still-undecided amber state; the plain
   .parking-box (once logged or explicitly skipped) matches the neutral
   --panel-2 "resolved" look used elsewhere on this page. */
.parking-box { margin-top: 10px; padding: 10px 12px; border-radius: 9px; font-size: 12.5px; }
.parking-box:not(.parking-prompt) { background: var(--panel-2); color: var(--text); display: flex; align-items: center; gap: 8px; }
.parking-box.parking-prompt { background: var(--orange-wash); color: var(--orange); }
.parking-box.parking-prompt .parking-prompt-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.parking-box.parking-prompt input[type=number] { width: 100px; color: var(--text); }

/* Delivery's "book parking in advance" reminder - deliberately distinct
   from .parking-box above, which logs what parking actually COST after
   the fact. Blue (reminder), not orange (.parking-box's "log a cost"
   colour) or red ("something's wrong") - matches Trip Notes' own blue
   flag-tag semantic: this is a heads-up to action, not a problem. */
.bne-parking-box { margin-top: 10px; padding: 10px 12px; border-radius: 9px; font-size: 12.5px; background: var(--amber-wash); color: var(--amber); }
.bne-parking-box .bne-parking-window { margin-top: 4px; color: var(--text-dim); font-family: ui-monospace, "SF Mono", "Menlo", "Cascadia Code", Consolas, monospace; font-variant-numeric: tabular-nums; }
.bne-parking-box .bne-parking-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.bne-parking-box.bne-parking-note { background: var(--panel-2); color: var(--text); }
.bne-parking-box .bne-parking-ack-form { align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.bne-parking-box .bne-parking-ack-form input[type=number] { width: 140px; color: var(--text); }

input:not([type=checkbox]):not([type=radio]):not([type=file]):focus {
  outline: 2px solid var(--amber);
  outline-offset: 1px;
}

input:not([type=checkbox]):not([type=radio]):not([type=file]):disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

button.submit {
  margin-top: 18px;
  background: var(--amber);
  color: #fff;
  border: none;
  padding: 10px 22px;
  border-radius: 999px;
  font-weight: 650;
  font-size: 13px;
  cursor: pointer;
  font-family: inherit;
}
button.submit:hover { opacity: 0.88; }
button.submit:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

button.btn-secondary {
  margin-top: 18px;
  margin-left: 10px;
  background: var(--panel-2);
  border: none;
  color: var(--text-dim);
  padding: 10px 18px;
  border-radius: 999px;
  font-size: 13px;
  cursor: pointer;
  font-family: inherit;
}

/* ---- Approve/reject review-queue actions ----
   Used across review.html, trips.html, trip-workflow.html, loans.html,
   payroll-worker.html, bas-gst.html - but was only ever actually DEFINED
   in review.html's own local <style> block, so every other page's
   Approve/Reject/Confirm button has been rendering as a bare unstyled
   browser default this whole time. Promoted here so all six share one
   real definition instead of five of them having none. */
button.btn-approve {
  background: var(--green); color: #fff; border: none;
  padding: 8px 18px; border-radius: 999px;
  font-size: 13px; font-weight: 650; cursor: pointer; font-family: inherit;
}
button.btn-approve:hover { opacity: 0.88; }
button.btn-reject {
  background: rgba(255,59,48,0.12); color: var(--red); border: none;
  padding: 8px 18px; border-radius: 999px;
  font-size: 13px; font-weight: 650; cursor: pointer; font-family: inherit;
}
button.btn-reject:hover { background: rgba(255,59,48,0.2); }

#form-status, .form-status {
  margin-top: 12px;
  font-size: 13px;
}
.form-status.ok, #form-status.ok { color: var(--green); }
.form-status.err, #form-status.err { color: var(--red); }

/* ---- Tables ---- */
table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
th {
  text-align: left;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  padding: 9px 10px;
  border-bottom: 1px solid var(--line-hair);
}
td {
  padding: 10px 10px;
  border-bottom: 1px solid var(--line-hair);
}
tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: var(--panel-2); }

/* A financial statement table's numeric column needs to line up on the
   right (digits/decimal points in a scannable vertical line), not the
   plain left-align every other table column uses - see
   renderStatementTables() in app.js, shared by Financial Overview's
   Income Statement and Cash Flow's Cash Flow Statement. */
.statement-table th:nth-child(2),
.statement-table td:nth-child(2) {
  text-align: right;
}

/* "Send from phone" QR hand-off modal (wirePhoneUpload(), app.js) - a
   generic centered overlay, not tied to any one page since it's triggered
   from five different upload points across two files. */
.phone-upload-overlay {
  position: fixed; inset: 0; z-index: 500;
  background: rgba(0,0,0,0.4);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
.phone-upload-modal {
  background: var(--panel); border-radius: 16px; padding: 24px;
  width: 100%; max-width: 320px; text-align: center;
  box-shadow: var(--shadow);
}
.phone-upload-modal img { max-width: 100%; height: auto; }

/* Tablet (iPad and similar) - forms go to 2 columns instead of 3,
   gauges go to 3 instead of 5, sidebar stays fixed/visible down to the
   phone breakpoint below. */
@media (max-width: 1024px) {
  .form-grid { grid-template-columns: repeat(2, 1fr); }
  .gauge-strip { grid-template-columns: repeat(3, 1fr); }
  main { padding: 22px 24px 50px; }
}

/* Phone - a persistent top bar (hamburger, brand, FY selector, Admin,
   Sign out - Sam's explicit ask, all in one row) plus an off-canvas
   sidebar that slides in from the left when the hamburger is tapped.
   Deliberately reuses the EXACT desktop sidebar markup/styling for the
   drawer (#app-header, unchanged - same renderSidebarNav() output, same
   icons, same section captions, same group toggles) rather than a
   separate simplified mobile structure, per Sam's explicit "menu should
   look the same on mobile as desktop" ask - supersedes the old two-row
   "☰ <page title>" toggle + separate #app-nav toolbar strip, and the old
   dropdown-style .mobile-nav-drawer/.mobile-nav-section accordion this
   replaced entirely (both deleted, not just hidden). */
@media (max-width: 720px) {
  body { padding-left: 0; padding-top: 52px; }

  /* #app-header becomes the off-canvas drawer itself - same element as
     the desktop sidebar, just fixed + translated off-screen by default
     and slid in via .open. Starts below the persistent topbar (top:52px,
     not 0) so the topbar stays visible/usable even while the drawer's
     open, rather than being covered by it. */
  #app-header {
    top: 52px;
    width: 280px;
    max-width: 85vw;
    box-shadow: 4px 0 24px rgba(0,0,0,0.18);
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    z-index: 200;
  }
  #app-header.open { transform: translateX(0); }
  /* .sidebar-scroll/.sidebar-footer/.sidebar-search no longer need a
     phone-specific display override at all - the old `display:none` rule
     here is simply gone, so they fall back to their normal (desktop)
     display values, which is exactly what "look the same as desktop"
     means for the drawer's own content. */

  /* The drawer's own brand row is hidden at phone width - the persistent
     topbar below already shows "Sammuki Enterprises", so keeping this too
     would just repeat it the moment the drawer opens. */
  .sidebar-brand { display: none; }

  /* #app-nav (desktop's per-page title + FY/Admin/Sign-out toolbar) is
     fully replaced by #mobile-topbar below at this width - Sam's explicit
     "everything in the header" ask. */
  #app-nav { display: none; }

  #mobile-topbar {
    display: flex; align-items: center; gap: 8px;
    position: fixed; top: 0; left: 0; right: 0;
    height: 52px; padding: 0 12px;
    background: var(--bg-chrome);
    backdrop-filter: blur(24px) saturate(1.6);
    -webkit-backdrop-filter: blur(24px) saturate(1.6);
    border-bottom: 1px solid var(--line-hair);
    z-index: 210;
  }
  .mobile-hamburger-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; padding: 0; flex-shrink: 0;
    background: none; border: none; border-radius: 8px;
    font-size: 18px; line-height: 1; color: var(--text); cursor: pointer;
  }
  .mobile-hamburger-btn:active { background: var(--panel-2); }
  .mobile-topbar-brand {
    font-size: 14.5px; font-weight: 750; letter-spacing: -0.01em;
    flex: 1; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  #mobile-topbar #fy-selector-mobile {
    flex-shrink: 0; padding: 6px 24px 6px 12px; font-size: 12px;
  }
  #mobile-topbar .icon-btn { flex-shrink: 0; width: 28px; height: 28px; }
  #mobile-topbar .icon-btn svg { width: 14px; height: 14px; }

  #mobile-nav-backdrop {
    display: block;
    position: fixed; top: 52px; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,0.35);
    z-index: 190;
    opacity: 0; pointer-events: none;
    transition: opacity 0.22s ease;
  }
  #mobile-nav-backdrop.open { opacity: 1; pointer-events: auto; }

  .gauge-strip { grid-template-columns: repeat(2, 1fr); }
  .form-grid { grid-template-columns: 1fr; }
  main { padding: 16px 16px 40px; }
  /* Four segments' labels genuinely don't fit ~360px - rather than
     compressing text until it's unreadable, let the control scroll
     horizontally (flex:0 0 auto stops items being squashed) like an
     overflowing iOS segmented control does. */
  .stage-segmented { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .stage-tile { flex: 0 0 auto; padding: 9px 12px; }
  .stage-tile-label { font-size: 11.5px; }
  .stage-tile-count { font-size: 10px; }
  .stage-tile.active .stage-tile-count { padding: 1px 5px; }
  .trip-row { flex-wrap: wrap; }
  .trip-row-right { margin-left: auto; }
}
