/**
 * styles.css
 * ----------
 * The app's shared design system — colours, spacing, and basic component
 * styles used by every screen (vehicle selector, dashboard, quick-add).
 *
 * COLOUR SYSTEM (from CLAUDE.md)
 * -------------------------------
 * Colour is meaningful, not decorative. The three COST-CATEGORY colours
 * (dashboard stat cards, category breakdown bars, the Fuel & Gas
 * quick-add button) are their own dedicated tokens — "Earthy Trail",
 * chosen 2026-09-10 — separate from the app's general brand accents
 * below them:
 *   --color-run  (ochre)  = cost TO RUN   (fuel, campsites, tolls, gas)
 *   --color-mods (rust)   = vehicle modifications (the separate
 *                           improvements ledger, kept apart from
 *                           day-to-day running costs — see CLAUDE.md)
 *   --color-own  (forest green) = cost TO OWN (insurance, storage,
 *                           finance, tax)
 *
 * --color-teal/--color-coral/--color-purple still exist and are used
 * throughout the app, but as general BRAND accents rather than cost
 * categories: teal is buttons/links/the active bottom-nav tab, purple
 * is the vehicle-pairing icon, coral is status badges ("Now sold", the
 * reminder-count dot). They were split apart from the cost-category
 * colours specifically because teal was doing double duty as both — see
 * --color-run's own comment below (Brand accents section) for the full
 * reasoning.
 *
 * Light and dark mode are both defined here from the start, and follow
 * the device's system setting automatically (prefers-color-scheme) by
 * default. As of 2026-09-04 there's also an in-app "Appearance" choice
 * (Settings screen, see settings.js/theme.js) that can override this
 * per device: picking Light or Dark stamps a `data-theme="light"` or
 * `data-theme="dark"` attribute on <html> (via localStorage + theme.js,
 * loaded on every page), and "Match device" simply removes it, falling
 * straight back to the plain prefers-color-scheme behaviour below. See
 * the THEME OVERRIDE block right after the dark-mode media query for
 * how this is wired into the actual colour tokens.
 *
 * ALL TEXT SCALED UP 20% (2026-09-05) — Bob's request: "throughout the
 * app the text needs to be larger... keeping the same ratio between the
 * smaller font sizes and the larger font sizes." Every single font-size
 * value in this app — every one in this file, plus every inline
 * `style="font-size:...px"` in every web-app/*.html and web-app/js/*.js
 * file — was multiplied by 1.2 and rounded to the nearest half-pixel, so
 * a heading that was already twice the size of a caption is still
 * (very close to) exactly twice its size now — nothing was scaled
 * unevenly, and no single screen was skipped. This was done with a
 * small script that searched every file for the literal pattern
 * `font-size: <number>px` and replaced the number, not by hand — so if
 * a font size is ever added anywhere in this app in future, add it at
 * today's (already-scaled) sizes directly; there's no separate
 * "unscaled base" to keep multiplying from. The exact mapping used:
 *   11px→13px   12.5px→15px    15px→18px   21px→25px    32px→38.5px
 *   11.5px→14px 13px→15.5px    16px→19px   22px→26.5px  36px→43px
 *   12px→14.5px 13.5px→16px    18px→21.5px 28px→33.5px
 *   14px→17px   14.5px→17.5px  20px→24px
 * The one exception, already documented in its own comment further
 * down: input/select/textarea's own font-size also went through this
 * same 1.2x scaling (16px→19px) rather than being left alone — no
 * conflict with the earlier mobile-auto-zoom fix, since 19px is still
 * comfortably above the 16px threshold that fix cares about.
 *
 * SCALED UP AGAIN, SLIGHTLY (2026-09-05, same day) — Bob asked for the
 * text to be "slightly larger again" after seeing the 20% pass above.
 * Exact same method, applied a second time on top of the already-scaled
 * sizes: every font-size in this file and in every web-app/*.html and
 * web-app/js/*.js file was multiplied by 1.1 this time (a smaller bump
 * than the first pass) and rounded to the nearest half-pixel again.
 * Combined, the app's text is now about 32% larger than it was before
 * either pass (1.2 × 1.1). The mapping table above is now one step
 * stale — every number in it should be read as "then this got a further
 * ×1.1 on top" — so treat the ACTUAL current font-size values in this
 * file as the source of truth, not that table. input/select/textarea
 * went from 19px to 21px in this pass, still comfortably clear of the
 * 16px mobile-auto-zoom threshold.
 */

/* Self-hosted logo fonts (2026-09-09, Bob's request) — used only by the
   login screen's "AXLEATOR" wordmark (see the "Log in / Sign up screen"
   section further down for .axle/.ator/.brand-tagline). Previously loaded
   from Google Fonts' own CDN (a <link> in index.html's <head>); Bob asked
   to download the actual font files instead so the login page never
   depends on Google's servers being reachable. Only the weights the
   wordmark/tagline actually use are here — see delivery notes for exactly
   which files to download and where to upload them (web-app/assets/fonts/).
   font-display: swap means the page shows a plain system-font fallback for
   a moment rather than invisible text while these load, then swaps in the
   real thing once they're ready.

   REVISED 2026-09-12 (Bob's ask, referencing "example e8" from a font/logo
   comparison page he put together): "AXLE" and "ator" used to be two
   DIFFERENT font families (Orbitron-Black/900 for "AXLE", Exo2-Regular/400
   for "ator") — now both halves share ONE font (Orbitron, bold), matching
   e8's own single-font treatment of its "MACSUK" wordmark. The
   `Orbitron-Black.ttf` (900) @font-face below is gone, replaced by
   `Orbitron-Bold.ttf` (700) — see `.axle`/`.ator`'s own comment further
   down for why 700, and for the italic slant (CSS-applied, not a separate
   font file — Orbitron has no italic style on Google Fonts at all).
   `Exo2-Regular.ttf` (400) is ALSO gone — it was only ever used by the old
   `.ator`, and nothing else in this app uses Exo 2 at 400 weight.
   `Exo2-Bold.ttf` (700) is UNCHANGED — Bob's second ask ("the second font
   should replace the current font used for the running cost calculator")
   just confirms `.brand-tagline` keeps using Exo 2, which it already did;
   nothing to change there. Net effect: one font file swapped out
   (Orbitron-Black.ttf → Orbitron-Bold.ttf), one font file removed
   (Exo2-Regular.ttf, safe to delete from the server — nothing references
   it any more), Exo2-Bold.ttf untouched. */
@font-face {
  font-family: "Orbitron";
  src: url("../assets/fonts/Orbitron-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Exo 2";
  src: url("../assets/fonts/Exo2-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Surfaces */
  --surface-0: #FFFFFF;   /* page background &mdash; plain white, Bob's explicit
                             request 2026-09-04 (was an off-white #F6F6F3) */
  --surface-1: #FFFFFF;   /* card background */
  --tour-bg: #FFFFFF;
  --track-bg: #DEDDD6; /* bar-chart track: darker grey, light mode only (dark blocks set it back) */
  --surface-2: #FBFBF9;   /* subtle nested surface, e.g. progress track */
  /* --body-bg is what `body`'s own background actually uses (added
     2026-09-04). Light mode: just the flat --surface-0 above (white).
     Dark mode overrides this to a diagonal slate gradient instead of a
     flat colour (see the dark-mode blocks below) — Bob's request, "the
     dark colour... instead of black". --hero-1/--hero-2 aren't used in
     light mode at all. */
  --body-bg: var(--surface-0);
  /* --card-bg is what every previously-flat-dark surface actually uses —
     cards, buttons, inputs/selects, and the category icon grid (added
     2026-09-04, follow-up to --body-bg above; widened same day to cover
     buttons/inputs too — Bob's exact request: "can each element that
     was dark in the app be that colour too in dark mode"). Light mode:
     just the ordinary flat --surface-1 colour, same as always. Dark
     mode overrides this to the SAME diagonal slate gradient as
     --body-bg (see the dark-mode blocks below), so every one of these
     elements blends into the page instead of sitting on a flat,
     separate dark tone. See the placeholder-visibility comment further
     down for the one thing this required fixing alongside it: default
     placeholder text was too low-contrast against the lighter end of
     this gradient once inputs stopped being a flat, uniformly dark
     colour. */
  --card-bg: var(--surface-1);

  /* Text */
  --text-primary: #1B1B18;
  --text-secondary: #5C5C57;
  --text-muted: #6E6D67; /* 2026-10 accessibility pass: was #8C8B84 (3.4:1 on white) — now 5.2:1 */
  --on-primary: #FFFFFF;
  /* --placeholder is the colour used for empty-field placeholder text
     (added 2026-09-04, alongside --card-bg above). Light mode: just
     --text-muted, same greyish tone browsers already use by default —
     this only exists as its own variable because dark mode needs a
     DIFFERENT value (see that block's own comment for why). */
  --placeholder: var(--text-muted);

  /* Borders */
  --border: #E4E3DE;
  --border-strong: #C8C7C0;
  /* --border-box (2026-09-27, Bob's ask: "in light mode only can all of
     the borders around the white boxes in the app be a slightly darker
     grey colour apart from the top and bottom navigation") — a
     dedicated token for the OUTER border of a freestanding white/card
     box (cards, list rows, buttons, form fields, dropdown popups, photo
     frames), kept separate from --border itself so the top bar/bottom
     nav (which also use --border for their own edge line) and internal
     row dividers (which use --border too, but aren't "the border around
     a box") are both left untouched. Reuses --border-strong's own
     value rather than inventing a third shade — the same grey `button:
     hover` already switches to further down this file, so this is a
     shade already vetted as visible-but-subtle against a white card. In
     DARK mode this resolves back to plain var(--border) (see both dark
     blocks below) — Bob's ask was explicitly light-mode only. */
  --border-box: #C8C7C0;

  /* Brand accents */
  /* --color-teal updated 2026-09-04 to Bob's real brand teal (#50DEFF in dark
     mode, sampled directly from his logo/website). This light-mode value is
     a deeper, more saturated shade of the SAME hue (~191°, cyan-blue) rather
     than the dark-mode value used verbatim, because #50DEFF is far too light
     to read as text or as a filled-button background against a white card —
     same "one hue, two lightness levels" treatment this file already gives
     every other brand colour between light and dark mode. */
  --color-teal: #006B85;
  --color-teal-soft: #E3F4F8;
  --color-coral: #B5521F; /* 2026-10 accessibility pass: was #C15A2C (4.4:1 with white text) — now 5.0:1 */
  --color-coral-soft: #FAECE7;
  --color-purple: #5B4FD6;
  --color-purple-soft: #EEEDFE;
  /* --color-own/-run/-mods (2026-09-10, "Earthy Trail" palette) — the
     three COST-CATEGORY colours (dashboard stat cards, category
     breakdown bars, the Fuel & Gas quick-add button) are now their own
     dedicated tokens, separate from --color-teal/-purple/-coral above.
     Bob's explicit call: cost to run should recolour to this new
     palette, but the app's general brand teal (links, buttons, active
     bottom-nav tab) should NOT — teal was doing double duty as both the
     "cost to run" colour AND the app's primary brand accent, so
     splitting them out means one can change without dragging the other
     along. (Purple/coral are similarly reused elsewhere — the vehicle
     pairing icon, "Now sold" badge, reminder-count dot — so they keep
     their existing values too; only the three literal cost-category
     colours move to Earthy Trail.) One hue, two lightness levels
     between light/dark, same pattern as every other colour here — see
     the dark-mode blocks below for the brighter dark-mode values. */
  --color-own: #007F66; /* 2026-10 accessibility pass: was #008F72 (4.1:1 with white) — now 5.0:1 */
  --color-own-soft: #E2F3ED;
  --color-run: #C08A1E;
  --color-run-soft: #F6EDE0;
  --color-mods: #9C3D2E;
  --color-mods-soft: #FCEAE6;
  --color-trip: #2F6FB5;
  --color-trip-soft: #E4EEF9;
  /* --color-orange (2026-09-09): the distinct orange accent discussed
     back when the new-look bottom nav/quick-add mockup was first drawn
     up (#FF9736, Bob's own pick from that canvas, never yet applied to
     the real app) — same value in BOTH light and dark mode (unlike
     teal/coral/purple's "two lightness levels" treatment above), same
     as the mockup used it, since it's a solid icon-button fill rather
     than page text or a soft tinted background. Declared once here in
     the base :root rather than in either dark-mode block, following
     this file's own convention for a token that doesn't change between
     modes (e.g. --topo-bg, --radius above). First real use: the top
     bar's "+" Add button (see .top-bar #tbAddBtn below) — Bob's
     request to make it more visible. --on-orange is the icon colour
     that reads on top of that fill, a dark near-black (matching the
     mockup's own --on-orange), needed in both modes since #FF9736 is
     light enough that a light/white icon would fail contrast either
     way. */
  --color-orange: #FF9736;
  --on-orange: #1B1B18;

  /* Semantic */
  /* --fill-primary changed 2026-09-18 (Bob's ask: "buttons that are
     blue... change all of those buttons that are blue to the coral
     colour in the app") — was a literal duplicate of --color-teal's own
     value (#006B85), which is what actually made every .primary-btn
     ("Save", "Email to upgrade", etc. — see .primary-btn below) look
     blue rather than the app's coral brand accent. Now a literal
     duplicate of --color-coral's value instead, same "keep it a
     separate token from the colour it happens to currently match"
     approach this file already used for --fill-primary/--color-teal —
     see the dark-mode blocks below for the matching dark-mode value. */
  --fill-primary: #B5521F; /* matches --color-coral, see above */
  --fill-ghost-selected: #E5F3F7;
  --text-danger: #B3392A;
  --text-success: #006B85;
  /* --tab-bg (2026-09-10, Bob's ask) — the dashboard's 6 period/metric
     toggle buttons ("This month"/"This year"/"All time" and "Total"/
     "Per mile"/"Per night away") used to be transparent at rest with a
     --fill-ghost-selected highlight on whichever one was active; Bob
     asked for all 6 to share one flat background instead, distinguished
     from each other by border-strong (already set) and font-weight
     (added alongside this) rather than a colour fill. Light mode: a
     light grey, chosen for clear contrast against --text-primary
     (#1B1B18) — 13.9:1, comfortably past WCAG's 4.5:1 floor for normal
     text, so "accessibility maintained" isn't just asserted, it's
     checked. Dark mode's value is set to var(--card-bg) in both dark
     blocks below, per Bob's explicit ask to match dark mode's tab
     colour to "the background colour of the total cost tab below the 6
     tabs" (the headline/stat cards, which use --card-bg).
     Light-mode value updated 2026-09-12 (Bob's ask: "change the
     background colour to #ececec") — from #E8E7E1 to #ececec, a barely
     perceptible difference, contrast against --text-primary is 14.6:1,
     comfortably past the 4.5:1 AA floor. Scoped to light mode only —
     Bob's message didn't mention dark mode, and dark mode's tab colour
     is deliberately tied to --card-bg rather than a flat grey (see
     above), so it's been left as-is; flag this if he actually wants
     dark mode's tabs to also become a flat grey. */
  --tab-bg: #ececec;

  --radius: 10px;
  --radius-lg: 14px;

  /* Topographic contour-line backdrop (2026-09-04, matching the same
     watermark-style pattern used on Bob's own website,
     motorhoming-adventures.co.uk) — updated 2026-09-04 to use the REAL
     vector SVG straight from Bob's own site (fetched directly from
     motorhoming-adventures.co.uk/images/topology-background.svg), not a
     pre-rendered raster capture of it. The site never inverts this
     pattern for a dark background — same soft grey-green (#d8ddd2,
     baked into the SVG's own <style> block) in both modes — so ONE file
     covers both themes; --topo-opacity below is what applies the fade,
     via body::before's own `opacity` (a dedicated pseudo-element built
     for exactly this, so there's no need to bake the fade into the
     image itself). Applied via body::before below, not directly on
     body's own background, so it can sit behind ALL page content as one
     fixed backdrop without affecting anything drawn on top of it.

     --topo-opacity itself (2026-09-05): originally 28% in BOTH modes.
     Bob confirmed dark mode looks right as-is, but the lines read as
     too faint in light mode specifically — so this base value (used by
     light mode, since light mode has no override block of its own) is
     now 42%, a noticeable but still subtle increase, while both dark-
     mode blocks below explicitly set it back to the original 28% so
     dark mode is completely unaffected. */
  --topo-bg: url('../assets/topo-background.svg');
  --topo-opacity: 0.42;

  /* color-scheme (2026-09-17, Bob's ask — see the "Dropdown legibility"
     entry near the bottom of this file for the full story): tells the
     browser which theme its OWN native form controls should render
     in — most importantly here, a <select>'s dropdown popup. Without
     this, a browser has no way to know this page has its own dark
     mode, so in dark mode it kept drawing every dropdown's popup with
     its default LIGHT native chrome while the app's own CSS still gave
     the <select>/<option> text white (matching the rest of the page) —
     white text on a light popup background, invisible. Declared as a
     plain single keyword (light here, dark in both dark-mode blocks
     below) rather than "light dark" specifically because this app's
     own in-app Appearance override (data-theme="light"/"dark") must be
     able to win over the device's OS setting, same reason every other
     themed value in this file uses the three-block
     media-query-plus-explicit-attribute pattern instead of trusting
     the browser to infer it. */
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  /* :not([data-theme="light"]) — so someone whose PHONE is set to dark
     mode but has explicitly picked "Light" in the app's own Appearance
     setting doesn't get pulled back into dark by their device setting. */
  :root:not([data-theme="light"]) {
    --surface-0: #171715;
    --surface-1: #201F1D;
    --surface-2: #262523;
    --track-bg: #262523;
    --toggle-section-bg: transparent;
    --tour-bg: #2F373E;

    /* --text-primary and --text-secondary were an off-white (#F1F0EA) and
       a mid grey (#B7B5AC) until 2026-09-04 — Bob's explicit follow-up:
       "can all of the other text that is not in a placeholder be white
       in dark mode". Both now plain white, same as --text-muted just
       below. Deliberately NOT extended to --text-danger/--text-success
       (error/success messages) or the teal/coral/purple category
       colours (--color-teal etc.) — those are meaningful signal colour
       per this app's own colour system (see the top of this file),
       not plain body text, so turning them white would erase
       information rather than just changing a text colour. --on-primary
       is also untouched: it's the text colour ON TOP OF a light-filled
       button/badge (e.g. the teal "Sign up" button), not page text, and
       needs to stay dark for contrast against that light fill. */
    --text-primary: #FFFFFF;
    --text-secondary: #FFFFFF;
    --text-muted: #FFFFFF;
    --on-primary: #0E0E0D;

    --border: #35342F;
    --border-strong: #48473F;
    /* --border-box falls back to plain --border in dark mode — see the
       light-mode :root's own comment on this token; Bob's ask was
       explicitly light-mode only, so dark mode is unchanged here. */
    --border-box: var(--border);

    --color-teal: #50DEFF;
    --color-teal-soft: #123138;
    --color-coral: #E8916B;
    --color-coral-soft: #3B2620;
    --color-purple: #A79BFF;
    --color-purple-soft: #29254A;
    /* Earthy Trail dark-mode values — see the base :root block's own
       comment above for why these are separate from --color-teal. */
    --color-own: #48C3A2;
    --color-own-soft: #093328;
    --color-run: #F1B136;
    --color-run-soft: #39280A;
    --color-mods: #F87964;
    --color-mods-soft: #41211C;
    --color-trip: #6FA8F0;
    --color-trip-soft: #14283F;

    --fill-primary: #E8916B;
    --on-primary: #0E0E0D;
    --fill-ghost-selected: #14363E;
    --text-danger: #E0897B;
    --text-success: #50DEFF;
    /* See --tab-bg's own comment in the base :root block above — dark
       mode matches the dashboard's 6 toggle tabs to the same background
       the stat cards use (--card-bg, itself var(--body-bg) in dark
       mode — see that block below), instead of the old --fill-ghost-
       selected teal highlight. */
    --tab-bg: var(--card-bg);
    --link-line: #FFFFFF;
    --link-line-hover: #000000;

    /* Body background — a diagonal slate gradient instead of a flat
       colour (Bob's request 2026-09-04: "the dark colour... instead of
       black"). --hero-1/--hero-2 are sampled directly from
       motorhoming-adventures.co.uk's own hero-section gradient
       (rgb(72,81,90) / rgb(102,113,123) — two of its middle stops); the
       darkest stop, #2f363d, is Bob's own value, itself a close match to
       the site's own darkest hero stop (rgb(43,49,56)). Flag to Bob if
       these aren't the exact two shades he had in mind — easy to swap. */
    --hero-1: #66717B;
    --hero-2: #48515A;
    --body-bg: linear-gradient(165deg, var(--hero-1) 0%, var(--hero-2) 55%, #2f363d 100%);
    /* Every previously flat-dark surface (cards, buttons, inputs, the
       category icon grid) uses this same gradient — see --card-bg's
       comment in the base :root block above. */
    --card-bg: var(--body-bg);
    /* Placeholder text (2026-09-04, still needed as its own variable even
       now that --text-muted above is plain white): --text-muted at full
       white would read almost identically to actually-typed text
       (--text-primary, #F1F0EA), leaving no visual difference between an
       empty field's hint text and something the user has actually typed.
       #DCDAD2 (a warm light grey) is deliberately a shade dimmer than
       both, while still keeping a minimum of ~3.6:1 contrast against
       every stop in this gradient (checked mathematically against all
       three — #66717B/#48515A/#2f363d — not just the darkest one). */
    --placeholder: #DCDAD2;
    /* Keeps dark mode's topo backdrop at its original 28% — see the
       base :root block's --topo-opacity comment for why this needs to
       be restated here now that light mode uses a higher value. */
    --topo-opacity: 0.28;

    /* See the base :root block's own color-scheme comment above. */
    color-scheme: dark;
  }
}

/* THEME OVERRIDE (2026-09-04) — the other half of the in-app Appearance
   toggle. The media query above only ever fires when the DEVICE itself
   is in dark mode. This block is what makes picking "Dark" in the app
   work even on a device whose system setting is Light — same variable
   list as the dark-mode block above, just applied unconditionally
   whenever theme.js has stamped data-theme="dark" on <html>, rather
   than only when the OS says so. Picking "Match device" (the default)
   never sets this attribute at all, so this block simply never
   matches and the plain prefers-color-scheme behaviour above applies. */
:root[data-theme="dark"] {
  --surface-0: #171715;
  --surface-1: #201F1D;
  --surface-2: #262523;
    --track-bg: #262523;
    --toggle-section-bg: transparent;
    --tour-bg: #2F373E;

  /* Same white --text-primary/--text-secondary/--text-muted as the
     dark-mode media query above — see its comment for why, and for why
     the semantic/category colours and --on-primary are untouched. */
  --text-primary: #FFFFFF;
  --text-secondary: #FFFFFF;
  --text-muted: #FFFFFF;
  --on-primary: #0E0E0D;

  --border: #35342F;
  --border-strong: #48473F;
  /* --border-box falls back to plain --border in dark mode — see the
     light-mode :root's own comment on this token; Bob's ask was
     explicitly light-mode only, so dark mode is unchanged here. */
  --border-box: var(--border);

  --color-teal: #50DEFF;
  --color-teal-soft: #123138;
  --color-coral: #E8916B;
  --color-coral-soft: #3B2620;
  --color-purple: #A79BFF;
  --color-purple-soft: #29254A;
  /* Earthy Trail dark-mode values — see the base :root block's own
     comment (Brand accents section) for why these are separate from
     --color-teal. Same values as the prefers-color-scheme dark block
     above, since this THEME OVERRIDE block always mirrors it. */
  --color-own: #48C3A2;
  --color-own-soft: #093328;
  --color-run: #F1B136;
  --color-run-soft: #39280A;
  --color-mods: #F87964;
  --color-mods-soft: #41211C;
  --color-trip: #6FA8F0;
  --color-trip-soft: #14283F;

  --fill-primary: #E8916B;
  --fill-ghost-selected: #14363E;
  --text-danger: #E0897B;
  --text-success: #50DEFF;
  /* See --tab-bg's own comment in the base :root block above. */
  --tab-bg: var(--card-bg);
    --link-line: #FFFFFF;
    --link-line-hover: #000000;

  /* Same gradient background as the dark-mode media query above — see
     its comment for where --hero-1/--hero-2 come from. */
  --hero-1: #66717B;
  --hero-2: #48515A;
  --body-bg: linear-gradient(165deg, var(--hero-1) 0%, var(--hero-2) 55%, #2f363d 100%);
  /* Every previously flat-dark surface uses this same gradient — see
     --card-bg's comment in the base :root block above. */
  --card-bg: var(--body-bg);
  /* Same lighter placeholder colour as the dark-mode media query above —
     see its comment for the contrast math. */
  --placeholder: #DCDAD2;
  /* Same reasoning as the dark-mode media query above — keeps dark
     mode's topo backdrop at its original 28% now that light mode uses
     a higher value. */
  --topo-opacity: 0.28;

  /* See the base :root block's own color-scheme comment further up. */
  color-scheme: dark;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

/* Always reserve the vertical scrollbar's space, on every page, whether
   or not that page's own content is actually tall enough to need
   scrolling (2026-09-27, Bob's report: "the whole app visibly moving
   sideways... it seems to happen on all screen sizes").

   ROOT CAUSE, now CONFIRMED directly (not just theorised) via a real,
   non-headless test browser — a genuine desktop scrollbar physically
   narrows the usable page width by ~15px, but ONLY on a page whose
   content is tall enough to need one. This app centres its whole
   app-shell/top-bar/bottom-nav horizontally within whatever width is
   actually available, so a page that needs a scrollbar sits centred
   ~7.5px further left than a page that doesn't — confirmed with a
   controlled short-page-vs-tall-page test: 0px shift before this fix,
   15px of scrollbar reserved on the tall page and 0px on the short
   one. Some of this app's pages are consistently one way or the other
   (hence Bob seeing a consistent left/right shift tied to a specific
   destination — Vehicles, Trips, Reminders, etc. — regardless of which
   page he came from); a page whose own height changes as its data
   loads in (Dashboard, Upgrade) can cross that threshold mid-render,
   which is what Bob is seeing as a "jitter" rather than a clean shift.

   FIX: `overflow-y: scroll` forces the browser to ALWAYS show a real,
   permanent scrollbar — even on a short page that doesn't need to
   scroll — so the width available for centring never changes between
   pages in the first place. This is the OLD, universally-supported way
   to do this (long predates the newer `scrollbar-gutter` property) and
   was confirmed, via that same real-browser test, to reserve the
   identical 15px on both a short and a tall page — eliminating the
   shift entirely (0px difference either way).

   An earlier attempt at this fix used the newer `scrollbar-gutter:
   stable` property instead, which technically also reserves the space,
   but paints its own reserved (and mostly empty) area using the
   browser's plain default canvas colour rather than anything this app
   can style — producing an ugly, unfixable dark stripe down the screen
   edge in dark mode. Going back to the OLDER technique here sidesteps
   that entirely: because `overflow-y: scroll` always draws a REAL,
   ordinary scrollbar widget (not an empty reserved gutter), the
   `scrollbar-color` property below (the modern, cross-browser way to
   recolour a native scrollbar) and the `::-webkit-scrollbar-*` rules
   (the older Chrome/Edge/Safari-specific equivalent, for full
   coverage) both have a real scrollbar to actually restyle — confirmed
   with real screenshots in both light and dark mode, no dark stripe,
   no mismatched colour, just a slim scrollbar in the app's own tones.
   Neither this rule nor its styling has any effect on a platform that
   already uses overlay scrollbars (a Mac, or an iPhone/Android — none
   of which ever reserved layout space to begin with, so there was
   nothing to shift there in the first place). */
html {
  overflow-y: scroll;
  scrollbar-color: var(--border-strong) var(--body-bg);
}
html::-webkit-scrollbar { width: 14px; }
html::-webkit-scrollbar-track { background: var(--body-bg); }
html::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 8px;
  border: 3px solid var(--body-bg);
  background-clip: padding-box;
}

/* body is a flex column with min-height:100vh, and .app-shell below has
   auto top/bottom margins — that's the whole trick (2026-09-05, Bob's
   request: "bring the app down lower so the padding above and below
   [is] equal... without obscuring anything"). Auto margins on a flex
   item absorb any SPARE space evenly, so on a short screen (e.g. the
   login page, or any screen with little content) .app-shell sits
   centred with equal breathing room above and below instead of stuck
   to the top with a large gap underneath. On a tall screen (dashboard,
   cost history, a long reminders list) there's no spare space to
   absorb in the first place, so this has no effect at all — the page
   just starts at the normal top padding and scrolls exactly as before,
   nothing is ever pushed up out of view. align-items: center replaces
   .app-shell's old `margin: 0 auto` for horizontal centring, since a
   flex item's own left/right auto margins can conflict with vertical
   auto margins in a column flex — same visual result, just done the
   flex way now that body is a flex container. */
body {
  font-family: var(--font-sans);
  background: var(--body-bg);
  /* 2026-09-09 — pinned to the viewport (not the page's own, possibly
     much taller, scrollable height) so it's computed the exact same way
     as .top-bar/.bottom-nav's own background — see their shared comment
     for why. Without this, on a long scrolling page the two could show
     very slightly different portions of the gradient at different scroll
     positions instead of matching exactly everywhere. */
  background-attachment: fixed;
  background-position: center top;
  background-size: cover;
  color: var(--text-primary);
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
}

/* The topo backdrop itself — a fixed, full-viewport layer sat BEHIND
   every page's content (z-index: -1), so it never scrolls away or gets
   stretched/cropped oddly on a long, tall page the way a plain body
   background-image would (a plain body background is sized against the
   whole scrollable page height, not just the visible viewport). Every
   screen in the app already has a solid --surface-1 card background for
   its actual content, so this only ever shows through the page's outer
   margins — exactly where it shows on Bob's own website, behind the
   content rather than under it.

   background-size (2026-09-04, history):
   1) Started as `cover` — wrong, because the SVG's own artwork is
      landscape (2:1, viewBox 800x400) but the app's screens are
      portrait. `cover` scaled the image up to fill the viewport
      HEIGHT, zooming into only a ~25% horizontal sliver of the
      artwork — lines looked sparse.
   2) Switched to `100% auto` + `repeat-y` — showed the full width of
      the artwork with no cropping. Better, but `100%` is a PERCENTAGE
      of the element's own width, i.e. the viewport width — so the
      actual density still depended on screen size: dense-ish on a
      narrow phone (390px, smaller than the art's native 800px design
      width), but noticeably sparser on a wide desktop browser window
      (e.g. 1440px, well over native size, stretching the same amount
      of line-work over much more area). That's why it could still
      look too sparse depending on where you looked at it.
   3) A FIXED pixel tile — 300px 150px, well under the artwork's
      native 800x400 — with `repeat` (tiling in BOTH directions, not
      just down). A fixed size is not relative to the viewport at all,
      so the density is the same everywhere, and shrinking the tile
      below native size packs more of the artwork's line-work into the
      same area (a smaller tile = more repeats = denser). `repeat`
      (rather than `repeat-y`) also fills the width with tiles instead
      of one stretched band, closer to how the pattern reads on Bob's
      own website. There's a visible seam at each repeat (the art
      wasn't drawn to tile seamlessly), but at 28% opacity, sat behind
      the app's own card content, it reads as organic texture rather
      than an obvious repeat.
   4) Back to 800px 400px (2026-09-05) — Bob's own current live value,
      confirmed directly by him ("i have this in the body before -
      background-size: 800px 400px") when flagging an unrelated bug,
      so this file is updated to match rather than pulling his site
      back to the denser 300x150 tile from step 3. If density ever
      needs revisiting again, treat 800x400 (native size, one full
      copy of the artwork per tile) as today's actual starting point,
      not 300x150 — and keep whatever ratio is chosen close to 2:1 to
      avoid stretching the line-work. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  background-image: var(--topo-bg);
  background-repeat: repeat;
  background-position: center top;
  background-size: 800px 400px;
  opacity: var(--topo-opacity);
  pointer-events: none;
  z-index: -1;
}

.app-shell {
  max-width: 420px;
  width: 100%;
  margin: auto 0;
  padding: 24px 16px;
}

/* Mobile follow-up (2026-09-05): the full vertical centring above
   looked right on desktop but pushed the app too far down on an actual
   phone screen — Bob's own words, "too low now". Below 600px width
   (i.e. an actual phone, as opposed to a desktop/tablet browser window
   with lots of empty space either side of the 420px-wide app column)
   this switches back to the original top-anchored layout, just with a
   bit more top padding than there was originally (24px -> 40px) so the
   app sits slightly lower than before, without going all the way to
   centred. Desktop (600px and up) is untouched — it keeps the auto-
   margin centring from the .app-shell rule above. */
@media (max-width: 600px) {
  .app-shell {
    margin: 0;
    padding: 40px 16px 48px;
  }
}

h1, h2, h3 { margin: 0; }

/* A default look for a plain text link (2026-09-04, added for the new
   login screen's "App by Motorhoming Adventures" footer credit) — every
   other <a> in this app already sets its own colour via a class or
   inline style (e.g. dashboard.html's "&larr; Vehicles" link), so this
   only ever applies where nothing more specific is set; it can't change
   how any existing link looks.
   Changed from --color-teal to --fill-primary (2026-09-27, Bob's ask:
   "can all links in the app be changed from blue to var(--fill-primary)
   #E8916B") — --fill-primary is the same coral value already used for
   every .primary-btn (see the 2026-09-18 "Blue buttons changed to
   coral app-wide" entry), so this keeps one single source of truth for
   that colour rather than a second, separate coral value. */
a { color: var(--fill-primary); }
a:hover { color: var(--fill-primary); opacity: 0.8; }

button {
  font-family: inherit;
  border-radius: var(--radius);
  border: 0.5px solid var(--border-box);
  background: var(--card-bg);
  color: var(--text-primary);
  cursor: pointer;
}
button:hover { border-color: var(--border-strong); }
button:disabled { opacity: 0.5; cursor: default; }

/* font-size history for input/select/textarea:
   - Originally 14px.
   - Raised to 16px (2026-09-04) as a targeted fix for a specific
     mobile-Safari/Chrome behaviour, not a general "make text bigger"
     change (Bob's own question when reporting this). iOS auto-zooms the
     whole page in whenever you focus a text field whose font-size is
     under 16px, on the theory that anything smaller is too small to
     read while typing — that's what Bob saw happen focusing the
     password field ("the app width... gets bigger"), and why the
     layout looked wrong/cropped on the very next screen: the browser
     was left zoomed in from the field that had just been focused, so
     the next page render started from that zoomed-in state instead of
     a normal one. Bringing every input/select/textarea up to 16px
     stopped the auto-zoom from triggering — nothing else needed to get
     bigger for that fix specifically.
   - Raised to 19px, then to 21px (both 2026-09-05, two back-to-back
     "make all text larger" passes — see those comments just above the
     :root block) — comfortably still above the 16px auto-zoom
     threshold, so this doesn't reopen the mobile zoom issue above. */
input, select, textarea {
  font-family: inherit;
  font-size: 21px;
  border-radius: var(--radius);
  border: 0.5px solid var(--border-box);
  background: var(--card-bg);
  color: var(--text-primary);
  padding: 9px 10px;
}
/* Outline colour changed from --color-teal to --fill-primary (2026-09-27)
   — Bob's follow-up to the link-colour change above: "the ones that
   change to blue on hover or click just change to the orange on hover
   or click only" — this is the app's click/keyboard-focus ring, a
   colour that only ever appears on focus, not at rest, so it's exactly
   what that follow-up describes. */
input:focus, select:focus, textarea:focus, button:focus-visible {
  outline: 2px solid var(--fill-primary);
  outline-offset: 1px;
}
/* Placeholder text (2026-09-04, added alongside the --card-bg widening
   above) — uses --placeholder rather than being left to each browser's
   own default. opacity: 1 specifically overrides Firefox, which dims
   ::placeholder text to ~54% opacity by default; without this override
   the carefully-contrast-checked --placeholder colour in dark mode
   would still end up too faint there. */
input::placeholder, textarea::placeholder {
  color: var(--placeholder);
  opacity: 1;
}

/* DROPDOWN LEGIBILITY (2026-09-17, Bob's ask): "on the cost history
   dropdown choices for categories etc you can see the choices in black
   in light mode but you can't in dark mode." Checked every <select> in
   the app (grepped for "<select" across every HTML/JS file) — every
   one of them shares this one global rule above (`input, select,
   textarea`), so this single fix covers every dropdown in the app
   (cost-history.html's category/date filters, quick-add.html/
   edit-entry.html's currency/trip/fuel-type selects, every hub's "Add a
   vehicle"/"Add a type" picker, the Vehicles page's pairing/
   vehicle-type pickers) — nothing scattered per-file needed fixing
   here, unlike some of this app's other per-page duplicated styling.

   ROOT CAUSE (confirmed, not guessed): a <select>'s dropdown POPUP is
   drawn by the browser/OS itself, not by this page's own layout — and
   without telling the browser this page has its own dark mode, it kept
   drawing that popup with its default LIGHT native chrome even when the
   app's own CSS was giving the select/option text white (matching the
   rest of a dark-mode page) — white text on a light popup background,
   invisible. `color-scheme` (added to the base :root block and both
   dark-mode blocks above, light/dark to match) tells the browser which
   scheme its OWN native controls should render in — the dropdown
   popup, the little arrow icon inside a closed select, and scrollbars.
   `select option`'s explicit background/colour just below is a
   belt-and-braces layer honoured by Chrome/Firefox/Edge for the
   popup's own text/background (Safari draws the popup itself
   regardless — color-scheme is what actually fixes Safari) — plain,
   long-established CSS, nothing experimental.

   HIGHLIGHT COLOUR — TRIED, THEN REVERTED (2026-09-17): first attempt
   used `appearance: base-select`, a genuinely new (2026) CSS feature
   meant to hand a select's whole popup over to ordinary CSS so the
   hover-highlighted row could be recoloured green (no other standard
   CSS controls that highlight at all — checked directly, `accent-color`
   explicitly doesn't apply to <select>/<option> in any browser). Bob
   tried it on his real device and reported it made things WORSE, not
   better: some choices highlighted blue while scrolling (inconsistent
   with the intended green — a half-working activation, not a clean
   one) and dark-mode choices were invisible until scrolled (a real
   rendering glitch in that still-very-new feature, not something this
   app's own CSS could work around). Bob asked to remove the green and
   just make sure everything's reliably visible — so `appearance:
   base-select`, `::picker(select)`, and the `option:hover/:focus/
   :checked` green rules are gone entirely. This app is back to each
   browser's own plain, ordinary dropdown highlight colour (not green)
   — with the color-scheme + option-colour fixes above still doing the
   actual job of making every dropdown legible in both modes.

   SUPERSEDED (2026-09-17, same day): Bob tried this on his real desktop
   Chrome/Edge and still saw items invisible until hovered. Confirmed via
   research this is a genuine, well-documented browser limitation — Chrome
   in particular doesn't reliably apply author CSS to a native <select>'s
   own popup, even when it's completely correct, because that popup is a
   separate native rendering surface. There's no reliable CSS-only fix.
   Every <select> in the app is now rendered with a fully custom dropdown
   instead (see js/custom-select.js and the ".cs-*" rules below) — the
   native <select> is hidden and never opens its own popup any more. The
   `select option` rule just below is left in place purely as a harmless
   fallback (in case custom-select.js somehow fails to load, the native
   dropdown — if it were ever actually shown — would still be legible
   rather than reverting to fully unstyled), not because it's doing the
   real work any more. `color-scheme` above is still genuinely useful for
   OTHER native controls this page has (date inputs, scrollbars) and was
   left completely unchanged. */
select option {
  background: var(--card-bg);
  color: var(--text-primary);
  padding: 9px 10px;
}

/* CUSTOM DROPDOWN (2026-09-17) — see js/custom-select.js's own header
   comment for the full story of why this exists. Every <select> gets
   wrapped in one of these; the native select itself becomes `.cs-native`
   (hidden, kept only so its value/events keep working for every other
   file's existing code, completely unchanged). The trigger button is
   styled identically to the app's existing `input, select, textarea`
   rule above so nothing about a dropdown's CLOSED appearance changes at
   all — only the OPEN list is genuinely new. */
.cs-native {
  /* display:none, not just visually hidden — the custom trigger/listbox
     below has its own full keyboard handling and ARIA roles, so the
     native element never needs to be focusable or shown to anyone,
     including a screen reader. */
  display: none;
}
.cs-wrap {
  /* Block, with an explicit 100% width — NOT inline-block with no width
     (2026-09-18: Bob found the trailer-pairing dropdown on the Vehicles
     page spilling out past the right edge of the page). Root cause: a
     plain <select> in a `.field` never set its own width — it relied on
     `.field select { width: 100%; }` — but that rule targets `select`
     elements, and the select is now nested INSIDE this wrap (hidden, see
     .cs-native), so the wrap itself was getting no width at all from
     that rule. Left as `inline-block` with no width, this wrap's own
     width is "shrink to fit content" — and because its .cs-trigger child
     is ALSO `width: 100%` (a percentage against an auto-width parent,
     which CSS leaves undefined when they'd otherwise be circular) and
     its .cs-listbox child is an absolutely-positioned box whose own
     width can grow to fit its widest option, browsers can resolve that
     undefined case by letting the whole wrap balloon out to match the
     LISTBOX's own natural content width — ignoring the max-width bound
     custom-select.js's position() sets, since by then the wrap (and so
     the listbox's `min-width: 100%`) had already grown to match it. An
     explicit, definite `width: 100%` here breaks that circular
     resolution outright — the wrap always fills its real container
     (matching the old plain-<select> behaviour exactly), so
     .cs-trigger's own 100% resolves against something concrete, and
     .cs-listbox's `min-width: 100%` can never be pulled wider than that.
     A select with its own explicit inline sizing (e.g.
     cost-history.html's `flex:1; min-width:0;`, carried onto this wrap
     by custom-select.js's enhance()) still overrides this default via
     ordinary CSS specificity/inline-style precedence, so those are
     unaffected. */
  position: relative;
  display: block;
  width: 100%;
}
.cs-trigger {
  font-family: inherit;
  font-size: 21px;
  border-radius: var(--radius);
  border: 0.5px solid var(--border-box);
  background: var(--card-bg);
  color: var(--text-primary);
  padding: 9px 10px;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  text-align: left;
  cursor: pointer;
}
/* Outline colour changed from --color-teal to --fill-primary (2026-09-27)
   — same reasoning as the input/select/textarea/button focus rule
   above: this custom-dropdown trigger's own focus ring only ever shows
   on focus, not at rest, matching Bob's "blue on hover or click" ask. */
.cs-trigger:focus-visible {
  outline: 2px solid var(--fill-primary);
  outline-offset: 1px;
}
.cs-trigger.cs-disabled { opacity: 0.5; cursor: default; }
.cs-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cs-chevron {
  flex-shrink: 0;
  display: inline-flex;
  transition: transform 0.15s ease;
}
.cs-trigger[aria-expanded="true"] .cs-chevron { transform: rotate(180deg); }
.cs-listbox {
  /* Sized to fit its WIDEST option (min-width keeps it at least as wide
     as the trigger button; width:max-content lets it grow past that to
     fit the longest option label; max-width stops it growing past the
     screen). Added 2026-09-18: Bob found the list opened only as wide as
     the currently-selected (often short) label, so longer choices like
     "Insurance renewal due" were cramped — the trigger button itself is
     unchanged and still shows its short default text; only the OPEN list
     is wider now. See custom-select.js's position() for the matching
     left/right-edge flip that keeps a wide list on-screen. */
  display: none;
  position: absolute;
  left: 0;
  top: calc(100% + 4px);
  z-index: 40;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  background: var(--card-bg);
  border: 0.5px solid var(--border-box);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
  max-height: 260px;
  overflow-y: auto;
  box-sizing: border-box;
  min-width: 100%;
  width: max-content;
  max-width: min(90vw, 420px);
}
.cs-listbox.cs-open { display: block; }
.cs-listbox.cs-flip-up { top: auto; bottom: calc(100% + 4px); }
.cs-optgroup-label {
  padding: 8px 12px 4px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: normal;
  overflow-wrap: break-word;
}
.cs-option {
  /* 2026-09-20: Bob found the trailer-pairing dropdown's own text getting
     cut off ("No motorhome or campervan (unpaired)" losing its last few
     words) — this was every option's text everywhere in the app, not
     just that one. Cause: the listbox's own width is deliberately capped
     to stay within its card/column (see custom-select.js's position(),
     2026-09-18) — right for keeping the BOX itself from overflowing the
     page, but this rule was still `white-space: nowrap` +
     `text-overflow: ellipsis`, so once a real option's label was wider
     than that capped box, it silently chopped the end of the text off
     instead of ever making the box any wider. Fix: options now wrap onto
     a second (or third) line instead of being cut off — `white-space` is
     no longer forced to one line, and `overflow-wrap: break-word` is a
     safety net for the rare single "word" (e.g. a long, spaceless trip
     name someone typed in) that's still too wide for the column on its
     own, so it breaks rather than pushing the box wider than its card.
     The box itself still grows to fit shorter options on one full line
     wherever there's room — this only changes what happens once an
     option genuinely doesn't fit. */
  padding: 9px 12px;
  color: var(--text-primary);
  cursor: pointer;
  font-size: 19px;
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.3;
}
.cs-option.cs-active,
.cs-option:hover:not(.cs-option-disabled) {
  background: var(--color-own);
  color: var(--on-primary);
}
.cs-option.cs-selected { font-weight: 600; }
/* 2026-10-04, Bob's ask: a thin grey line between neighbouring options so
   each reads as its own button when the green highlight is on one of them. */
.cs-option + .cs-option { border-top: 1px solid rgba(128, 128, 128, 0.55); }
/* While the pointer is over the list, only the option under it is green —
   the keyboard-active/selected one stops being highlighted too, so two
   neighbours never both look pressed (Bob, 2026-10-04). */
.cs-listbox:hover .cs-option.cs-active:not(:hover) { background: transparent; color: var(--text-primary); }
.dropdown-item + .dropdown-item { border-top: 0.5px solid var(--border); border-radius: 0; }
.cs-option-disabled { opacity: 0.5; cursor: default; }
.cs-empty {
  padding: 9px 12px;
  color: var(--text-secondary);
  font-style: italic;
}

.icon {
  width: 20px;
  height: 20px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -4px;
  flex-shrink: 0;
}

/* Vehicle selector */
/* color/text-decoration (2026-09-12): vehicle-selector.js only ever uses
   .vehicle-card as a <button>, which needed neither — added so
   dashboard.js's vehicle switcher can reuse this same class on a real
   <a href="dashboard.html?..."> link without it rendering as a default
   blue underlined link. Inert for the existing button usage. */
.vehicle-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  width: 100%;
  text-align: left;
  color: inherit;
  text-decoration: none;
  background: var(--card-bg);
  border-radius: var(--radius-lg);
  border: 0.5px solid var(--border-box);
}
.vehicle-card .icon-wrap,
.pairing-toggle .icon-wrap {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-teal-soft);
  color: var(--color-teal);
  flex-shrink: 0;
}
/* 2026-10-04, Bob's ask: every former purple icon (unresolved MOT advisories, the old trailer-tent type, pairing) is now the app's green. */
.vehicle-card .icon-wrap.purple, .pairing-toggle .icon-wrap.purple { background: var(--color-own); color: var(--on-primary); }
.vehicle-card .icon-wrap.coral, .pairing-toggle .icon-wrap.coral { background: var(--color-coral-soft); color: var(--color-coral); }
/* .mods/.own/.run (2026-09-12, Bob's ask, dashboard vehicle switcher
   only) — motorhome/campervan, caravan/pairing, and tow car icons there
   respectively. Unlike .purple/.coral above (a pale "-soft" tint behind
   a matching darker icon of the same hue), these use the SOLID
   --color-mods/-own/-run brand colour as the background — the same
   colours "Vehicle modifications"/"Cost to own"/"Cost to run" use
   elsewhere on this page — so the icon itself needs an explicit colour
   rather than a same-hue shade: --on-primary (white in light mode,
   near-black in dark mode), the same token #logCostBtn/#printBtn already
   use for exactly this pairing. Named after what they mean (not the
   colour itself, unlike .purple/.coral) since --color-own/-run/-mods
   already carry their own per-theme values — see this file's own
   comments on those tokens further up. Deliberately separate classes
   from .purple/.coral rather than repainting them: those two are still
   used elsewhere (trailer/trailer_tent here, and every icon on
   vehicle-selector.js's Vehicles page), which this request didn't ask to
   change. */
.vehicle-card .icon-wrap.mods, .pairing-toggle .icon-wrap.mods { background: var(--color-mods); color: var(--on-primary); }
.vehicle-card .icon-wrap.own, .pairing-toggle .icon-wrap.own { background: var(--color-own); color: var(--on-primary); }
.vehicle-card .icon-wrap.run, .pairing-toggle .icon-wrap.run { background: var(--color-run); color: var(--on-primary); }
.vehicle-card .icon-wrap.trip, .pairing-toggle .icon-wrap.trip { background: var(--color-trip); color: var(--on-primary); }
.vehicle-card .meta, .pairing-toggle .meta { flex: 1; min-width: 0; }
.vehicle-card .name, .pairing-toggle .name { font-size: 20px; font-weight: 600; }
.vehicle-card .type, .pairing-toggle .type { font-size: 17px; color: var(--text-secondary); margin-top: 2px; }
.vehicle-card .amount { font-size: 20px; font-weight: 600; flex-shrink: 0; }
/* "Now Sold" badge (2026-08-29) — shown next to a sold vehicle's name on
   the vehicle selector list. Uses the same coral tint as a tow car's icon
   elsewhere on this screen, just as a small pill rather than an icon
   background. */
.sold-badge { display: inline-block; font-size: 14.5px; font-weight: 600; padding: 2px 8px; border-radius: 999px; background: var(--color-coral-soft); color: var(--color-coral); margin-left: 8px; vertical-align: middle; }

.pairing-group {
  background: var(--card-bg);
  border-radius: var(--radius-lg);
  border: 0.5px solid var(--border-box);
  overflow: hidden;
}
.pairing-toggle {
  width: 100%;
  border: none;
  border-radius: 0;
  background: transparent;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  text-align: left;
}
.pairing-options { border-top: 0.5px solid var(--border); }

/* Vehicle selector — manage (edit pairing / delete) */
.vehicle-row { display: flex; align-items: center; gap: 8px; }
.vehicle-row > .vehicle-card, .vehicle-row > .pairing-group { flex: 1; width: auto; min-width: 0; }
.manage-btn {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
}
.manage-panel { margin-top: 6px; }
.manage-panel .field:last-child { margin-bottom: 0; }
/* color/text-decoration (2026-09-12) — same reasoning as .vehicle-card
   above: vehicle-selector.js only ever uses .subopt as a <button>, but
   dashboard.js's vehicle switcher reuses it on real <a href="..."> links
   too, which need the reset to avoid rendering as default blue
   underlined links. Inert for the existing button usage. */
.subopt {
  width: 100%;
  border: none;
  border-radius: 0;
  background: transparent;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px 11px 52px;
  text-align: left;
  color: inherit;
  text-decoration: none;
  font-size: 17px;
}
.subopt + .subopt { border-top: 0.5px solid var(--border); }

.empty-state {
  text-align: center;
  padding: 32px 16px;
  color: var(--text-secondary);
  font-size: 18.5px;
}

.primary-btn {
  background: var(--fill-primary);
  color: var(--on-primary);
  border: none;
  padding: 11px 16px;
  font-size: 18.5px;
  font-weight: 600;
  width: 100%;
}

/* .primary-btn on an <a> tag (2026-09-18, Bob's ask, same root cause and
   same fix as a.ghost-btn just above/below in this file — see that
   comment for the full story) — border-radius lives on the plain
   `button` element rule, which an <a> never receives, so a link styled
   as .primary-btn (reset-password.js's "Go to log in", upgrade.js's
   "Email to upgrade", help.html's "Email support") had square corners
   while every real <button class="primary-btn"> had rounded ones.
   Everything else .primary-btn needs (background, colour, border: none,
   padding, font) is already set directly on the class itself, so
   border-radius is the only thing missing here. */
a.primary-btn { border-radius: var(--radius); }

/* a.primary-btn's hover TEXT colour (2026-09-27, Bob's ask, spotted on
   help.html's "Email support" button) — same three links as the
   border-radius fix just above (reset-password.js's "Go to log in",
   upgrade.js's "Email to upgrade", help.html's "Email support") were also
   quietly picking up the base `a:hover { color: var(--fill-primary); ... }`
   rule further up this file (specificity 0,1,1) over .primary-btn's own
   `color: var(--on-primary)` (0,1,0) — since a coral link on a coral
   button background reads as "goes orange on hover", almost unreadable.
   Bob's ask: hover text should be white in dark mode, black in light
   mode — exactly --text-primary's own light/dark mapping, not
   --fill-primary. The button's own slight 0.8-opacity fade on hover
   (from that same base a:hover rule) is left alone — only the text
   colour needed fixing. */
a.primary-btn:hover { color: var(--text-primary); }

/* #logCostBtn (2026-09-10, Bob's ask, settled after a few rounds —
   see CLAUDE.md's "Log a Cost" history for the back-and-forth) — was
   --color-own (the "cost to own" green). Text colour: white in light
   mode, near-black in dark mode — done via var(--on-primary) rather than
   hardcoded values, since --on-primary already happens to be exactly
   those two colours per theme.
   2026-09-16, Bob's ask: "can both the Quick Add Fuel and Log a cost
   buttons be changed to the coral colour with the text white" — same
   day as the top-bar "+"/.add-action-btn coral+white change above, so
   this now matches that same look. background switched from
   --color-own to --color-coral (matching .fuel-btn's own identical
   switch directly below — "so both buttons look similar", per Bob's
   own wording) — color stays var(--on-primary), UNCHANGED, since that
   token already resolves to white in light mode and near-black in dark
   mode, exactly the working pattern already proven for #printBtn above
   and for the top-bar "+"/.add-action-btn buttons today. White-on-
   light-coral computes to ~4.4:1 (checked when coral was first
   introduced today — comfortably clear); near-black-on-dark-coral is
   ~7.2:1. --color-own is unaffected elsewhere (still used for the
   "cost to own" stat card/category bars/etc. — this button just no
   longer matches that category's own colour, which is fine since it's
   a general action button, not a cost-to-own figure). */
#logCostBtn {
  background: var(--color-coral);
  color: var(--on-primary);
}

/* #printBtn (2026-09-12, Bob's ask) — the Service History page's
   "Print / Save as PDF" button. Background is --color-run (the "cost to
   run" amber/gold), in both light AND dark mode — see that token's
   comment further up this file for its two values (#C08A1E light /
   #F1B136 dark). Text colour: white in light mode, black in dark mode,
   via var(--on-primary) — same reasoning and same token as #logCostBtn
   above (it's normally paired with --fill-primary, not --color-run, but
   its per-theme VALUES — white light / near-black dark — are exactly
   what Bob asked for here too).
   ACCESSIBILITY NOTE for whoever edits this next: white-on-light-amber
   computes to 3.05:1 contrast — below the 4.5:1 AA guideline for normal
   text, and only just above the 3:1 floor that applies to large/bold
   text (this button's text is bold, similar size to #logCostBtn's own
   borderline case above). Flagged to Bob rather than silently shipped;
   he can ask for a darker shade of --color-run in light mode, or a
   different text colour, if he'd rather have a stronger margin here.
   Dark mode's black-on-#F1B136 is comfortably fine at 10.2:1. */
#printBtn {
  background: var(--color-run);
  color: var(--on-primary);
}

.ghost-btn {
  width: 100%;
  padding: 11px 16px;
  font-size: 17px;
  font-weight: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

/* .ghost-btn on an <a> tag (2026-09-18, Bob's ask on reminders-hub.js's
   "Manage" link — "can the manage link be styled exactly like the mark
   as done tab") — .ghost-btn itself only sets LAYOUT (width, padding,
   flex centring etc.); the actual button chrome — border, background,
   text colour, cursor, hover — comes from the plain `button` element
   rule further up this file, which an <a> never gets (it was instead
   picking up the plain `a { color: var(--color-teal); }` link-colour
   rule, so "Manage" looked like a bare teal link floating in the row
   rather than a proper bordered button next to "Mark as done"). This
   mirrors `button`'s own chrome onto `a.ghost-btn` specifically, so any
   <a class="ghost-btn"> anywhere in the app — reminders-hub.js's
   "Manage" link included — now looks pixel-for-pixel like its <button>
   siblings. */
a.ghost-btn {
  font-family: inherit;
  border-radius: var(--radius);
  border: 0.5px solid var(--border-box);
  background: var(--card-bg);
  color: var(--text-primary);
  cursor: pointer;
}
a.ghost-btn:hover { border-color: var(--border-strong); color: var(--text-primary); opacity: 1; }

/* .add-action-btn (2026-09-13, Bob's ask: "for all of those buttons can
   you give it the same background colour as the circular orange add
   button in the top nav") — the "+ Add a vehicle/trip/reminder/MOT
   advisory" ghost buttons across the app (vehicle-selector.js's
   addVehicleToggle, trips.js's/trips-hub.js's addTripToggle,
   reminders.js's/reminders-hub.js's addReminderToggle,
   mot-advisories.js's/mot-advisories-hub.js's addAdvisoryToggle) share
   the exact same fill and --on-orange text/icon colour as #tbAddBtn in
   the top bar, so "this is how you add something new" reads the same
   everywhere it appears. Added as a SECOND class alongside .ghost-btn,
   not a change to .ghost-btn itself — that class is also used for
   several unrelated secondary actions (Manage/Mark as done/Show
   dismissed/Remove photo/etc.) that must stay their plain neutral
   bordered look. border:none, matching this app's other solid-fill
   action buttons (.primary-btn/.fuel-btn/#logCostBtn/#printBtn above)
   rather than the plain bordered ghost-btn look.
   2026-09-16, Bob's ask: fill switched from --color-orange to
   --color-coral (both here and on #tbAddBtn below). Text/icon colour
   also revisited the same day: Bob asked for WHITE text specifically
   in light mode (see #tbAddBtn's own comment for the full note and the
   contrast numbers, which actually come out slightly BETTER for white
   than the near-black --on-orange was) — dark mode keeps --on-orange
   unchanged, since Bob's ask was light-mode-only and near-black is
   still clearly the better choice there (white would be a weak 2.4:1).
   --color-orange stays defined above (in case it's wanted again later)
   but isn't used anywhere in the app any more after the 2026-09-16
   change above. */
.add-action-btn {
  background: var(--color-coral);
  color: #fff;
  border: none;
}
/* cost-hub.js's new "+Add a cost" shortcut (2026-09-27) is the first
   .add-action-btn used on an <a> rather than a <button> — without this,
   a.ghost-btn's own background/border override (added 2026-09-18 for
   the "Manage" link fix, specificity 0,1,1) would beat the plain
   .add-action-btn class alone (0,1,0) and the link would render with
   a.ghost-btn's plain card-coloured background instead of coral. Same
   specificity as a.ghost-btn so it wins correctly regardless of source
   order. */
a.add-action-btn {
  background: var(--color-coral);
  color: #fff;
  border: none;
}

/* .trip-action-btn (2026-09-15, Bob's ask: "can the add a trip and
   start trip buttons be made shorter in width so the info icon can sit
   on the same line after the buttons", then refined: "the add a trip
   button sits on one line with the info icon to the right of it and the
   start trip button sits on the line below with the icon to the right
   of it and each button is the same width") — overrides the width:100%
   that .ghost-btn/.primary-btn both set by default, ONLY for "Add a
   trip" and "Track your distance"/"End trip" (trips.js and
   trips-hub.js — see each file's addSection/actionButtonsHtml()).
   Added as a THIRD class alongside .ghost-btn/.add-action-btn or
   .primary-btn, not a change to any of those — they're still
   full-width everywhere else they're used (Add a vehicle/reminder/MOT
   advisory, Save changes, etc.), which Bob didn't ask to change.
   width:auto lets the button size to a CSS grid cell (see
   .trip-action-row below) rather than being stretched by the browser's
   own block-level default — the grid is what actually makes both
   buttons match width, this class just has to get out of its way. */
.trip-action-btn {
  width: auto;
}

/* .trip-action-row — the container around "Add a trip"/"Track your
   distance"/"End trip" and their own ⓘ info buttons, in both
   trips.js/trips-hub.js. A 2-column CSS grid (button column, icon
   column) rather than a flex-wrap row: "Add a trip" + its icon are one
   row, "Track your distance"/"End trip" + its icon are the row below —
   grid's own row-by-row auto-placement gives that for free from plain
   markup order, no explicit row markers needed. The button column is
   `1fr` (2026-09-15, Bob's follow-up: "can all the buttons and icon
   extend to the width the sections above them as they look short now")
   — takes up all the width left over after the icon column's own fixed
   size, so the row as a whole stretches out to the same full width as
   the trip cards/chart cards sitting above it, rather than only being
   as wide as the button text needed. Because each button itself is
   width:auto (see .trip-action-btn above), the default grid stretch
   behaviour then fills every button out to fill that same, now much
   wider, column — so "Add a trip" and "Track your distance"/"End trip"
   still always match each other's width, just a wider one than before. */
.trip-action-row {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: 8px;
  row-gap: 6px;
  align-items: center;
}

/* .trip-action-note (2026-09-15, Bob's ask: "can the add a trip info
   appear below the add a trip button and can the start a trip info
   appear below the start a trip button when their respective icons are
   clicked" — i.e. each ⓘ's own explanation directly under ITS OWN
   button, not both notes lumped together below the whole button row).
   Spans both of .trip-action-row's columns (button + icon) so it reads
   as a full-width line of its own, and — because it's placed in the
   markup right after its own button+icon pair (trips.js/trips-hub.js) —
   CSS Grid's own row-by-row auto-placement puts it on its own new row
   immediately under that pair, pushing whatever comes next in the
   markup (the other button+icon pair, and its own note) down to the
   row(s) after it, with no explicit row numbers needed. */
.trip-action-note {
  grid-column: 1 / -1;
  font-size: 16px;
}

/* "+ Quick Add Fuel" (2026-09-02) — sat opposite the dashboard's "+ Log
   a Cost" button (.primary-btn) in a deliberately different colour so
   the two were easy to tell apart at a glance; used this app's "cost to
   run" category colour (fuel is a variable/cost-to-run category), then
   --color-run once the cost-category colours were split out from the
   general brand teal on 2026-09-10.
   2026-09-16, Bob's ask: "can both the Quick Add Fuel and Log a cost
   buttons be changed to the coral colour with the text white" — the
   two buttons are now DELIBERATELY the same colour (reversing the
   original "different colour so they're easy to tell apart" reasoning
   above — see the renamed "Quick Add Cost" text in dashboard.js for how
   they're still told apart, by label + icon rather than colour) — same
   --color-coral fill as #logCostBtn right above, and color stays
   var(--on-primary) unchanged (already white in light mode / near-black
   in dark mode — see #logCostBtn's own comment for the checked contrast
   numbers, identical here since both buttons share the same fill/text
   tokens now). */
.fuel-btn {
  background: var(--color-coral);
  color: var(--on-primary);
  border: none;
  padding: 11px 16px;
  font-size: 18.5px;
  font-weight: 600;
}

/* Dashboard's own two buttons only (2026-09-26, Bob's ask: "in mobile
   view in the dashboard the quick a[d]d cost text is two line whilst the
   quick add fuel is one line, can we match these so they are the same -
   preferably both on one line"). Renaming "Log a cost" to "Quick Add
   Cost" (2026-09-16, see #logCostBtn's own comment below) already made
   the two labels the same length, so they now wrap the SAME way — but at
   the shared 18.5px .primary-btn/.fuel-btn font-size, both were still
   wrapping onto 2 lines side by side at a phone width, just consistently
   now rather than mismatched. Dropping to 15px (checked directly, not
   guessed) is enough for both "Quick Add Fuel"/"Quick Add Cost" to fit
   on one line, satisfying Bob's stated preference. Scoped by id since
   these two buttons only ever appear on the dashboard — every other
   .primary-btn/.fuel-btn app-wide keeps its original 18.5px size. */
#quickAddFuelBtn, #logCostBtn { font-size: 15px; }

.field { margin-bottom: 12px; }
.field label { display: block; font-size: 16px; color: var(--text-secondary); margin-bottom: 4px; }
.field input, .field select { width: 100%; }

/* Log in / Sign up screen (2026-09-04) — redesigned from the plain
   combined form that used to live here, to match the mockup Bob
   reviewed and approved. The two buttons below are deliberately NOT
   this app's usual --fill-primary/--color-teal: Bob's own explicit
   colour choice for this one screen only is a plain white "Log in" /
   "Create account" button, and a "Sign up" / "Log in" toggle button
   filled in the brand's bright accent teal (#50DEFF) with black text.
   Kept as literal values here (not tokens) so this screen doesn't
   shift if --color-teal is ever retuned again later — this is a
   one-off styling choice for this screen, not a new semantic colour. */
/* 2026-09-09, SECOND REVISION — Bob went back to an icon-only mark (his
   wave logo, no baked-in text) plus real HTML/CSS text built to match a
   styled preview he sent (axleatorhtmllogopreviewv5.html): "AXLE" in a
   bold, tight, blocky face (Orbitron) fused against a lighter "ator"
   (Exo 2), with the wave icon sitting right on the join between the two
   words, and a small letter-spaced tagline underneath. This replaces the
   PREVIOUS revision's approach (a single wide image with the wordmark
   text baked into the PNG itself) — that's why the old
   .login-brand img / .login-brand-name / .login-brand-tagline rules are
   gone; the image is now ONLY the small wave icon
   (assets/logo-icon.png), positioned by .wave-icon below, and the text
   is real, selectable HTML driven by --text-primary so it's automatically
   readable in both light and dark mode (unlike a baked-in-colour image,
   which would need two separate PNGs to do the same).
   Sizes below are Bob's preview file's own clamp() values scaled down to
   fit this app's ~420px-wide mobile card (his preview was a full-width
   desktop marketing page) — see delivery notes for the exact scale-down
   reasoning if this ever needs revisiting.

   2026-09-12, THIRD REVISION — Bob sent over a reference page
   (logo_and_header_examples.html, nine font/logo layout tests he'd put
   together) and asked for "example e8"'s two fonts: "the MACS part to be
   in bold and read AXLE and the UK part to be as it is but read ATOR",
   plus (a follow-up message) "i want the second font to replace the
   current the running cost calculator" — i.e. the tagline below. e8 turns
   out to use exactly ONE font (Orbitron, weight 700, italic) for its
   whole wordmark, so "AXLE" and "ator" are no longer two different font
   families — both now share Orbitron/700/italic below. .axle keeps the
   BOLD half of Bob's wording ("MACS part to be in bold"); .ator keeps the
   SAME font but without extra emphasis of its own — matching e8, where
   .macs/.uk differ only by colour, not weight, and Bob's own wordmark
   already has no colour split to preserve. Bob's "keep the colours i have
   currently" needed no change at all: both spans already shared
   `--text-primary`. Also unified the two spans' font-size/letter-spacing
   (previously 34px/-0.03em vs 37px/-0.04em, back when they were two
   visually-different typefaces that needed separate tuning) down to one
   shared pair, since a single font at two different sizes/spacings for
   "AXLE"+"ator" looked like a mismatch rather than one wordmark. The
   tagline (.brand-tagline further down) is untouched — it already used
   Exo 2, which is Bob's "second font" from this request; see the
   @font-face comment above for the font-FILE side of this change. */
.login-brand { margin-bottom: 40px; }
.axleator-logo { display: inline-flex; flex-direction: column; align-items: flex-start; }
/* 2026-10-14: the login-screen logo links to landing.html */
.logo-link { display: inline-block; text-decoration: none; color: inherit; transition: opacity .15s ease, transform .15s ease; }
.logo-link:hover, .logo-link:focus-visible { opacity: .8; transform: scale(1.03); }

.wordmark {
  display: flex;
  align-items: baseline;
  white-space: nowrap;
  line-height: 0.86;
  color: var(--text-primary);
}
/* 2026-09-12, FOURTH REVISION — Bob asked for one half of the wordmark to
   have its own colour, separate from the other. First ask was "AXLE":
   "in light mode can the letters AXLE be the same colour as the text
   below - the running cost calculator. in dark mode can the letters AXLE
   be this colour - #e4d9d9" — then he asked to swap which half gets it:
   "can we reverse that so it's the letters ATOR that get the colour
   changes instead". So it's now .ator carrying the colour treatment and
   .axle left plain. "The text below" is .brand-tagline, further down,
   which is `var(--text-secondary)` — so .ator's light-mode colour is
   that same token (it'll always match the tagline even if
   --text-secondary is ever retuned again). Dark mode is a literal
   one-off hex Bob gave directly, not a token, so it's overridden
   explicitly below rather than run through --text-secondary's own dark
   value — same override pattern already used elsewhere in this file for
   an explicit dark-mode style (see .theme-word-dark above). "AXLE" is
   plain — inherits .wordmark's `var(--text-primary)`, exactly as before
   this revision. */
.axle {
  font-family: "Orbitron", sans-serif;
  font-size: 34px;
  font-weight: 700;
  font-style: italic;
  letter-spacing: -0.03em;
  text-transform: uppercase;
}
.ator {
  font-family: "Orbitron", sans-serif;
  font-size: 34px;
  font-weight: 700;
  font-style: italic;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  margin-left: 0.015em;
  color: var(--text-secondary);
}
/* .ator's dark-mode colour override (2026-09-12, see the FOURTH REVISION
   comment above .axle) — Bob's own literal hex, #e4d9d9, not a token.
   Same guarded pair used elsewhere in this file for an explicit dark
   style: the media-query block covers the device/browser dark-mode
   setting (only when the app itself hasn't been explicitly set to
   "Light"), and the plain :root[data-theme="dark"] rule covers an
   explicit in-app "Dark" choice. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ator { color: #e4d9d9; }
}
:root[data-theme="dark"] .ator { color: #e4d9d9; }
/* Zero-width anchor sitting exactly on the "e"/"a" join, same technique
   as Bob's preview file — .wave-icon is positioned absolute against this,
   not against .wordmark itself, so it lines up on the letter join
   regardless of how wide "Axle" ends up rendering. */
.ea-join { position: relative; width: 0; height: 0; display: inline-block; align-self: baseline; }
.wave-icon {
  position: absolute;
  width: 35px;
  height: auto;
  /* 2026-09-09: Bob's final positioning, confirmed after fixing the missing
     "px" unit bug (see git history/progress-log.md if this ever needs
     retracing) — left/bottom together park the icon just to the right of
     centre, sitting close above the letters rather than floating high
     above them. */
  left: 3px;
  bottom: 19px;
  transform: translateX(-50%);
  pointer-events: none;
  user-select: none;
  filter: drop-shadow(0 2px 2px rgba(0,0,0,.18));
}
.brand-tagline {
  margin-top: 6px;
  margin-left: 0.06em;
  font-family: "Exo 2", sans-serif;
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.login-primary-btn {
  width: 100%;
  background: #FFFFFF;
  color: #1B1B18;
  border: 0.5px solid var(--border-strong);
  padding: 12px 16px;
  font-size: 19px;
  font-weight: 600;
}
.login-secondary-btn {
  width: 100%;
  /* Changed 2026-09-18 (Bob's ask: "the buttons that are blue... change
     all of those buttons that are blue to the coral colour") — was
     #50DEFF, a literal copy of --color-teal's dark-mode value used
     deliberately as a FIXED colour in both light and dark mode (this
     login button, like .login-primary-btn above, doesn't follow the
     app's normal light/dark colour-scheme switching). Same idea here:
     a fixed coral, specifically --color-coral's own dark-mode value
     (#E8916B — a lighter, more pastel shade than the light-mode coral)
     since that's what pairs correctly with this button's unchanged dark
     (#1B1B18) text — the light-mode coral is too dark for that same
     text to stay readable on it. */
  background: #E8916B;
  color: #1B1B18;
  border: none;
  padding: 11px 16px;
  font-size: 18.5px;
  font-weight: 600;
}
/* Slightly larger, white-in-dark-mode/dark-grey-in-light-mode, no
   underline (2026-09-05, Bob's request). Uses --text-secondary rather
   than a fixed colour deliberately — that variable already resolves to
   a dark grey in light mode and plain white in dark mode (see the
   dark-mode blocks near the top of this file), so this automatically
   stays readable in both themes without needing its own new variable. */
.login-footer { text-align: center; font-size: 17px; color: var(--text-secondary); margin-top: 24px; }
.login-footer a { color: var(--text-secondary); text-decoration: none; }

/* Login screen's own Email/Password fields: Bob asked for these to
   always have a white background (2026-09-04), regardless of light or
   dark mode — previously they followed the app's normal --surface-1
   token, which is dark in dark mode like every other field in the app.
   Scoped to #authGate only, so no other screen's fields are affected. */
#authGate input {
  background: #FFFFFF;
  color: #1B1B18;
}
/* These fields' background is ALWAYS white (see comment above), so they
   need their own placeholder override too (2026-09-04) — otherwise the
   dark-mode --placeholder colour (a light grey, chosen for the dark
   gradient elsewhere in the app) would apply here as well and be nearly
   invisible against this field's own permanently-white background. */
#authGate input::placeholder { color: #8C8B84; opacity: 1; }
#authGate .password-toggle-btn { color: #5C5C57; }
#authGate .password-toggle-btn:hover { color: #1B1B18; }

/* Chrome (and most other browsers) paints its OWN light blue/yellow
   tint over a field once it's autofilled from a saved password, no
   matter what background colour the page itself sets — this is what
   Bob saw on the live staging site once his email/password were
   filled in from his saved Chrome login. The background-color above
   can't override that on its own; browsers only let you override
   autofill styling via this specific inset-box-shadow trick (painting
   a large white shadow inside the field, which visually replaces the
   browser's own tint), plus forcing the text colour with
   -webkit-text-fill-color since autofill also overrides plain `color`. */
#authGate input:-webkit-autofill,
#authGate input:-webkit-autofill:hover,
#authGate input:-webkit-autofill:focus,
#authGate input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0 1000px #FFFFFF inset !important;
  -webkit-text-fill-color: #1B1B18 !important;
  caret-color: #1B1B18;
}

/* Show/hide password eye toggle (2026-08-30) */
.password-field-wrap { position: relative; }
.password-field-wrap input { padding-right: 38px; }
.password-toggle-btn {
  position: absolute;
  right: 2px;
  top: 50%;
  transform: translateY(-50%);
  border: none;
  background: none;
  padding: 6px;
  color: var(--text-secondary);
  display: flex;
}
.password-toggle-btn:hover { border-color: transparent; color: var(--text-primary); }

.card {
  background: var(--card-bg);
  border-radius: var(--radius-lg);
  border: 0.5px solid var(--border-box);
  padding: 18px;
}

.error-text { font-size: 17px; color: var(--text-danger); margin-top: 6px; }
.muted-text { font-size: 17px; color: var(--text-secondary); }

/* Combined length box (2026-08-30) — sits under a paired vehicle's own
   Length field in the manage panel, so it reads as its own clearly
   separate result rather than being squeezed in elsewhere on the panel.
   Originally used the same pale purple tint as a caravan/trailer pairing
   icon elsewhere on this screen (a soft background + purple text).
   Changed 2026-09-27 (Bob's ask, quoting this box's own "Combined
   length"/"Enter a length above..." copy directly) to a SOLID
   --color-run (amber/gold — the "cost to run" brand colour) fill
   instead, with --on-primary text throughout (white in light mode,
   black in dark mode, per Bob's own explicit split) — the same
   solid-fill-plus-on-primary-text convention already used for
   #logCostBtn/#printBtn and the vehicle-switcher's own icon-wraps,
   rather than the old pale-tint-plus-coloured-text style. Applies to
   every text element in the box (label, the combined figure itself, and
   the "vehicle + vehicle" / "enter a length..." captions) since all of
   them now sit on a solid colour background, not just the one line Bob
   quoted. */
.combined-length-box {
  background: var(--color-run);
  border-radius: var(--radius);
  padding: 10px 14px;
  margin: -4px 0 16px;
}
.combined-length-label { font-size: 16px; font-weight: 600; color: var(--on-primary); }
.combined-length-value { font-size: 26.5px; font-weight: 700; margin-top: 2px; color: var(--on-primary); }
.combined-length-note { font-size: 16px; color: var(--on-primary); margin-top: 2px; }
.combined-length-box-empty .combined-length-note { color: var(--on-primary); }

/* Dashboard */
/* .tabs-group (2026-09-10, Bob's ask: "add a border but keep the
   background as it is... i just want the 6 tabs to appear grouped
   together") — wraps both toggle rows (#periodToggle/#metricToggle,
   6 buttons total) in one bordered box so they read as a single grouped
   control instead of two rows floating loose on the page. Border colour
   and radius reuse the exact tokens `.card` uses elsewhere on this page
   (var(--border-box) as of 2026-09-27's "slightly darker grey box
   borders" pass, var(--radius-lg)) — "in keeping with the rest of the
   page", per Bob's own wording — but deliberately no `background` here:
   Bob was explicit that the background should stay as it already is,
   so each button inside keeps doing its own thing (transparent at rest,
   --tab-bg on whichever one is active — see render() in dashboard.js),
   with nothing new filled in behind them. */
.tabs-group {
  border: 0.5px solid var(--border-box);
  border-radius: var(--radius-lg);
  padding: 10px;
  margin-bottom: 16px;
}
.tabs-group .view-toggle:last-child { margin-bottom: 0; }

.view-toggle { display: flex; gap: 6px; margin-bottom: 16px; }
.view-toggle button { flex: 1; font-size: 17px; padding: 7px; }
/* Dashboard's own two toggle rows only (2026-09-16, Bob's ask: "can the
   6 tabs below the quick add buttons all be the same size as the top
   row. the bottom row is increased because of per night away") —
   .view-toggle is shared by many other toggle rows across the app (the
   Trips hub's By vehicle/All trips, Settings' Light/Dark/Match device,
   Quick Add's Cost to own/Cost to run, etc.), which weren't part of
   this ask and keep the original 17px/7px sizing — this override is
   scoped to just #periodToggle/#metricToggle's own buttons.
   Root cause, found by measuring rather than guessing: whichever tab is
   SELECTED renders bold (font-weight:600, see render() in
   dashboard.js's own 2026-09-10 accessibility addition), and bold
   "This month" needs ~92px at the original 17px/7px sizing — more than
   fits in one of these 3-column buttons' ~83px interior at this app's
   own standard 390px phone width, so it silently wrapped to 2 lines
   there too (just not always as far as "Per night away" did, so the
   two rows didn't always visibly disagree). Bob only noticed the
   bottom-row case because "Per night away" is this row's longest
   label, but shrinking text/padding ONLY on that one row would have
   just swapped which row was taller rather than truly fixing it — so
   this applies to both rows' buttons together, checked with EVERY tab
   in turn as the bold/selected one, not just the two labelled in Bob's
   message. See dashboard.html's own "Per night" label for the other
   half of this fix. */
#periodToggle .viewbtn, #metricToggle .viewbtn { font-size: 14px; padding: 7px 2px; }

/* Settings — Appearance (Match device/Light/Dark) and Units (Miles/Kilometers)
   tabs sized to match the dashboard's own 3-across toggles above (2026-09-26,
   Bob's ask: "can we the 3 appearance tabs... to be the same size as the This
   Month This Year and All time tabs in the dashboard"). Same font-size/padding
   values, scoped to these two Settings toggles only — every other .view-toggle
   in the app (Trips hub's By vehicle/All trips, Quick Add's Cost to own/Cost to
   run, etc.) keeps its original, larger sizing, unchanged. */
#themeToggle .viewbtn, #unitsToggle .viewbtn { font-size: 14px; padding: 7px 2px; }

/* .headline-card/.headline-label/.headline-value/.headline-sub retired
   2026-09-10 — Bob asked for the dashboard's big "Total cost this
   month/this year/all time" hero card (also used for the Per mile/Per
   night away views) to become the same size as the "Total cost of
   ownership" card below it, so dashboard.js now renders it with the
   plain .card.stat-card/.stat-title/.stat-value/.stat-detail classes
   instead — identical markup shape to every other stat card on this
   page, nothing bespoke left to style here. A generic .card.clickable
   rule replaces the old .headline-card.clickable one, since the "tap to
   manage trips" hover behaviour isn't specific to this one card. */
.card.clickable { cursor: pointer; }
.card.clickable:hover { border-color: var(--border-strong); }

.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 20px; }
.stat-card { padding: 14px; }
.stat-card .stat-title { font-size: 17px; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; }
.stat-card .stat-value { font-size: 27.5px; font-weight: 600; margin-top: 4px; }
.stat-card .stat-value .unit { font-size: 17px; color: var(--text-muted); font-weight: 400; }
.stat-card .stat-detail { font-size: 16px; color: var(--text-muted); margin-top: 2px; }
.stat-card.own { color: var(--color-own); }
.stat-card.use { color: var(--color-run); }
.stat-card.mod { color: var(--color-mods); }
.stat-card.trip { color: var(--color-trip); }

.section-title { font-size: 18.5px; font-weight: 600; margin-bottom: 10px; }
/* "vs last month/year" sub-line under the category section title
   (2026-09-10) — only shown when there's a previous period to compare
   against (This month / This year, not All time — see dashboard.js's
   `previousPeriodEntries()`). */
.section-sub { font-size: 15px; color: var(--text-muted); margin-top: -4px; margin-bottom: 12px; }
.cat-row { margin-bottom: 8px; }
.cat-row-top { display: flex; justify-content: space-between; font-size: 17px; margin-bottom: 3px; }
.cat-row-top .cat-amount { color: var(--text-secondary); }
/* Dashboard category breakdown, wrapped in a real <table> (2026-09-10,
   Bob's ask — see dashboard.js's own comment on barsHtml for the full
   reasoning). Two <tr>s per category: name+amount as genuine table
   columns, so every amount lines up at the same horizontal position no
   matter how long the category name above it is (flexbox's
   space-between, used above for .cat-row-top, can't guarantee that);
   then a second row spanning both columns for the bar/marker/foot-note.
   Generous padding plus a divider line under each category's second row
   are the "clearer to read" spacing Bob asked for. These are NEW
   classes, not a repurposing of .cat-row/.cat-row-top above — those stay
   exactly as they were for trips.js's simpler plain-<div> category
   breakdown (no comparison bars, no table) — see that file's
   tripCategoryBreakdownHtml(). */
.cat-table { width: 100%; border-collapse: collapse; }
.cat-name-cell { padding: 14px 8px 4px 0; font-size: 17px; text-align: left; }
.cat-amount-cell { padding: 14px 0 4px 8px; font-size: 17px; text-align: right; white-space: nowrap; color: var(--text-secondary); }
.cat-table tr:first-child td { padding-top: 0; }
.cat-bar-cell { padding: 0 0 18px; border-bottom: 1px solid var(--border); }
.cat-table tr:last-child .cat-bar-cell { border-bottom: none; padding-bottom: 4px; }
.cat-label { display: flex; align-items: baseline; gap: 7px; }
/* The "vs last month/year" delta badge next to a category's label
   (2026-09-10). Bob's explicit call: spending MORE than the previous
   period is always worth flagging red, even for a one-off cost like a
   service bill — so `.delta.up` is red whatever the category or size
   of the increase, never conditional. A decrease or no change is a
   quieter grey, deliberately never green (green has no meaning in this
   app's colour system, and using it here would risk reading as though
   it were the "cost to run" category colour). */
.delta { font-size: 14.5px; font-weight: 600; }
.delta.up { color: var(--text-danger); }
.delta.down { color: var(--text-muted); font-weight: 500; }
.delta.flat { color: var(--text-muted); font-weight: 500; }
.cat-track { position: relative; margin-top: 8px; height: 8px; border-radius: 4px; background: var(--track-bg, var(--surface-2)); }
.cat-fill { height: 100%; border-radius: 4px; }
.cat-fill.use { background: var(--color-run); }
.cat-fill.own { background: var(--color-own); }
.cat-fill.mod { background: var(--color-mods); }
.cat-fill.trip { background: var(--color-trip); }
/* (2026-09-12, Bob's ask: removed the .prev-marker hourglass shape that
   used to sit on a category's track at the previous period's spend
   position, plus its matching .cat-legend .swatch legend key — Bob felt
   it wasn't adding anything over the delta badge (▲/▼ + %) next to the
   category name and the "£X last month" caption already shown below the
   bar, both of which say the same thing more clearly. The comparison
   itself (the delta badge, the "vs last month" bars, the two legend
   lines explaining the triangle colours below) is unchanged — only the
   position-marker shape and its own legend line are gone. */
.cat-foot { font-size: 14.5px; color: var(--text-muted); margin-top: 4px; }
.cat-legend { display: flex; align-items: center; gap: 6px; font-size: 14.5px; color: var(--text-muted); margin-top: 14px; }
/* Adjacent legend lines (2026-09-10) — dashboard.js emits two
   .cat-legend lines back to back (explaining the red/grey up/down
   triangle colours) — this tightens the gap between them so they read
   as one small legend block rather than two separate paragraphs,
   without touching the normal spacing the FIRST .cat-legend line
   already has above it (margin-top:14px, from the category rows).
   (2026-09-12: originally three lines, the first being the .prev-marker
   key — that line and the marker shape itself were removed, see the
   comment further up this file; this rule still applies unchanged to
   the two remaining lines.) */
.cat-legend + .cat-legend { margin-top: 4px; }
/* Theme-aware wording (2026-09-10) — the down-triangle legend line
   calls its colour "grey", which is only true in light mode: --text-muted
   (used by .delta.down, see above) is a mid grey in light mode but goes
   to plain white in dark mode (Bob's 2026-09-04 "make other text white
   in dark mode" change — see that block further up this file). Rather
   than hardcode one word, dashboard.js renders BOTH ("grey" and
   "white") wrapped in these two classes, and CSS shows only the one
   that matches the active theme — same light/dark selector pattern as
   the colour tokens themselves (prefers-color-scheme, guarded so an
   explicit in-app "Light" choice always wins over the device setting;
   then the explicit "Dark" override last). Checked separately: the
   actual ▼ triangle glyph needs no colour change of its own — it
   already inherits --text-muted, which is already plain white in dark
   mode, confirmed by screenshot. */
.theme-word-dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-word-light { display: none; }
  :root:not([data-theme="light"]) .theme-word-dark { display: inline; }
}
:root[data-theme="dark"] .theme-word-light { display: none; }
:root[data-theme="dark"] .theme-word-dark { display: inline; }
.cat-row.clickable { cursor: pointer; }
/* Targets .cat-label-text specifically (2026-09-10, previously
   `.cat-row-top span:first-child`) so hovering a row only underlines
   the category label itself, not the "vs last month" delta badge that
   now sits alongside it inside the same first-child span. */
.cat-row.clickable:hover .cat-label-text { text-decoration: none; }
.cat-row.clickable:hover .link-underline::after { transform: scaleX(1); }
.cat-label-text.link-underline { font-size: 17px; }

/* Cost history / edit entry */
.entry-row { margin-bottom: 8px; }
.danger-btn {
  width: 100%;
  padding: 11px 16px;
  font-size: 17px;
  font-weight: 600;
  color: var(--text-danger);
  border-color: var(--text-danger);
}

/* Quick add */
.amount-row { text-align: center; margin-bottom: 20px; }
.amount-row .amount-caption { font-size: 17px; color: var(--text-secondary); margin-bottom: 4px; }
.amount-row .amount-line { display: flex; align-items: baseline; justify-content: center; gap: 4px; }
.amount-row .currency { font-size: 37px; color: var(--text-secondary); }
.amount-input {
  font-size: 47.5px;
  font-weight: 600;
  border: none;
  background: transparent;
  width: 180px;
  text-align: center;
  padding: 0;
  color: var(--text-primary);
}
.amount-input:focus { outline: none; }
/* 2026-10-03: quick-add's smaller Amount figure (Bob's ask) */
.amount-row.compact { margin-bottom: 14px; }
.amount-row.compact .currency { font-size: 25px; }
.amount-row.compact .amount-input { font-size: 29px; width: 140px; }

/* 2026-09-13 bug fix, Bob's report: "the category tabs on the right
   extent too far on desktop and in mobile and in mobile the right side
   is obscured when you land on the page." Root cause: a bare `1fr` grid
   track has an IMPLICIT minimum width of `auto` (its content's own
   min-content size), not zero. Several of these buttons' labels are a
   single unbreakable word ("Modifications", "Subscriptions",
   "Servicing") — the button's own min-content width is driven by that
   one word, which comes out wider than a true 1/4 share of .app-shell's
   ~358px-wide content column. Since EVERY column hits this at the same
   width, all four columns get stretched to fit their widest single-word
   button, and the row ends up ~20-80px wider than its container on
   every screen size (confirmed with real measurements — the 4th
   column's buttons were spilling past the app-shell's right edge on
   both a 390px phone width AND a 1440px desktop width, since
   .app-shell's own content column is a fixed ~420px regardless of
   window size — see its own comment further up this file). Fixed with
   the standard CSS Grid fix for exactly this: `minmax(0, 1fr)` instead
   of a bare `1fr` sets each column's minimum to zero instead of its
   content's natural size, so a column is free to actually shrink to
   fit — combined with `min-width: 0` on the button itself (overriding
   the flex/grid item default of `min-width: auto`, the other half of
   this same well-known sizing gotcha). Purely a sizing fix — the
   4-column icon-grid look itself is unchanged, still the fast two-tap
   category picker from this app's original quick-add design.

   2026-09-15, Bob's follow-up: the very first fix above used
   `overflow-wrap: break-word` to stop a too-wide single word from
   forcing its button wider — it worked (no more overflow), but the
   trade-off was ugly: `break-word` is allowed to split a word ANYWHERE
   it needs to, mid-letter, with no hyphen, e.g. "Subscrip-" / "tions".
   Bob's ask was specifically to stop that. Real per-word measurements
   against each button's actual content width (75.5px at 390px/phone
   width, the narrowest case — this app's `.app-shell` column is the
   same ~420px cap on desktop, so this is the binding constraint at
   every screen size) showed exactly which category labels' single
   longest word didn't fit at the original 14.5px: "Maintenance" (83px),
   "Modifications" (84.6px), "Subscriptions" (87px, from "Security
   Subscriptions"), and worst of all "Membership(s)"/"Subscription(s)"
   (96.7px each, from "Club Membership(s)"/"App Subscription(s)") — all
   comfortably over the 75.5px available. Dropping the font size alone
   to fit the worst case (Membership(s)/Subscription(s)) needed ~10.5px
   — a bigger cut than felt right for a page whose own history is
   several deliberate PASSES making text bigger for readability (see
   this file's own "make all text larger" comments elsewhere). So this
   uses a smaller, safer combination instead: font-size down a more
   modest amount, to 12px (every other word above already fits
   comfortably at 12px with 3.5px+ to spare) plus two of quick-add.js's
   16 tile labels shortened, on this grid only, to drop just their
   trailing "(s)" — "Club Memberships"/"App Subscriptions" instead of
   "Club Membership(s)"/"App Subscription(s)" (`TILE_LABEL_OVERRIDES` —
   the full "Club Membership(s)"/"App Subscription(s)" wording is
   UNCHANGED everywhere else in the app: the category dropdown, Cost
   History filter, dashboard, service history, CSV export, etc. — only
   this one compact tile needed the shorter form). Bob's first version
   of this fix (same day) shortened these two tiles further still,
   dropping the "Club "/"App " prefix word entirely down to a single
   word each ("Memberships"/"Subscriptions") — Bob then asked for that
   prefix word back, wrapping onto its own first line the same way
   "Security Subscriptions" already wraps onto two lines elsewhere in
   this grid, which is what the current, shorter-only-by-"(s)" override
   above now gives: "Club" / "Memberships" and "App" / "Subscriptions".
   `overflow-wrap`/`word-break` are both left at their normal defaults
   now — with every real word guaranteed to fit its column at 12px,
   normal wrapping (which only ever breaks BETWEEN words, e.g.
   "Maintenance" / "& Servicing" on two lines) can never need to split
   one, so there's nothing left for `break-word` to do except risk an
   ugly break it no longer needs to make. Verified with real Playwright
   measurements (not eyeballed) that no word, in any of the 16 real
   tiles, ever renders across two different lines — see CLAUDE.md's
   dated entry for the full verification. */
.cat-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-bottom: 16px; }
.catbtn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 12px 4px;
  font-size: 14.5px;
  background: var(--card-bg);
  min-width: 0;
}
.catbtn span { text-align: center; max-width: 100%; font-size: 13px; }
.catbtn.selected { background: var(--tab-bg); border-color: var(--border-strong); }
/* Cost-type tinting (2026-09-13, Bob's ask: "the tabs themselves once
   selected should have the theme colours for modifications, cost to
   run and cost to own as their background colour according to the type
   of cost they are") — layered on TOP of .catbtn.selected via higher
   specificity (3 classes vs. 2), so a category with a known cost type
   (every one except "Other" — see categories.js) shows that colour
   once picked, instead of the plain neutral selected look every button
   used to share. Same solid-fill + --on-primary (white in light mode,
   near-black in dark mode) pairing already used for the dashboard
   vehicle switcher's own .icon-wrap.mods/.own/.run (2026-09-12) and
   #logCostBtn/#printBtn — border-color: transparent since a solid fill
   makes the plain neutral border look wrong against it. "Other" has no
   fixed cost type of its own (costType: null) until quick-add.js's own
   Cost to own/Cost to run choice further down the page is answered —
   applySelectedTint() in quick-add.js adds tint-own/tint-run once that
   choice is made, live, without needing to reselect the category tile;
   until then "Other" just keeps the plain .catbtn.selected look above. */
.catbtn.selected.tint-mods { background: var(--color-mods); color: var(--on-primary); border-color: transparent; }
.catbtn.selected.tint-own { background: var(--color-own); color: var(--on-primary); border-color: transparent; }
.catbtn.selected.tint-run { background: var(--color-run); color: var(--on-primary); border-color: transparent; }
.catbtn.selected.tint-trip { background: var(--color-trip); color: var(--on-primary); border-color: transparent; }

.details-toggle {
  width: 100%;
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.saved-msg {
  text-align: center;
  font-size: 17px;
  color: var(--text-success);
  margin-top: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

/* Inline confirmation message, made "more obvious" (2026-09-27) — Bob's
   own correction of a much bigger original ask: "actually cancel the
   thank you message but with all inline messages after a email button
   is pressed can we make them more obvious." No new "thank you" screen,
   no new navigation link — just a visual-prominence upgrade to the
   EXISTING inline success text that already shows after a forgot-
   password/resend-verification/change-email/request-deletion action
   (auth.js, vehicle-selector.js, settings.js, delete-my-data.js).
   Deliberately NOT --text-success — that token is still literally the
   same blue value as --color-teal (see its own comment further up this
   file), and using it here would have quietly reintroduced the exact
   colour Bob just asked to remove from links elsewhere in this same
   request. --color-own (green) is used instead — the app's existing
   "positive/confirmed" accent (cost-to-own figures, the Log a Cost
   button) — with a soft matching background so the message reads as a
   small callout rather than a plain line of text. Only ever applied to
   a SUCCESS-path message; the "no email typed"/validation-error styling
   elsewhere (--text-danger, via .error-text) is completely untouched. */
.msg-callout {
  font-size: 17px;
  font-weight: 700;
  color: var(--color-own);
  background: var(--color-own-soft);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-top: 10px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.msg-callout .icon { flex-shrink: 0; }

/* Reminders (2026-09-02) — due-date urgency colouring on reminders.html's
   list rows and the dashboard's reminder strip. Overdue/due-today reuses
   the same red already used for form errors elsewhere; "due soon" (within
   7 days) uses coral, this app's existing "needs attention" colour
   (maintenance/vehicle-modifications); anything further out or already
   dismissed just uses the row's normal muted colour, no extra emphasis. */
.reminder-badge-overdue { color: var(--text-danger); font-weight: 600; }
.reminder-badge-soon { color: var(--color-coral); font-weight: 600; }
.reminder-badge-dismissed { color: var(--text-muted); }

/* Resale-ready PDF service history (2026-09-02) — service-history.html's
   printable report. There's no server-side PDF generation on Bob's
   hosting (see service-history.js's header comment) — this is a plain
   web page that just PRINTS well, using the browser's own "Save as PDF"
   in the print dialog. @media print below hides the on-screen-only chrome
   (back link, print button) and simplifies the card/table borders for
   paper, since a screen-style shadow/rounded-card look doesn't print
   cleanly. */
.print-table th, .print-table td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  font-size: 17px;
}
.print-table th { font-weight: 600; color: var(--text-secondary); }

/* Service History report tables → "boxed" card layout (2026-09-10,
   Bob's ask, extended same day). Started as a fix for the Maintenance &
   Servicing table specifically, which overran the page's content column
   on screen, on both desktop and mobile — the cause isn't really "mobile
   vs desktop", .app-shell keeps the same ~420px content width at EVERY
   on-screen size (see the .app-shell rule further up this file; it only
   widens under @media print) — it's that that table has 6 columns, two
   of them free text ("Details", "Installer / Supplier"), which was never
   going to fit 420px as a plain table on any screen. Bob then separately
   asked for the same "boxed" look on the Vehicle Modifications table too
   (4 columns, wasn't overflowing, but he wanted the visual consistency)
   — so this now applies to both of service-history.js's tables via the
   shared .table-cards class, not just the one that had to change.
   Same pattern already used for the admin users table in
   backend/api/admin_users.php: on screen, each row of a table carrying
   the .table-cards class (added in service-history.js) becomes its own
   bordered, labelled card via a data-label attribute + ::before, no JS
   layout needed. .table-wrap (overflow-x:auto) is also wrapped around
   each as a belt-and-braces safety net, in case some future content
   still doesn't fit even the card view. @media print below restores the
   normal table layout for the printed/PDF report — that output wasn't
   reported as broken and already renders full-width (see the .app-shell
   reset in the same block), so it's left working exactly as it was. */
.table-wrap { overflow-x: auto; }

.table-cards thead { display: none; }
.table-cards, .table-cards tbody, .table-cards tr, .table-cards td {
  display: block;
  width: 100%;
}
.table-cards { min-width: 0; }
.table-cards tr {
  border: 1px solid var(--border-box);
  border-radius: 8px;
  padding: 4px 12px;
  margin-bottom: 10px;
}
.table-cards td {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  text-align: left;
}
.table-cards tr td:last-child { border-bottom: none; }
.table-cards td[data-label]::before {
  content: attr(data-label);
  font-weight: 600;
  color: var(--text-secondary);
  flex-shrink: 0;
}

@media print {
  #screenChrome { display: none !important; }
  /* Vehicle switcher (2026-09-20, service-history.html) — the same
     on-screen-only chrome as #screenChrome's own print button: useful for
     PICKING which report to view, meaningless once you're already looking
     at the printed page/PDF itself. */
  #vehicleSwitcherPanel { display: none !important; }
  /* A "Combined service cost"/"All service history records" report
     (service-history.js's renderReport()) prints each vehicle's own
     section — Vehicle details + both tables — one after another. Starting
     each one on its own fresh page keeps a multi-vehicle PDF readable,
     rather than one vehicle's tables running straight into the next
     vehicle's heading mid-page. */
  .service-history-section + .service-history-section { page-break-before: always; }
  /* margin: 0 here resets the auto top/bottom margins added 2026-09-05
     for on-screen vertical centring (see body/.app-shell above) — a
     printed page has no "spare space to centre in" concept, so this
     keeps the PDF/print output exactly as it always was. */
  .app-shell { max-width: none; padding: 0; margin: 0; }
  .print-report .card { border: none; box-shadow: none; padding: 0; }
  .print-table th, .print-table td { border-bottom: 1px solid #ccc; }

  /* Undo the on-screen card layout above — the printed/PDF report keeps
     the plain tables it always had. */
  .table-wrap { overflow-x: visible; }
  .table-cards, .table-cards thead, .table-cards tbody, .table-cards tr {
    display: revert;
    width: auto;
  }
  .table-cards td {
    display: table-cell;
    width: auto;
    padding: 6px 8px;
  }
  .table-cards tr {
    border: none;
    border-radius: 0;
    padding: 0;
    margin-bottom: 0;
  }
  .table-cards td[data-label]::before { content: none; }
}

/* Bottom navigation (2026-09-05) — Bob's new persistent tab bar:
   Dashboard / Vehicles / Reminders / More. Built by js/bottom-nav.js
   (one shared function, called from the four pages that show it) rather
   than duplicated per-page, since it needs to look and behave identically
   everywhere it appears — see that file's header comment for the full
   reasoning and which pages show it.

   Fixed to the bottom of the viewport (not inside the normal .app-shell
   flow, which is what the vertical-centring rules above operate on) and
   capped to the same 420px column width so it lines up with the app's
   content on a wide desktop browser window exactly like .app-shell does.
   `body.has-bottom-nav` reserves matching space at the bottom of
   `.app-shell` on any page that shows the nav, so it never covers the
   last bit of content — added in both the base rule and the mobile
   (max-width: 600px) override above, since that override sets its own
   padding-bottom independently. */
.bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* 2026-09-09 — the `max-width: 420px; margin: 0 auto;` this rule used to
     have (matching .app-shell's own content cap) is why Bob still saw a
     margin on the wider iPhones (the Plus/Pro Max sizes, ~428-430 CSS
     px — over 420) even after the blend fix below: the BAR itself was
     being capped and centred like a piece of content, not treated as
     full-width chrome. Removed here (and from .top-bar below) so the bar
     always spans the true device width, on every iPhone, with no
     exceptions — only .app-shell's own cards/content stay capped at
     420px, a normal "full-bleed chrome, capped content" split. */
  display: flex;
  /* 2026-09-09, Bob: "it doesn't really blend with the grey background and
     topography" — the translucent-tint-plus-blur this used to rely on
     depends on backdrop-filter actually rendering, which isn't guaranteed
     on every device/browser. Replaced with something that can't fail to
     match: the bar's own background is --body-bg (the exact same flat
     colour/gradient the page itself uses) painted with
     background-attachment: fixed, so it's positioned against the VIEWPORT
     rather than the bar's own small box — the same trick used below for
     the topo pattern (see the shared ::before rule after .top-bar), and
     the reason body's own background gained the same `fixed` attachment
     (see the body{} rule above) — both are now anchored to the same
     coordinate system, so they can't drift out of alignment. The
     backdrop-filter blur stays on top as a bonus for real scrolled
     content passing behind the bar, but the blend no longer DEPENDS on it
     working. */
  background: var(--body-bg);
  background-attachment: fixed;
  background-position: center top;
  background-size: cover;
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  /* Clips the topo-pattern ::before below to this bar's own strip of
     screen instead of the pseudo-element's technically-unbounded box —
     see that rule's own comment. */
  overflow: hidden;
  border-top: 0.5px solid var(--border);
  padding-top: 8px;
  padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
  /* 2026-09-09, Bob on desktop: "the icons are spread out across the
     screen and not central to the contents of the app" — a direct side
     effect of making the bar itself full width (above): the TABS used to
     be confined to the same 420px column as .app-shell's content because
     the whole bar was capped at 420px too. Now that the bar's background
     spans the true window width, its flex children (the tabs) were
     stretching out to match — right on a phone (nothing wider than 420px
     to notice), wrong on a wide desktop window. Fixed the same way a
     lot of full-bleed headers do it: the BACKGROUND stays full width, but
     left/right padding grows on anything wider than 420px, pushing the
     tabs back into a centred 420px-wide lane that lines up with
     .app-shell's own content underneath. `max()` keeps this a no-op at
     phone widths (the calc() term goes negative, so the plain 4px wins)
     and only kicks in once the bar is genuinely wider than the app's
     content column. */
  padding-left: max(4px, calc((100% - 420px) / 2 + 4px));
  padding-right: max(4px, calc((100% - 420px) / 2 + 4px));
  /* Above the dropdown backdrop/panel (z-index 28/29 below) so the "More"
     button itself stays clickable to toggle its own dropdown shut again,
     rather than the backdrop intercepting that second tap. */
  z-index: 30;
}
.bottom-nav .bn-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 6px 4px;
  text-decoration: none;
  color: var(--text-secondary);
  font-size: 14px;
  font-weight: 500;
  /* .bn-more is a <button>, the other three tabs are <a> — reset the
     button-specific defaults so all four look identical. */
  background: none;
  border: none;
  font-family: inherit;
  cursor: pointer;
}
.bottom-nav .bn-tab .icon {
  width: 24px;
  height: 24px;
  vertical-align: 0;
}
/* Active tab colour changed from --color-teal to --fill-primary (coral)
   on 2026-09-27, per Bob's own hover/click-only-blue follow-up ("the ones
   that change to blue on hover or click just change to the orange on
   hover or click only") — a nav icon is neutral at rest and only turns
   this colour once tapped/active, exactly the pattern that ask covered,
   just missed in the first pass since it's driven by JS adding an
   `.active` class rather than a CSS :hover/:focus pseudo-class. */
.bottom-nav .bn-tab.active { color: var(--fill-primary); }
/* The "More" tab's own dropdown being open counts as "active" too, same
   highlight, so it's clear which button opened it. */
body.bn-more-open .bottom-nav .bn-more { color: var(--fill-primary); }

body.has-bottom-nav .app-shell { padding-bottom: 104px; }
@media (max-width: 600px) {
  body.has-bottom-nav .app-shell { padding-bottom: 100px; }
}

/* Top bar (2026-09-08) — Upgrade / Reminders / Add, added by js/top-bar.js
   on every "browse/manage" in-app page. Replaces the 2026-09-05 top-left
   burger menu (js/top-menu.js, now deleted) after Bob sent a reference
   screenshot showing this three-button layout instead — see top-bar.js's
   header comment for the full reasoning. `body.has-top-bar` reserves
   matching space at the TOP of `.app-shell`, same principle as
   `body.has-bottom-nav`'s own bottom padding. */
.top-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* 2026-09-09 — see .bottom-nav's own comment above: the max-width/
     margin cap that used to match .app-shell's content width is gone, so
     this bar is always the true device width, including on the wider
     iPhones (Plus/Pro Max) that are over 420 CSS px. */
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* 2026-09-09, Bob: "it doesn't really blend with the grey background and
     topography" — see .bottom-nav's own comment above for the full
     reasoning (same fix, same background-attachment: fixed trick, applied
     identically here so both bars line up with the page's own background
     AND with each other). */
  background: var(--body-bg);
  background-attachment: fixed;
  background-position: center top;
  background-size: cover;
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  overflow: hidden;
  border-bottom: 0.5px solid var(--border);
  padding-top: calc(10px + env(safe-area-inset-top, 0px));
  padding-bottom: 10px;
  /* 2026-09-09, Bob on desktop: "the icons are spread out across the
     screen and not central to the contents of the app" — see
     .bottom-nav's own comment above for the full reasoning (identical
     fix, same max() trick, so both bars keep their buttons/tabs lined up
     with .app-shell's 420px content column on anything wider, while the
     background itself stays full width). */
  padding-left: max(14px, calc((100% - 420px) / 2 + 14px));
  padding-right: max(14px, calc((100% - 420px) / 2 + 14px));
  /* Above the dropdown backdrop/panel (z-index 28/29 below) — same
     reasoning as .bottom-nav's own z-index above. */
  z-index: 30;
}
/* The topo-pattern layer, replicated on BOTH bars so they visually
   continue the page's own topographic backdrop rather than showing a
   flat gap where it's covered. `position: absolute; inset: 0` sizes this
   pseudo to exactly the bar's own box (clipped by that bar's own
   `overflow: hidden` above); `background-attachment: fixed` is what makes
   the PATTERN inside that small box line up with the page's own
   full-viewport pattern — with `fixed` attachment the browser computes
   background-position/-size against the VIEWPORT, not this pseudo's own
   tiny box, so wherever this box happens to sit on screen (top or
   bottom), it shows exactly the slice of the SAME infinite tiled pattern
   that would be there anyway — no manual offset math, and it can't drift
   out of sync with body::before's own identical parameters (same image,
   position, size). Kept as its own layer (rather than a second
   background-image on the bar itself) purely so --topo-opacity can apply
   to just this pattern, not the bar's icons/text too. */
.top-bar::before,
.bottom-nav::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: var(--topo-bg);
  background-repeat: repeat;
  background-attachment: fixed;
  background-position: center top;
  background-size: 800px 400px;
  opacity: var(--topo-opacity);
  pointer-events: none;
  z-index: -1;
}
.top-bar .tb-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: none;
  border: none;
  color: var(--text-primary);
  cursor: pointer;
  padding: 0;
}
.top-bar .tb-btn .icon { width: 22px; height: 22px; }
/* The Reminders button sits visually in the centre of the bar even though
   it isn't the flex midpoint (Upgrade/Add are equal-width end buttons, so
   `justify-content: space-between` on three equal-width children already
   centres the middle one correctly). */
.top-bar .tb-reminders { color: var(--text-primary); }
/* A same-day :active-colour experiment here (2026-09-27) was reverted —
   Bob reported the app "moves left and right" when tapping these icons
   on his real device, and separately that the colour change wasn't even
   visible. Root cause of the second part, at least: :active only lasts
   as long as a click is physically held down, and both these icons are
   plain navigating links (straight to upgrade.html/reminders-hub.html)
   — the page changes near-instantly after a click, so any :active colour
   flash is over before it can be seen; not a working "click feedback"
   for a navigating link in the first place. The reported left-right
   movement wasn't explained by that rule alone (it only ever set
   `color`, nothing layout-related) and needs its own investigation
   before trying again — see CLAUDE.md's own dated entry for this. */
/* Add button (2026-09-09, Bob's request — "can the + action button... be
   inside an orange circle to make it more visible for users"). Every
   other .tb-btn is a plain icon with no fill (see the rule above), but
   this one gets a solid circle so it stands out as the bar's one
   primary action, same treatment the still-unbuilt quick-add FAB from
   the styling mockup was planned to use. --on-orange keeps the "+"
   glyph a dark, readable colour against that fill in both light and
   dark mode (the icon's stroke reads `currentColor`, so setting `color`
   here is what recolours it).
   2026-09-16, Bob's ask: fill switched from --color-orange to
   --color-coral, matching .add-action-btn's own switch above. First
   checked --on-orange's contrast against the new fill: 3.9:1 in light
   mode (coral #C15A2C) and 7.2:1 in dark mode (coral #E8916B) — both
   passable, so --on-orange (near-black, fixed both modes) was kept
   initially.
   Same day, follow-up — Bob asked for the text/icon on every coral
   button to be WHITE specifically in light mode. Recomputed properly
   rather than assuming this would be worse (the original note above
   had only checked white against DARK mode's coral, where it genuinely
   is worse — 2.4:1): white against LIGHT mode's coral (#C15A2C) comes
   out at ~4.4:1 — actually a touch BETTER than near-black's ~3.9-4.0:1,
   comfortably past the 3:1 floor for this bold, large "+" glyph/button
   text. So in light mode both .add-action-btn and #tbAddBtn now use a
   plain #fff; dark mode is UNCHANGED — still --on-orange near-black,
   via the override block directly below, since white would be a weak
   2.4:1 there and near-black's 7.2:1 was never in question. */
.top-bar #tbAddBtn {
  border-radius: 50%;
  background: var(--color-coral);
  color: #fff;
}
/* Dark-mode override for the two coral buttons above (2026-09-16) —
   same guarded prefers-color-scheme + explicit data-theme="dark"
   pattern used throughout this file (see .theme-word-dark above for
   the same three-block shape), so an explicit in-app "Light" choice
   always keeps white even on a dark device, and an explicit "Dark"
   choice always gets near-black even on a light device. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .add-action-btn,
  :root:not([data-theme="light"]) .top-bar #tbAddBtn { color: var(--on-orange); }
}
:root[data-theme="dark"] .add-action-btn,
:root[data-theme="dark"] .top-bar #tbAddBtn { color: var(--on-orange); }
.tb-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: 8px;
  background: var(--color-coral, #e05a4e);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  box-sizing: border-box;
}
/* Inline variant of .tb-badge (2026-09-08, Bob's request) — same orange
   count-dot the top bar's Reminders icon uses, but sitting in normal text
   flow next to a vehicle's name on reminders-hub.html's "By vehicle" list,
   rather than absolutely-positioned over a 40x40 icon button. Overrides only
   what .tb-badge sets for absolute positioning against a .tb-btn parent —
   the colour/size/shape rules above are inherited unchanged. See
   reminders-hub.js's own "ORANGE REMINDER-COUNT DOT" header comment. */
.tb-badge-inline {
  position: static;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 8px;
  vertical-align: middle;
}
/* Without this, the rule above's `display: inline-flex` beats the browser's
   own default `[hidden] { display: none }` (an author style always wins
   over the user-agent stylesheet, even at equal selector specificity) — so
   a badge left `hidden` by reminders-hub.js's loadReminderCounts() would
   still show as an empty orange dot next to every vehicle with zero
   reminders. Caught via a screenshot during this feature's own testing. */
.tb-badge-inline[hidden] {
  display: none;
}

body.has-top-bar .app-shell { padding-top: 76px; }
@media (max-width: 600px) {
  body.has-top-bar .app-shell { padding-top: 72px; }
}

/* Generic dropdown panel + backdrop (2026-09-08) — shared by the top bar's
   Add dropdown (top-bar.js) and the bottom nav's More dropdown
   (bottom-nav.js), so the two never drift apart visually. Each dropdown
   stays in the DOM the whole time (rendered once by its owning function)
   and is simply hidden/shown via its own body class (`tb-add-open` /
   `bn-more-open`) — avoids re-building the markup every time it opens. */
.ui-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.25);
  z-index: 28;
}
body.tb-add-open #tbAddBackdrop,
body.bn-more-open #bnMoreBackdrop {
  display: block;
}
.dropdown-panel {
  display: none;
  position: fixed;
  z-index: 29;
  min-width: 220px;
  max-width: calc(100vw - 32px);
  background: var(--card-bg);
  border: 0.5px solid var(--border-box);
  border-radius: 14px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
  padding: 8px;
}
body.tb-add-open #tbAddDropdown,
body.bn-more-open #bnMoreDropdown {
  display: block;
}
/* Opens downward from the top bar's Add button, right-aligned under it.
   `right` (2026-09-11, Bob's ask — this was flush with the true edge of
   the browser window on desktop, "too far over to the right of the
   screen", while the Add button it opens from sits inside .top-bar's
   centred 420px content column, not the window edge) now uses the exact
   same max() formula .top-bar itself uses for its own padding-right —
   see .top-bar's comment above — so the dropdown's right edge lines up
   with the Add button's own right edge on any screen width, instead of
   the window's. On a phone (viewport under ~448px) `max()` always picks
   the plain 14px branch, so mobile is completely unchanged. */
.dropdown-panel-right {
  top: calc(66px + env(safe-area-inset-top, 0px));
  right: max(14px, calc((100% - 420px) / 2 + 14px));
}
/* Opens upward from the bottom nav's More button, right-aligned above it.
   Same fix and reasoning as .dropdown-panel-right above — .bottom-nav
   centres its own tabs on desktop the same way .top-bar does (see
   .bottom-nav's own comment), so the More button has the same "sits in
   the centred column, not the window edge" issue there. The calc() term
   here matches .bottom-nav's own padding-right exactly (`+ 4px`, not
   .top-bar's `+ 14px` — the two bars use different padding, see each
   one's own comment), so the dropdown's right edge tracks the actual
   right edge of the tab row on desktop; the `max(14px, ...)` floor is
   kept as-is (not .bottom-nav's own 4px floor) so mobile — where the
   calc() term goes negative and the floor always wins — is completely
   unchanged from before this fix. */
.dropdown-panel-up {
  bottom: calc(90px + env(safe-area-inset-bottom, 0px));
  right: max(14px, calc((100% - 420px) / 2 + 4px));
}
.dropdown-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  border: none;
  background: none;
  border-radius: 8px;
  color: var(--text-primary);
  text-decoration: none;
  font-family: inherit;
  font-size: 16px;
  text-align: left;
  cursor: pointer;
  box-sizing: border-box;
}
.dropdown-item:hover { background: var(--surface-2); }
.dropdown-item .icon { width: 20px; height: 20px; flex-shrink: 0; }
/* Vehicle photos (2026-09-09) — see vehicle-selector.js's
   vehiclePhotoFieldHtml() (the manage-panel editor, a small square
   preview + "Choose/Change photo" button) and dashboard.js's
   renderVehiclePhotos() (the actual dashboard display — one wide photo
   for a single vehicle, or two side by side with the pairing/link icon
   between them for a combined view). The photo itself lives in
   Cloudflare R2, never in this app's own database — see
   backend/includes/r2.php's header comment. */
.vehicle-photo-editor {
  display: flex;
  align-items: center;
  gap: 14px;
}
.vehicle-photo-preview {
  width: 72px;
  height: 72px;
  border-radius: var(--radius);
  object-fit: cover;
  flex-shrink: 0;
  border: 0.5px solid var(--border-box);
}
.vehicle-photo-preview-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  color: var(--text-secondary);
}
.vehicle-photo-preview-empty .icon { width: 28px; height: 28px; }

/* The dashboard's own display — a wider, shorter crop than the manage
   panel's square preview above, since this sits right under the
   vehicle's name rather than next to a form field. */
.vehicle-photo {
  width: 100%;
  height: 160px;
  object-fit: cover;
  border-radius: var(--radius-lg);
  display: block;
  margin-bottom: 14px;
  border: 0.5px solid var(--border-box);
  box-sizing: border-box;
}
.vehicle-photo-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--card-bg);
  color: var(--text-secondary);
}
.vehicle-photo-placeholder .icon { width: 36px; height: 36px; }

.vehicle-photo-pair {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
.vehicle-photo-pair .vehicle-photo-slot { flex: 1; min-width: 0; }
.vehicle-photo-pair .vehicle-photo,
.vehicle-photo-pair .vehicle-photo-placeholder {
  height: 120px;
  margin-bottom: 0;
}
/* Same rounded-square "icon-wrap" treatment the Vehicles page already
   uses for a pairing's link icon (see .pairing-toggle .icon-wrap.purple
   above) — reused here rather than inventing a new look. */
.vehicle-photo-link {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 2026-10-04, Bob's ask: the link icon between the two pairing photos is
     now the app's green (same as the Vehicles page's own link icon) —
     was purple. */
  background: var(--color-own);
  color: var(--on-primary);
}
.vehicle-photo-link .icon { width: 20px; height: 20px; }

.dropdown-divider {
  height: 0.5px;
  background: var(--border);
  margin: 6px 4px;
}

/* 2026-10 accessibility pass: people who've asked their phone to cut down on
   motion (iOS "Reduce Motion" / Android "Remove animations") get no
   transitions or animations. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}


/* .link-underline (2026-10-05, Bob's ask) — the dashboard's "View charts" and
   "View all" links. Same effect as the hero links on motorhoming-adventures.co.uk:
   a 2px rounded line sits under the text and a second one sweeps in from the left
   over 280ms on hover/focus/press. Both lines use theme tokens so it works in light
   and dark mode; the sweeping line is --text-primary (near-black / white). The
   parent <a> (or the card's own <a>) triggers the sweep. */
.link-underline {
  position: relative; display: inline-block; padding-bottom: 0.4rem;
  font-weight: 600; color: var(--text-primary); text-decoration: none;
}
.link-underline::before, .link-underline::after {
  content: ""; position: absolute; left: 0; bottom: 0; width: 100%; height: 2px; border-radius: 999px;
}
.link-underline::before { background: var(--link-line, var(--border-strong)); }
.link-underline::after { background: var(--link-line-hover, var(--text-primary)); transform: scaleX(0); transform-origin: left center; transition: transform 280ms; }
a.link-underline:hover::after, a.link-underline:focus-visible::after, a.link-underline:active::after,
button.link-underline:hover::after, button.link-underline:focus-visible::after, button.link-underline:active::after,
a:hover .link-underline::after, a:focus-visible .link-underline::after, a:active .link-underline::after { transform: scaleX(1); }
a.link-underline:hover { opacity: 1; color: var(--text-primary); }
@media (prefers-reduced-motion: reduce) { .link-underline::after { transition: none; } }

/* Text-style buttons ("Forgot password?", "Start over", "refresh to today's rate") get the same look as the links. */
button.link-underline { background: none; border: none; border-radius: 0; padding: 0 0 0.4rem; font: inherit; color: var(--text-primary); cursor: pointer; }
button.link-underline:hover { border: none; }

/* Dashboard "Quick Add Fuel" / "Quick Add Cost" hover (2026-10-06, Bob's ask):
   same hover as the Fuel page's own Quick Add Fuel button (a.primary-btn:hover)
   — text goes to --text-primary (black in light mode, white in dark mode) and
   the button fades slightly. They're <button>s, so they didn't pick this up. */
#quickAddFuelBtn:hover, #logCostBtn:hover,
#quickAddFuelBtn:focus-visible, #logCostBtn:focus-visible { color: var(--text-primary); opacity: 0.8; }

/* All the orange action buttons share the same hover (2026-10-06, Bob's ask:
   "the same for the trips page and vehicles page orange buttons and wherever
   else they occur"): text goes to --text-primary (near-black in light mode,
   white in dark mode) and the button fades slightly — exactly the Fuel page's
   Quick Add Fuel button. Disabled buttons are left alone. */
button.primary-btn:hover:not(:disabled), a.primary-btn:hover,
button.add-action-btn:hover:not(:disabled), a.add-action-btn:hover,
.fuel-btn:hover:not(:disabled), #printBtn:hover:not(:disabled),
.login-primary-btn:hover:not(:disabled),
button.primary-btn:focus-visible:not(:disabled), a.add-action-btn:focus-visible, button.add-action-btn:focus-visible:not(:disabled) {
  color: var(--text-primary) !important; opacity: 0.8;
}

/* Settings > Appearance / Units boxes (2026-10-06, Bob's ask): normal white card
   in light mode, but see-through in dark mode so the inactive toggle buttons
   show the topography background, like the dashboard tabs. */
.toggle-section { background: var(--toggle-section-bg, var(--card-bg)); }

/* The top bar's orange "+" button gets the same hover as the other orange buttons
   (2026-10-06, Bob's ask): icon goes near-black in light mode / white in dark
   mode and the button fades slightly. !important because the dark-mode colour
   rules for #tbAddBtn are more specific. */
.top-bar #tbAddBtn:hover, .top-bar #tbAddBtn:focus-visible { color: var(--text-primary) !important; opacity: 0.8; }

/* Top bar Upgrade / Reminders icons turn orange on their own page (2026-10-06, Bob's ask),
   same colour as the bottom nav's active tab. */
.top-bar .tb-btn.active { color: var(--fill-primary); }

/* Welcome tours (tour.js) — a centred card over a dimmed page, bottom-aligned on phones. */
.tour-backdrop { position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,0.55); display: flex; align-items: flex-end; justify-content: center; padding: 16px; }
@media (min-width: 600px) { .tour-backdrop { align-items: center; } }
.tour-card { width: 100%; max-width: 388px; box-sizing: border-box; background: var(--tour-bg); color: var(--text-primary); border: 0.5px solid var(--border-box); border-radius: var(--radius-lg); padding: 22px 20px 18px; text-align: center; }
.tour-icon { display: flex; justify-content: center; color: var(--fill-primary); margin-bottom: 6px; }
.tour-icon .icon { width: 34px; height: 34px; }
.tour-step { font-size: 15px; margin-bottom: 4px; }
.tour-title { font-size: 21px; margin: 0 0 8px; }
.tour-text { font-size: 17px; color: var(--text-secondary); margin-bottom: 14px; }
.tour-dots { display: flex; justify-content: center; gap: 7px; margin-bottom: 16px; }
.tour-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border-strong); }
.tour-dot.on { background: var(--fill-primary); }
.tour-actions { display: flex; gap: 10px; justify-content: space-between; align-items: center; margin-top: 10px; }
.tour-actions button { flex: 1; width: auto; }
.tour-skip { margin-top: 14px; font-size: 16px; }
