@import url('https://fonts.googleapis.com/css2?family=Big+Shoulders+Display:wght@600;700;800&family=Public+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@500;600&family=Chivo:wght@500;700;900&family=Work+Sans:wght@400;500;600&display=swap');

/* ---- Theming: "Alpenglow" ----
   Grounded in the one moment every campsite looks unmistakably like itself: dusk light
   over the treeline. Spruce/Ember are the same roles the old pine/amber filled (and are
   still what a site's own branding colours override in Settings — see applyBranding in
   booking.js), just recoloured off the cream/serif look that reads as a generic
   AI-design default. Dusk is new: the palette's old pine+amber pairing never had a
   second, cooler hue to lean on for secondary accents and chart states. */
:root, [data-theme="light"] {
  /* Without this, native controls (date-picker icon, select dropdown, number spinners,
     scrollbars) keep the browser's default light chrome no matter what data-theme says —
     on a form-heavy page like the booking flow, that reads as dark mode "not working"
     even though every themed colour above is switching correctly. */
  color-scheme: light;
  --pine: #123524;           /* Spruce — headings, brand */
  --pine-light: #1F5738;
  --dusk: #33407A;           /* new: secondary accent, links, "awaiting" states */
  --dusk-soft: #5762A0;
  --clay: #A6532E;           /* fourth hue, alongside pine/dusk/amber — "present now" states */
  --bark: #4B5142;
  --on-bark: #FAFBF6;
  --canvas: #EFF2EA;
  --canvas-light: #FAFBF6;
  --amber: #FF6B35;          /* Ember — the one accent: buttons, highlights */
  --ember: #B23A55;          /* berry — overdue/cancelled/errors, kept apart from the accent */
  /* Text colour used wherever --pine/--amber are a BACKGROUND (nav highlights, buttons,
     chart headers) rather than text on the page's own canvas. These start as sane
     defaults for the stock palette, but applyBrandColours()/applyBranding() in JS
     recompute them live from whatever --pine/--amber actually resolve to — including a
     site's own custom brand colour — so text on top of them stays readable regardless of
     how light or dark that colour turns out to be, in either theme. */
  --on-pine: #FAFBF6;
  --on-dusk: #FAFBF6;
  --on-amber: #201C16;
  --ink: #171B12;
  --line: #D7DCCC;
  --radius: 4px;

  --muted: #5B6154;          /* secondary text */
  --surface: #FAFBF6;        /* cards */
  --surface-alt: #E4E8DC;    /* hover, table stripes */
  --surface-sunken: #EFF2EA;
  --tint-good: #DCEEE2;      /* soft green background */
  --tint-warn: #F5E7CC;      /* soft amber background */
  --tint-bad: #F6DEE4;       /* soft berry background */
  --on-accent: #23140A;      /* text on the (light) amber accent */
  --shadow: rgba(23,27,18,0.14);

  /* Site map illustration */
  --map-grass: #86AD68;
  --map-grass-edge: #9EC080;
  --map-path: #F7F4EC;
  --map-building: #C79E6B;
  --map-roof: #B08652;
  --map-tree-dark: #123524;
  --map-tree-mid: #1F5738;
  --map-tree-light: #4B7A54;
  --map-label: #171B12;
  --map-label-halo: #ffffff;

  /* The header is one fixed moment (dusk over the treeline), not a themed surface — it
     keeps its own colours in light and dark, the way a photograph wouldn't recolour
     itself for dark mode. See header.topbar below. */
  --hero-top: #123524;
  --hero-mid: #33407A;
  --hero-bottom: #FF6B35;
  --hero-ridge: #0B2115;
  --hero-on: #FBF6EC;

  /* Built-in "campsite" watermark — a small tent/bell-tent/campervan line-art tile,
     layered OVER whatever background colour is actually showing (a custom pick, or the
     theme's own default) rather than being an alternative to it — see applyAdminBranding()
     in dashboard.js, which picks whichever of these two tone variants actually contrasts
     against that colour (checked via colourLuminance(), the same helper already used for
     header/page text-colour contrast). A flat single tone each, not a multi-colour
     illustration — a watermark needs to read clearly against ANY colour someone picks, not
     just the handful this theme happens to use elsewhere. Defined once here, not redefined
     for [data-theme="dark"] below, since the choice between them is driven by the actual
     resolved colour's lightness, not by which theme is active — the same reasoning as
     --hero-* above, just for a different kind of "doesn't change with theme". */
  --watermark-dark: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'170'%20height%3D'140'%20viewBox%3D'0%200%20170%20140'%3E%20%3C!--%20Ridge%2FA-frame%20tent%20--%3E%20%3Cg%20transform%3D'translate(10%2C78)'%20opacity%3D'0.24'%3E%20%3Cpath%20d%3D'M0%2C34%20L22%2C3%20L44%2C34%20Z'%20fill%3D'none'%20stroke%3D'%23171B12'%20stroke-width%3D'2'%20stroke-linejoin%3D'round'%2F%3E%20%3Cline%20x1%3D'22'%20y1%3D'3'%20x2%3D'22'%20y2%3D'34'%20stroke%3D'%23171B12'%20stroke-width%3D'1.4'%2F%3E%20%3Cpath%20d%3D'M17%2C34%20L22%2C21%20L27%2C34'%20fill%3D'none'%20stroke%3D'%23171B12'%20stroke-width%3D'1.4'%20stroke-linejoin%3D'round'%2F%3E%20%3C%2Fg%3E%20%3C!--%20Bell%20tent%20with%20flag%20--%3E%20%3Cg%20transform%3D'translate(95%2C58)'%20opacity%3D'0.24'%3E%20%3Cpath%20d%3D'M3%2C37%20Q3%2C9%2022%2C7%20Q41%2C9%2041%2C37%20Z'%20fill%3D'none'%20stroke%3D'%23171B12'%20stroke-width%3D'2'%20stroke-linejoin%3D'round'%2F%3E%20%3Cline%20x1%3D'22'%20y1%3D'7'%20x2%3D'22'%20y2%3D'0'%20stroke%3D'%23171B12'%20stroke-width%3D'1.4'%2F%3E%20%3Cpath%20d%3D'M22%2C0%20L29%2C2.5%20L22%2C5%20Z'%20fill%3D'%23171B12'%20stroke%3D'none'%2F%3E%20%3C%2Fg%3E%20%3C!--%20Campervan%20--%3E%20%3Cg%20transform%3D'translate(43%2C10)'%20opacity%3D'0.24'%3E%20%3Cpath%20d%3D'M1%2C30%20L1%2C15%20Q1%2C7%2011%2C7%20L37%2C7%20Q47%2C7%2047%2C17%20L47%2C30%20Z'%20fill%3D'none'%20stroke%3D'%23171B12'%20stroke-width%3D'2'%20stroke-linejoin%3D'round'%2F%3E%20%3Cline%20x1%3D'16'%20y1%3D'7'%20x2%3D'12'%20y2%3D'30'%20stroke%3D'%23171B12'%20stroke-width%3D'1.4'%2F%3E%20%3Crect%20x%3D'4'%20y%3D'12'%20width%3D'8'%20height%3D'8'%20rx%3D'1.5'%20fill%3D'none'%20stroke%3D'%23171B12'%20stroke-width%3D'1.3'%2F%3E%20%3Crect%20x%3D'19'%20y%3D'12'%20width%3D'24'%20height%3D'8'%20rx%3D'1.5'%20fill%3D'none'%20stroke%3D'%23171B12'%20stroke-width%3D'1.3'%2F%3E%20%3Ccircle%20cx%3D'13'%20cy%3D'30'%20r%3D'4.5'%20fill%3D'none'%20stroke%3D'%23171B12'%20stroke-width%3D'1.4'%2F%3E%20%3Ccircle%20cx%3D'37'%20cy%3D'30'%20r%3D'4.5'%20fill%3D'none'%20stroke%3D'%23171B12'%20stroke-width%3D'1.4'%2F%3E%20%3C%2Fg%3E%20%3C%2Fsvg%3E");
  --watermark-light: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'170'%20height%3D'140'%20viewBox%3D'0%200%20170%20140'%3E%20%3C!--%20Ridge%2FA-frame%20tent%20--%3E%20%3Cg%20transform%3D'translate(10%2C78)'%20opacity%3D'0.36'%3E%20%3Cpath%20d%3D'M0%2C34%20L22%2C3%20L44%2C34%20Z'%20fill%3D'none'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'2'%20stroke-linejoin%3D'round'%2F%3E%20%3Cline%20x1%3D'22'%20y1%3D'3'%20x2%3D'22'%20y2%3D'34'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'1.4'%2F%3E%20%3Cpath%20d%3D'M17%2C34%20L22%2C21%20L27%2C34'%20fill%3D'none'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'1.4'%20stroke-linejoin%3D'round'%2F%3E%20%3C%2Fg%3E%20%3C!--%20Bell%20tent%20with%20flag%20--%3E%20%3Cg%20transform%3D'translate(95%2C58)'%20opacity%3D'0.36'%3E%20%3Cpath%20d%3D'M3%2C37%20Q3%2C9%2022%2C7%20Q41%2C9%2041%2C37%20Z'%20fill%3D'none'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'2'%20stroke-linejoin%3D'round'%2F%3E%20%3Cline%20x1%3D'22'%20y1%3D'7'%20x2%3D'22'%20y2%3D'0'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'1.4'%2F%3E%20%3Cpath%20d%3D'M22%2C0%20L29%2C2.5%20L22%2C5%20Z'%20fill%3D'%23FAFBF6'%20stroke%3D'none'%2F%3E%20%3C%2Fg%3E%20%3C!--%20Campervan%20--%3E%20%3Cg%20transform%3D'translate(43%2C10)'%20opacity%3D'0.36'%3E%20%3Cpath%20d%3D'M1%2C30%20L1%2C15%20Q1%2C7%2011%2C7%20L37%2C7%20Q47%2C7%2047%2C17%20L47%2C30%20Z'%20fill%3D'none'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'2'%20stroke-linejoin%3D'round'%2F%3E%20%3Cline%20x1%3D'16'%20y1%3D'7'%20x2%3D'12'%20y2%3D'30'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'1.4'%2F%3E%20%3Crect%20x%3D'4'%20y%3D'12'%20width%3D'8'%20height%3D'8'%20rx%3D'1.5'%20fill%3D'none'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'1.3'%2F%3E%20%3Crect%20x%3D'19'%20y%3D'12'%20width%3D'24'%20height%3D'8'%20rx%3D'1.5'%20fill%3D'none'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'1.3'%2F%3E%20%3Ccircle%20cx%3D'13'%20cy%3D'30'%20r%3D'4.5'%20fill%3D'none'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'1.4'%2F%3E%20%3Ccircle%20cx%3D'37'%20cy%3D'30'%20r%3D'4.5'%20fill%3D'none'%20stroke%3D'%23FAFBF6'%20stroke-width%3D'1.4'%2F%3E%20%3C%2Fg%3E%20%3C%2Fsvg%3E");

  --field-bg: #ffffff;       /* form fields — brighter than the card so they read as inputs */

  /* Chart bar colours — must stay clearly distinct from each other in both themes */
  --chart-paid: #1F5738;
  --chart-owing: #C9861A;
  --chart-overdue: #B23A55;
  /* Deeper, more saturated versions of the two above — a confirmed booking with
     literally nothing paid toward it is a different situation from one that's just
     waiting on the final balance, so it earns a visually heavier colour, not the same
     amber/berry as everything else that owes something. */
  --chart-owing-all: #A8460A;
  --chart-overdue-all: #7A1F3A;
  --chart-unconfirmed: #7A8B99;
  --chart-pending: #5762A0;
  --chart-external: #33407A;
  --chart-blocked-a: #D6D2C4;
  --chart-blocked-b: #C4BFAF;
  --chart-text: #ffffff;
}

/* Dark theme. Deliberately warm charcoal rather than pure black — this gets used at a
   reception desk at night, and pure black on a bright screen is harsh to read for long. */
[data-theme="dark"] {
  color-scheme: dark;
  --pine: #85DEAB;           /* headings/accents must be light in dark mode to stay legible */
  --pine-light: #61BC8D;
  --dusk: #8D97DA;
  --dusk-soft: #6B76B8;
  --clay: #E0916A;
  --bark: #A6AC9A;
  --on-bark: #1A2016;
  --canvas: #11150D;
  --canvas-light: #1A2016;
  --amber: #FF9B62;
  --on-pine: #1A2016;
  --on-dusk: #1A2016;
  --on-amber: #201C16;
  --ember: #D9557E;          /* balances two jobs: legible as text on dark canvas, and
                                 dark enough to still hold white text as a button fill */
  --ink: #EAEEE3;
  --line: #2B3226;

  --muted: #A6AC9A;
  --surface: #1A2016;
  --surface-alt: #242B1E;
  --surface-sunken: #161B11;
  --tint-good: #1D3527;
  --tint-warn: #3A2E14;
  --tint-bad: #3A1F26;
  --on-accent: #1A0D06;
  --shadow: rgba(0,0,0,0.5);

  --map-grass: #2F4033;
  --map-grass-edge: #364A38;
  --map-path: #4A4639;
  --map-building: #6E5940;
  --map-roof: #5A4733;
  --map-tree-dark: #14261C;
  --map-tree-mid: #1B3527;
  --map-tree-light: #295038;
  --map-label: #EAEEE3;
  --map-label-halo: #10140D;

  /* Fixed, same as light — the dusk moment doesn't invert for dark mode. */
  --hero-top: #123524;
  --hero-mid: #33407A;
  --hero-bottom: #FF6B35;
  --hero-ridge: #0B2115;
  --hero-on: #FBF6EC;

  --field-bg: #242B1E;       /* lighter than the card, same "stands out" affordance as light mode */

  /* Lighter, less saturated versions — full-strength colours glow uncomfortably on dark */
  --chart-paid: #3E8F65;
  --chart-owing: #D99A2E;
  --chart-overdue: #C4527A;
  --chart-owing-all: #E06A1A;
  --chart-overdue-all: #B0446E;
  --chart-unconfirmed: #8FA0AD;
  --chart-pending: #6F79B8;
  --chart-external: #4A5590;
  --chart-blocked-a: #2B3226;
  --chart-blocked-b: #242B1E;
  --chart-text: #F5F1E6;
}

/* ---- Theming: "Command Deck" — admin dashboard only ----
   A separate identity from Alpenglow above, scoped to body.admin-page so it never reaches
   the guest-facing pages (public/index.html, portal.html, pay.html, etc.), which keep
   Alpenglow and their own per-site branding colours untouched. Deliberately fixed, not
   overridable per site (see applyAdminBranding() in dashboard.js, which no longer applies
   a site's custom colours to the dashboard) — the tape chart's arriving/departing/
   overdue/settled colour-coding needs to mean the same thing on every site's dashboard,
   not clash with whatever brand colour that site picked for its own booking page.
   Reuses the SAME variable names as Alpenglow (--pine, --amber, --surface, etc.) rather
   than introducing new ones, since every existing rule in this file and every var(--...)
   reference in dashboard.js already reads those — a full reskin becomes a matter of
   redefining values, not rewriting every selector that uses them. A rule that matches
   body.admin-page directly always wins over whatever's inherited from <html> (including
   any leftover inline override), so this doesn't depend on the JS having been updated
   too — it is, but this alone would already be enough. */
body.admin-page, body.admin-page[data-theme="light"] {
  color-scheme: light;
  --pine: #1F5F52;
  --pine-light: #2C7C6A;
  --dusk: #2F5FA0;
  --dusk-soft: #5B85BE;
  --clay: #B5651D;
  --bark: #57645F;
  --on-bark: #F8FAF9;
  --canvas: #EEF1F0;
  --canvas-light: #FFFFFF;
  --amber: #C97A1E;
  --ember: #B4433A;
  --on-pine: #F8FAF9;
  --on-dusk: #F8FAF9;
  --on-amber: #FFFFFF;
  --ink: #16211F;
  --line: #D7DEDC;
  --muted: #55645F;
  --surface: #FFFFFF;
  --surface-alt: #E7ECEA;
  --surface-sunken: #EFF3F1;
  --tint-good: #DCEFE7;
  --tint-warn: #F6E6CE;
  --tint-bad: #F7DEDC;
  --on-accent: #FFFFFF;
  --shadow: rgba(20,30,28,0.10);
  --field-bg: #FFFFFF;
  --chart-paid: #1F6B4E;
  --chart-owing: #C9861A;
  --chart-overdue: #B4433A;
  --chart-owing-all: #A8460A;
  --chart-overdue-all: #7A2620;
  --chart-unconfirmed: #6B7F8C;
  --chart-pending: #3F62A0;
  --chart-external: #2F5FA0;
  --chart-blocked-a: #D6D9D8;
  --chart-blocked-b: #C7CBC9;
  --chart-text: #FFFFFF;
}
/* Higher specificity than the bare [data-theme="dark"] rule above (which only matches
   <html>) so this wins for everything inside body.admin-page regardless of source order. */
[data-theme="dark"] body.admin-page {
  color-scheme: dark;
  --pine: #4C9A78;
  --pine-light: #6BB596;
  --dusk: #5B8FD9;
  --dusk-soft: #7FA8E2;
  --clay: #E3A23C;
  --bark: #8FA09C;
  --on-bark: #0E1516;
  --canvas: #111A1D;
  --canvas-light: #1B2529;
  --amber: #E3A23C;
  --ember: #D9564A;
  --on-pine: #0E1516;
  --on-dusk: #0E1516;
  --on-amber: #14100A;
  --ink: #E8EDEA;
  --line: #2C3A3F;
  --muted: #8FA09C;
  --surface: #1B2529;
  --surface-alt: #223035;
  --surface-sunken: #0D1416;
  --tint-good: #1B3226;
  --tint-warn: #332616;
  --tint-bad: #33201C;
  --on-accent: #14100A;
  --shadow: rgba(0,0,0,0.45);
  --field-bg: #1F2A2E;
  --chart-paid: #4C9A78;
  --chart-owing: #E3A23C;
  --chart-overdue: #D9564A;
  --chart-owing-all: #E8843C;
  --chart-overdue-all: #C4433A;
  --chart-unconfirmed: #7E96A0;
  --chart-pending: #5B8FD9;
  --chart-external: #6F8FC4;
  --chart-blocked-a: #26312F;
  --chart-blocked-b: #1F2A28;
  --chart-text: #F5F1E6;
}
body.admin-page {
  font-family: 'Work Sans', -apple-system, sans-serif;
}
body.admin-page h1, body.admin-page h2, body.admin-page h3 {
  font-family: 'Chivo', 'Arial Narrow', sans-serif;
  letter-spacing: 0;
}
/* The dusk-over-treeline header stays as-is deliberately — it's Alpenglow's one fixed,
   theme-independent signature moment (see header.topbar below), not part of the token
   system this reskin recolours. Left in place rather than reworked in the same pass that
   just fixed the dashboard's own palette, so it doesn't get lost as a separate decision. */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: 'Public Sans', -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.5;
}

/* Big Shoulders Display is a condensed, signage-like face — bold and unmistakable at
   page-title size, but shouting in caps at every small section header throughout a
   dense admin screen would fatigue fast. So h1 (one per page) gets the full treatment;
   h2/h3 keep the same face's structure without going uppercase. */
h1, h2, h3 { font-family: 'Big Shoulders Display', 'Arial Narrow', sans-serif; font-weight: 700; letter-spacing: 0.01em; margin: 0 0 0.4em; color: var(--pine); }
h1 { font-size: 2.2rem; font-weight: 800; text-transform: uppercase; }
h2 { font-size: 1.5rem; }
h3 { font-size: 1.2rem; }

.mono { font-family: 'IBM Plex Mono', monospace; white-space: nowrap; }

a { color: var(--pine); }

/* The header is the one signature moment: dusk light over a treeline, the thing every
   campsite actually looks like at the hour most guests arrive. It's a fixed image, not a
   themed surface (--hero-* vars are the same in light and dark — see above), and it's
   spent nowhere else in the interface. A site's own branding colours (Settings) still
   override --pine/--amber everywhere else; the hero deliberately sits outside that so
   one guest-uploaded colour can't turn dusk into daylight. */
header.topbar {
  background: linear-gradient(165deg, var(--hero-top) 0%, var(--hero-mid) 65%, var(--hero-bottom) 130%);
  color: var(--hero-on);
  padding: 0.8rem 1.3rem 1.3rem 1.3rem;
  /* Pinned to the top on every device — sticky rather than fixed, so it keeps reserving
     its own space in normal flow (nothing below needs manual padding to avoid sliding
     under it) right up until the page actually scrolls past it. Still works as the
     containing block for the absolutely-positioned mark/ridge below, same as relative
     did. z-index keeps scrolled page content from painting over it once stuck; still
     comfortably below the mobile drawer (200), its backdrop (199) and the hamburger
     that needs to sit above the drawer (201) — see .nav-toggle. */
  position: sticky;
  top: 0;
  z-index: 100;
  overflow: hidden;
  box-shadow: 0 2px 14px var(--shadow);
}
/* Holds the operator's own logo (inserted by applyBranding() in booking.js) beside their
   site name, rather than the logo stacking above it as its own line. */
.site-brand-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.site-logo { height: 30px; width: auto; display: block; border-radius: 4px; }
header.topbar h1 {
  color: var(--hero-on);
  font-size: 1.3rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin: 0;
}
/* "Logged in as X (role)" used to sit here always — genuinely useful once, skippable on
   every subsequent visit, and it now also lives in Settings > Account, so dropping it
   here isn't a loss, just a header that stops repeating itself on every page load. */
header.topbar .subtitle { display: none; }
/* A low ridgeline silhouette, not a decorative rule — the horizon the gradient is lit
   against. Height scales with the smaller header above; the polygon itself is already
   in percentages, so it doesn't need its own adjustment. */
header.topbar::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 13px;
  background: var(--hero-ridge);
  clip-path: polygon(
    0% 100%, 0% 62%, 5% 35%, 11% 58%, 17% 20%, 24% 52%, 31% 28%, 38% 56%,
    45% 18%, 53% 48%, 61% 10%, 69% 44%, 77% 24%, 85% 54%, 93% 22%, 100% 40%, 100% 100%
  );
}

/* Scales with the viewport instead of sitting at a fixed width — a fixed 960px looked
   the same on a 13" laptop as on a 32" monitor, wasting most of the screen on anything
   larger. The cap keeps text-heavy steps (dates, details) from stretching into
   unreadably long lines on an ultrawide display. */
.wrap { max-width: min(92vw, 1400px); margin: 0 auto; padding: 1.5rem; }

/* A coloured left-border on a rounded card is a generic default nearly every AI-styled
   dashboard reaches for. A dashed inset line reads as a stitched seam instead — which is
   where this app's canvas motif actually came from, applied on purpose here rather than
   out of habit. */
.card {
  background: var(--canvas-light);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem;
  margin-bottom: 1rem;
  box-shadow: 0 1px 4px var(--shadow);
  position: relative;
}
.card::before {
  content: '';
  position: absolute;
  inset: 6px;
  border: 1px dashed color-mix(in srgb, var(--pine) 35%, transparent);
  border-radius: 2px;
  pointer-events: none;
}

/* Pitches grouped into their real sections (A, B, C, D…, matching sites.section) rather
   than one undifferentiated grid — a guest at a real site thinks "which row is this in?",
   not just "which number?". Chips, not bordered boxes: a previous version packed the name,
   surface and capacity into a tiny bordered card and ellipsis-truncated whatever didn't
   fit, which hid text outright and read poorly in dark mode (a 1px border in --line is low
   contrast against --canvas-light there). A pill with a solid fill reads clearly in both
   themes and never truncates — it just wraps onto the next line. */
.pitch-section { margin-bottom: 1.25rem; }
.pitch-section:last-child { margin-bottom: 0; }
.pitch-section-title {
  font-family: 'Big Shoulders Display', 'Arial Narrow', sans-serif;
  font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  font-size: 0.85rem; color: var(--pine); margin: 0 0 0.5rem;
}
.pitch-chip-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.pitch-chip {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  row-gap: 0.3rem;
  gap: 0.45rem;
  max-width: 100%;
  border: 1px solid transparent;
  border-radius: 100px;
  background: var(--surface-alt);
  color: var(--ink);
  padding: 0.5rem 1rem;
  font-family: 'Public Sans', -apple-system, sans-serif;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.pitch-chip:hover { border-color: var(--amber); }
.pitch-chip.selected { background: var(--amber); color: var(--on-amber); }
.pitch-chip .pc-meta { font-weight: 400; font-size: 0.78rem; opacity: 0.75; white-space: nowrap; }
.pitch-chip .pc-price { font-weight: 700; font-size: 0.85rem; white-space: nowrap; }
/* A visible reason a pitch is cheaper, not just a lower number next to it — its own colour
   (distinct from the amber "selected" state) so it reads as "why", not as another meta tag. */
.pitch-chip .pc-discount {
  font-weight: 600; font-size: 0.72rem; white-space: nowrap;
  background: var(--tint-good); color: var(--pine-light);
  border-radius: 100px; padding: 0.15rem 0.6rem;
}
.pitch-chip.selected .pc-discount { background: rgba(255,255,255,0.35); color: var(--on-amber); }

/* Photo strip for whichever pitch type is currently selected, above the pitch grid — the
   category's own photos (Sites > Categories > Edit / photos), not any one specific pitch. */
.category-photo-strip { display: flex; gap: 0.5rem; overflow-x: auto; margin-bottom: 0.75rem; padding-bottom: 0.2rem; }
.category-photo-strip img {
  height: 110px; width: auto; border-radius: var(--radius); object-fit: cover;
  cursor: zoom-in; flex-shrink: 0;
}

/* Step 2's "what type of pitch?" cards — bigger and fewer than a pitch chip (a handful of
   categories, not dozens of numbered pitches), each carrying a photo, description and a
   real from-price so this step earns being its own step rather than a bare dropdown. Photo
   sits beside the text, not stacked above it — a full-width banner photo pushed the price
   and description down where a quick side-by-side glance reads faster. A category with no
   photo uploaded yet just loses that column rather than leaving an empty gap. */
.category-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; }
.category-cards > * { min-width: 0; }
.category-card {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  background: var(--canvas-light);
  box-shadow: 0 1px 4px var(--shadow);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.category-card:hover { border-color: var(--amber); box-shadow: 0 4px 14px var(--shadow); transform: translateY(-2px); }
.category-card.selected { border-color: var(--amber); box-shadow: 0 0 0 2px var(--amber) inset; }
.category-card.unavailable { opacity: 0.55; cursor: default; }
.category-card.unavailable:hover { transform: none; box-shadow: 0 1px 4px var(--shadow); }
.category-card img { width: 130px; flex-shrink: 0; object-fit: cover; display: block; }
.category-card .cc-body { padding: 0.9rem 1.1rem; flex: 1; min-width: 0; }
.category-card h3 { margin: 0 0 0.3rem; font-size: 1.15rem; }
.category-card .cc-desc { font-size: 0.84rem; color: var(--muted); margin: 0 0 0.6rem; }
.category-card .cc-price { font-weight: 700; color: var(--pine); font-size: 1.05rem; margin: 0; }
.category-card .cc-select {
  margin: 0.55rem 0 0;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--amber);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.category-card.selected .cc-select { color: var(--pine); }
.category-card .cc-unavailable { color: var(--muted); font-size: 0.82rem; margin: 0; }

/* Site map sits above the pitch sections, full width — it's the visual reference; the
   sectioned chip list below it is the actual picker. */
.site-picker-layout { display: flex; flex-direction: column; gap: 1rem; }
#pitch-map-panel { width: 100%; }
#pitch-map-image-wrap { position: relative; line-height: 0; }
#pitch-map-image { width: 100%; max-height: 420px; object-fit: contain; background: var(--surface-alt); border-radius: var(--radius); border: 1px solid var(--line); display: block; }

/* Click-to-enlarge overlay for any pitch/category photo — one shared lightbox rather than
   per-photo modals, since only one can be open at a time anyway. */
.photo-lightbox {
  position: fixed; inset: 0; background: rgba(10,10,10,0.86); z-index: 1000;
  display: flex; align-items: center; justify-content: center; padding: 2rem; cursor: zoom-out;
}
.photo-lightbox img { max-width: 100%; max-height: 100%; border-radius: 6px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); }
.lightbox-nav {
  position: fixed; top: 50%; transform: translateY(-50%); z-index: 1001;
  background: rgba(255,255,255,0.15); color: #fff; border: none; border-radius: 100px;
  width: 2.75rem; height: 2.75rem; font-size: 1.6rem; line-height: 1; cursor: pointer;
}
.lightbox-nav:hover { background: rgba(255,255,255,0.3); }
.lightbox-prev { left: 1rem; }
.lightbox-next { right: 1rem; }
.lightbox-count {
  position: fixed; bottom: 1.25rem; left: 50%; transform: translateX(-50%);
  color: #fff; font-size: 0.8rem; background: rgba(0,0,0,0.4); padding: 0.2rem 0.7rem; border-radius: 100px;
}

/* Thumbnail on each pitch chip in step 3 — the pitch's own photo, so pitches of the same
   type are visually distinct rather than all reading as interchangeable text. */
.pc-thumb {
  width: 2.2rem; height: 2.2rem; border-radius: 6px; object-fit: cover;
  cursor: zoom-in; flex-shrink: 0; align-self: center;
  margin: -0.25rem 0 -0.25rem -0.4rem;
}

label { display: block; font-size: 0.85rem; font-weight: 500; margin: 0.75rem 0 0.3rem; color: var(--bark); }
input, select, textarea {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-family: 'Public Sans', -apple-system, sans-serif;
  font-size: 0.95rem;
  background: var(--field-bg);
  color: var(--ink);
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--amber); outline-offset: 1px; }

button.btn, a.btn {
  background: var(--amber);
  /* var(--on-amber), not var(--ink) — the button is amber in both themes (and can now be
     a custom accent colour too), so text needs to track what --amber actually resolves to,
     not the page's own theme. See --on-amber above. */
  color: var(--on-amber);
  border: none;
  padding: 0.7rem 1.3rem;
  border-radius: var(--radius);
  font-weight: 600;
  cursor: pointer;
  font-size: 0.95rem;
  display: inline-block;
  text-decoration: none;
}
button.btn:hover, a.btn:hover { background: var(--amber); }
button.btn:disabled { background: var(--line); color: var(--muted); cursor: not-allowed; }
/* A visible fill even at rest, not just a border — same reasoning as .subtab above: an
   outline-only button on a dark or custom-coloured card can read as barely-there, especially
   in dark mode where the border colour and card background sit close in tone. */
button.btn.secondary, a.btn.secondary { background: var(--surface-alt); border: 1px solid var(--pine); color: var(--pine); }
button.btn.danger, a.btn.danger { background: var(--ember); color: #fff; }

.price-summary { border-top: 1px dashed var(--line); margin-top: 1rem; padding-top: 0.75rem; }
.price-summary .row { display: flex; justify-content: space-between; font-family: 'IBM Plex Mono', monospace; font-size: 0.9rem; margin-bottom: 0.3rem; }
.price-summary .row.total { font-weight: 600; font-size: 1.05rem; color: var(--pine); }

.status-tag { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; padding: 0.2rem 0.55rem; border-radius: 100px; font-weight: 600; }
.status-confirmed { background: var(--tint-good); color: var(--pine-light); }
.status-pending_payment { background: var(--tint-warn); color: var(--amber); }
.status-cancelled { background: var(--tint-bad); color: var(--ember); }
.status-completed { background: var(--surface-alt); color: var(--bark); }

/* A literal little indicator light — check-in/check-out state at a glance on the Diary,
   without reading text, the way a physical panel would show it. Lit uses a glow (box-shadow),
   not just a colour swap, so "on" reads as genuinely lit rather than just a different dot. */
.led { display: inline-block; width: 9px; height: 9px; border-radius: 50%; vertical-align: middle; cursor: pointer; }
.led.on { background: var(--chart-paid); box-shadow: 0 0 4px 1px var(--chart-paid); }
.led.off { background: var(--line); }
.led:hover:not(.led-disabled) { box-shadow: 0 0 0 3px var(--tint-good); }
.led-disabled { cursor: default; opacity: 0.5; }

/* The dot itself is tiny — this is the actual click target around it (see
   checkStatusLedCell() in dashboard.js), padded well past the dot so a near-miss still
   toggles the light instead of falling through to the row underneath and opening the
   booking by mistake. */
.led-cell { text-align: center; padding: 0.5rem 0.7rem !important; cursor: pointer; }
.led-cell:has(.led-disabled) { cursor: default; }
.led-cell:hover { background: color-mix(in srgb, var(--pine) 8%, var(--canvas-light)); }

/* Capped rather than left to grow with the note's length — an unbounded note on the Diary
   tab widened its row enough to push the In/Out check-in controls off the visible edge of
   the screen. The full text is still reachable via the title tooltip, and one tap away
   regardless through the row's own click-through to the booking detail. */
.diary-notes-cell { max-width: 160px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.error-msg { color: var(--ember); font-size: 0.9rem; margin-top: 0.5rem; }
.success-msg { color: var(--pine-light); font-size: 0.9rem; margin-top: 0.5rem; }

table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
table th, table td { text-align: left; padding: 0.6rem 0.5rem; border-bottom: 1px solid var(--line); }
table th { font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--bark); }

/* ---- Shared data-table chrome: toolbar, sortable headers, columns picker, totals row,
   pagination — see dtRenderTable() in dashboard.js. ---- */
.dt-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.6rem; }
.dt-selection { font-size: 0.82rem; color: var(--muted); }
.dt-toolbar-actions { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }

table th.dt-sortable { cursor: pointer; user-select: none; }
table th.dt-sortable:hover { color: var(--pine); }

/* Opt-in per table via opts.onRowClick (Bookings only, so far) — the row itself is
   clickable to open its detail view, same affordance as a sortable header, while the
   buttons inside the actions cell keep working normally (see dtRenderTable's own
   event.target.closest carve-out) rather than every click double-firing. */
tr.dt-row-clickable { cursor: pointer; }
tr.dt-row-clickable:hover td { background: color-mix(in srgb, var(--pine) 8%, var(--canvas-light)); }

/* An ANPR-matched arrival (see anpr_highlight_arrivals) — the car has actually been seen
   arriving today, so this row is worth picking out from everyone else still expected. */
tr.diary-anpr-matched td { background: var(--tint-good); }
tr.diary-anpr-matched.dt-row-clickable:hover td { background: color-mix(in srgb, var(--tint-good) 70%, var(--pine) 12%); }

.dt-totals-row td { font-weight: 600; border-top: 2px solid var(--line); border-bottom: none; font-family: 'IBM Plex Mono', monospace; }

.dt-columns-picker { position: relative; }
.dt-columns-menu {
  position: absolute; right: 0; top: calc(100% + 0.3rem); z-index: 20;
  background: var(--canvas-light); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 4px 16px var(--shadow); padding: 0.6rem 0.8rem; min-width: 160px;
}
.dt-columns-menu label { display: flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; padding: 0.2rem 0; white-space: nowrap; }
.dt-columns-menu input[type="checkbox"] { width: auto; }

/* Scrollable table body: a visible (not native-invisible) scrollbar so overflow reads as
   "more to scroll" rather than content silently cut off at the edge. A min-width on the
   table itself matters just as much as the scrollbar — without it, `table { width:100% }`
   just keeps shrinking every column's content (wrapping text, squashing buttons) to fit
   the container instead of ever triggering real overflow, which is the actual mechanism
   behind the table looking "squeezed" rather than scrollable on a narrow screen. */
.dt-scroll { overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
.dt-scroll table { width: max-content; min-width: 100%; }
.dt-scroll::-webkit-scrollbar { height: 8px; }
.dt-scroll::-webkit-scrollbar-track { background: transparent; }
.dt-scroll::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }

/* Columns marked `secondary` (BOOKINGS_COLUMNS etc. in dashboard.js) fold away below the
   width they'd otherwise force the table past, so the essential columns and the actions
   stay reachable without scrolling on ordinary laptop widths. The columns picker can still
   bring one back on demand. */
@media (max-width: 1300px) {
  table th.dt-col-secondary, table td.dt-col-secondary { display: none; }
}

/* An actions cell with several stacked buttons (view/mark paid/refund/cancel) gets its own
   compact sizing so it doesn't dominate the table's width. Buttons stack via display:block,
   not a flex column — a flex formatting context nested inside a table cell measures its
   intrinsic size inconsistently between Chrome's table column-width pass and its final
   paint pass, which let this cell render past the table's own right edge instead of
   scrolling with it. Plain block-stacked buttons don't have that problem. */
.dt-actions-cell .btn { display: block; width: fit-content; margin-bottom: 0.3rem; padding: 0.35rem 0.6rem; font-size: 0.78rem; white-space: nowrap; }
.dt-actions-cell .btn:last-child { margin-bottom: 0; }

.dt-pager-wrap { margin-top: 0.75rem; }
.dt-pager { display: flex; justify-content: space-between; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.dt-pager-buttons { display: flex; gap: 0.35rem; flex-wrap: wrap; }
.dt-pager-buttons .btn { padding: 0.3rem 0.65rem; font-size: 0.82rem; }
.dt-pager-buttons .btn.secondary.active { background: var(--pine); color: var(--on-pine); }
.dt-pager-info { display: flex; align-items: center; gap: 0.6rem; font-size: 0.82rem; color: var(--muted); }
.dt-pager-info select { width: auto; padding: 0.2rem 0.4rem; }
.dt-pager-info label { display: flex; align-items: center; gap: 0.4rem; }

/* ---- Admin navigation ----
   24 sections is far too many for a row of pills across the top — it wrapped onto three
   lines and pushed the real content down the page. A grouped sidebar instead, which also
   makes it obvious which section you're in. */
/* Same dynamic idea as the base .wrap, just a bigger cap — the dashboard has a sidebar
   plus wide tables to fit, so it earns more width than the guest booking flow. */
body.admin-page .wrap { max-width: min(97vw, 1800px); }
.admin-shell { display: flex; gap: 1.25rem; align-items: flex-start; }

nav.tabs {
  flex: 0 0 190px;
  /* Sticks below the header now that the header itself is pinned (position:sticky,
     see header.topbar) rather than scrolling away — offset by its rendered height
     (~65px) plus a little headroom, not just 1rem, or the sidebar would tuck in
     underneath and start out partly hidden. */
  position: sticky; top: 4.75rem;
  max-height: calc(100vh - 5.75rem);
  display: flex; flex-direction: column;
  background: var(--canvas-light);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.6rem;
  box-shadow: 0 1px 4px var(--shadow);
}
/* The tab groups scroll on their own; the footer below stays pinned in view regardless of
   how long the tab list gets or how short the viewport is — logging out should never
   require scrolling to find. */
.nav-inner { flex: 1; min-height: 0; overflow-y: auto; }
.nav-footer {
  flex-shrink: 0;
  margin-top: 0.6rem; padding-top: 0.6rem;
  border-top: 1px solid var(--line);
}
.nav-footer .btn { width: 100%; }
.nav-group {
  margin-bottom: 0.9rem;
  padding-left: 0.5rem;
  border-left: 3px solid var(--line);
}
.nav-group:last-child { margin-bottom: 0; }
/* Each section gets its own colour, cycling through the brand palette, so the sidebar
   reads as distinct areas rather than one long flat list. The heading's own background is
   a soft tint of that same colour — not just the left border — so the section reads as one
   coloured block at a glance instead of the heading looking like plain grey text sitting
   above an unrelated coloured line. */
.nav-group:nth-child(1) { border-left-color: var(--pine); }
.nav-group:nth-child(1) .nav-group-title { background: color-mix(in srgb, var(--pine) 16%, transparent); color: var(--pine); }
.nav-group:nth-child(2) { border-left-color: var(--amber); }
.nav-group:nth-child(2) .nav-group-title { background: color-mix(in srgb, var(--amber) 16%, transparent); color: var(--amber); }
.nav-group:nth-child(3) { border-left-color: var(--ember); }
.nav-group:nth-child(3) .nav-group-title { background: color-mix(in srgb, var(--ember) 16%, transparent); color: var(--ember); }
.nav-group:nth-child(4) { border-left-color: var(--pine-light); }
.nav-group:nth-child(4) .nav-group-title { background: color-mix(in srgb, var(--pine-light) 16%, transparent); color: var(--pine-light); }
.nav-group:nth-child(5) { border-left-color: var(--bark); }
.nav-group:nth-child(5) .nav-group-title { background: color-mix(in srgb, var(--bark) 16%, transparent); color: var(--bark); }
.nav-group:nth-child(6) { border-left-color: var(--dusk); }
.nav-group:nth-child(6) .nav-group-title { background: color-mix(in srgb, var(--dusk) 16%, transparent); color: var(--dusk); }
.nav-group-title {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.09em;
  font-weight: 600;
  padding: 0.25rem 0.4rem;
  border-radius: 4px;
  margin-bottom: 0.3rem;
}
nav.tabs button {
  display: block; width: 100%; text-align: left;
  background: transparent; border: 0;
  padding: 0.4rem 0.5rem; margin-bottom: 1px;
  border-radius: 5px; cursor: pointer;
  font-size: 0.83rem; color: var(--ink);
  font-family: inherit;
}
nav.tabs button:hover { background: var(--surface-alt); }
nav.tabs button.active { background: var(--pine); color: var(--on-pine); font-weight: 600; }

/* min-width:0 stops the wide booking tables from squeezing the sidebar off screen */
.admin-content { flex: 1; min-width: 0; }

.nav-toggle { display: none; }
/* Always visible, unlike .nav-toggle above (mobile-only) — same hamburger-button treatment
   (size, colour, radius), just for the Help & Assistant drawer rather than the tab sidebar,
   since it's now the only way to reach either. */
.assistant-toggle {
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  width: 44px; height: 44px;
  background: var(--pine); color: var(--on-pine);
  border-radius: 8px; padding: 0;
  font-size: 1.15rem; min-height: 44px;
  position: relative; z-index: 201;
}

/* Help & Assistant drawer — mirrors nav.tabs' off-canvas pattern (.open + shared
   .nav-backdrop) but slides in from the right and is available at any viewport width, not
   just the mobile breakpoint. */
.side-drawer {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: 420px; max-width: 100vw;
  background: var(--surface);
  z-index: 200;
  transform: translateX(100%);
  transition: transform 0.2s ease;
  box-shadow: -2px 0 12px rgba(0,0,0,0.2);
  display: flex; flex-direction: column;
}
.side-drawer.open { transform: translateX(0); }
/* Unlike .nav-backdrop (mobile-only, since the left drawer only exists there), this drawer
   is available at every width, so its dimming overlay needs to apply everywhere too — not
   scoped to a media query. */
.drawer-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.35); z-index: 199; }
.side-drawer-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1rem 1.25rem; border-bottom: 1px solid var(--line); flex-shrink: 0;
}
.side-drawer-header h2 { margin: 0; }
.side-drawer-header button {
  width: auto; background: none; border: none; font-size: 1.6rem; line-height: 1;
  cursor: pointer; color: var(--muted); padding: 0.25rem 0.5rem;
}
.side-drawer-header button:hover { color: var(--ink); }
.side-drawer-subtabs { display: flex; gap: 0.5rem; padding: 0.75rem 1.25rem 0; flex-shrink: 0; }
.side-drawer-subtab {
  width: auto; background: var(--surface-alt); border: 1px solid var(--line);
  border-radius: 999px; padding: 0.35rem 0.9rem; font-size: 0.82rem; cursor: pointer;
  color: var(--muted);
}
.side-drawer-subtab.active { background: var(--pine); border-color: var(--pine); color: var(--on-pine); font-weight: 600; }
.side-drawer-body { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 1.25rem; }
.side-drawer-view { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; }
#assistant-drawer-chat #assistant-chat { display: flex; flex-direction: column; flex: 1; min-height: 0; }
#assistant-messages {
  flex: 1; overflow-y: auto; min-height: 150px;
  border: 1px solid var(--line); border-radius: 6px; padding: 0.75rem; background: var(--surface);
}
@media (max-width: 480px) { .side-drawer { width: 100vw; } }

/* Between the full sidebar and the drawer breakpoint, a laptop-width screen still gets a
   permanent sidebar but can't spare 190px for it once a wide table is on screen — narrow it
   and drop its label size a touch to give the content back some room. */
@media (max-width: 1150px) and (min-width: 901px) {
  nav.tabs { flex-basis: 155px; }
  nav.tabs button { font-size: 0.79rem; padding: 0.35rem 0.4rem; }
}

@media (max-width: 900px) {
  .admin-shell { display: block; }
  /* Slide-out drawer on small screens — a permanent column costs too much width */
  nav.tabs {
    position: fixed; top: 0; left: 0; bottom: 0;
    width: 230px; z-index: 200;
    border-radius: 0;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    box-shadow: 2px 0 12px rgba(0,0,0,0.2);
    max-height: 100vh;
  }
  nav.tabs.open { transform: translateX(0); }
  /* Sits inline in the header's toolbar row now (alongside search — see admin/index.html
     and .header-toolbar below), not floating fixed over the title. That freed up the
     header's top-left corner for the site logo and name, and means nothing needs to
     compensate with extra header/content padding to avoid it anymore. Only the drawer
     it opens (nav.tabs, above) still needs position:fixed, to lift it above the page. */
  .nav-toggle {
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    /* relative + z-index, not fixed — stays a normal flex item in the toolbar row (see
       admin/index.html), but still needs to sit above the open drawer (z-index 200) and
       its backdrop (199), since the drawer is position:fixed from top:0 and would
       otherwise cover this button, leaving no way to tap it again to close. */
    position: relative; z-index: 201;
    width: 44px; height: 44px;
    background: var(--pine); color: var(--on-pine);
    border-radius: 8px; padding: 0;
    font-size: 1.1rem; min-height: 44px;
  }
  .nav-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.35); z-index: 199; }
}

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
/* Grid items default to min-width:auto, not 0 — meaning a child with content that
   resists wrapping (a table cell with a long label, in practice) can force the whole
   grid, and everything containing it up to the page itself, wider than the viewport,
   since nothing in that chain clips overflow. Found live: "Booking revenue (last 30
   days)" in the Money-at-a-glance table did exactly this on a phone screen. This lets
   a card shrink and wrap its own content instead. */
.two-col > * { min-width: 0; }
@media (max-width: 640px) { .two-col { grid-template-columns: 1fr; } }

/* Manual booking form: stay details and guest details side by side instead of one long
   narrow column — the form has enough fields that stacked-and-capped-at-480px meant a lot
   of scrolling on a screen with plenty of spare width either side. */
.mb-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 0 2rem; align-items: start; }
.mb-columns > * { min-width: 0; }
@media (max-width: 900px) { .mb-columns { grid-template-columns: 1fr; gap: 0; } }

/* Theme switch on the public booking page — sits on the dark banner, so it needs its
   own contrast rather than the button styles meant for the light card background. */
.theme-toggle-btn {
  position: absolute;
  top: 1.1rem; right: 1.2rem;
  background: rgba(255,255,255,0.12);
  color: var(--hero-on);
  border: 1px solid rgba(255,255,255,0.35);
  border-radius: 100px;
  padding: 0.35rem 0.85rem;
  font-size: 0.8rem;
  font-family: 'Public Sans', -apple-system, sans-serif;
  cursor: pointer;
}
.theme-toggle-btn:hover { background: rgba(255,255,255,0.22); }
@media (max-width: 480px) {
  .theme-toggle-btn { top: 0.6rem; right: 0.6rem; padding: 0.3rem 0.65rem; font-size: 0.72rem; }
}

/* Nimbosoft's own mark, small and quiet, top-right of every page's hero banner — this is
   the platform provider's watermark, deliberately subtle so it never competes with an
   operator's own branding (Settings > Branding) on guest-facing pages. Where a page
   already carries the mark prominently next to its own title (platform.html, setup.html —
   pages that ARE Nimbosoft, not a tenant's site), this is skipped rather than doubled up. */
.topbar-brand-mark {
  position: absolute;
  top: 0.65rem; right: 0.9rem;
  height: 18px;
  width: auto;
  opacity: 0.85;
}
@media (max-width: 480px) {
  .topbar-brand-mark { top: 0.5rem; right: 0.55rem; height: 15px; }
}

/* public/index.html is the one page that carries BOTH the mark and the theme toggle —
   grouped in one flex row instead of two independently-guessed absolute offsets, so they
   sit side by side with a real gap regardless of the toggle's text width or screen size
   (the previous fixed-offset approach left them overlapping). */
.topbar-actions {
  position: absolute;
  top: 0.65rem; right: 0.9rem;
  display: flex; align-items: center; gap: 0.5rem;
}
.topbar-actions .topbar-brand-mark { position: static; top: auto; right: auto; }
.topbar-actions .theme-toggle-btn { position: static; top: auto; right: auto; }
@media (max-width: 480px) {
  .topbar-actions { top: 0.5rem; right: 0.55rem; gap: 0.35rem; }
  .topbar-actions .topbar-brand-mark { height: 15px; }
}

/* Electric hookup — a plain checkbox got lost next to the pitch-type dropdown, and it's
   one of the questions most likely to change which pitches are even offered, so it gets
   a full-width, unmissable toggle of its own instead. */
.electric-toggle {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  width: 100%;
  margin-top: 0.75rem;
  padding: 0.75rem 1rem;
  background: var(--surface-alt);
  border: 2px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  font-family: 'Public Sans', -apple-system, sans-serif;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink);
  text-align: left;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.electric-toggle:hover { border-color: var(--amber); }
.electric-toggle-icon { font-size: 1.3rem; line-height: 1; }
.electric-toggle-text { flex: 1; }
.electric-toggle-state {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 700;
  padding: 0.25rem 0.65rem;
  border-radius: 100px;
  background: var(--surface);
  color: var(--muted);
}
.electric-toggle[aria-pressed="true"] { background: var(--tint-warn); border-color: var(--amber); }
.electric-toggle[aria-pressed="true"] .electric-toggle-state { background: var(--amber); color: var(--on-amber); }

/* Custom calendar date-range picker — see renderCalendar() in booking.js. Replaces two
   native date inputs specifically because those can't grey out individual closed days. */
.date-range-field { position: relative; margin-bottom: 1rem; }
.drp-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
  width: 100%; text-align: left;
  background: var(--field-bg, var(--surface)); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 0.6rem 0.8rem; font-size: 0.95rem;
  cursor: pointer; color: var(--ink);
}
.drp-trigger:hover, .drp-trigger[aria-expanded="true"] { border-color: var(--amber); }
.drp-nights-badge {
  flex-shrink: 0; font-size: 0.78rem; font-weight: 600; color: var(--amber);
  background: color-mix(in srgb, var(--amber) 14%, transparent);
  border-radius: 999px; padding: 0.2rem 0.6rem; white-space: nowrap;
}
.drp-popover {
  position: absolute; z-index: 20; top: calc(100% + 0.4rem); left: 0;
  width: min(340px, 100%);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 6px 20px var(--shadow); padding: 0.9rem;
}
.drp-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.6rem; }
.drp-nav button {
  width: 32px; height: 32px; padding: 0; border-radius: 6px;
  background: var(--surface-alt); border: 1px solid var(--line); cursor: pointer; font-size: 1.1rem;
}
.drp-nav button:disabled { opacity: 0.35; cursor: default; }
.drp-month-label { font-family: 'Fraunces', serif; font-weight: 600; font-size: 1rem; }
.drp-weekdays, .drp-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.drp-weekdays span {
  text-align: center; font-size: 0.7rem; color: var(--muted); text-transform: uppercase;
  font-family: 'IBM Plex Mono', monospace; padding-bottom: 0.3rem;
}
.drp-day {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 6px; background: transparent; color: var(--ink);
  font-size: 0.85rem; cursor: pointer; font-family: 'IBM Plex Mono', monospace;
}
.drp-day:hover:not(:disabled) { background: var(--surface-alt); }
.drp-day:disabled { color: var(--line); cursor: default; text-decoration: line-through; }
.drp-day.drp-empty { visibility: hidden; }
.drp-day.drp-today { box-shadow: inset 0 0 0 1px var(--amber); }
.drp-day.drp-in-range { background: var(--tint-warn); border-radius: 0; }
.drp-day.drp-range-start { background: var(--amber); color: var(--on-amber); font-weight: 600; border-radius: 6px 0 0 6px; }
.drp-day.drp-range-end { background: var(--amber); color: var(--on-amber); font-weight: 600; border-radius: 0 6px 6px 0; }
.drp-day.drp-range-start.drp-range-end { border-radius: 6px; }
.drp-hint { font-size: 0.78rem; color: var(--muted); margin: 0.6rem 0 0; }
@media (max-width: 480px) { .drp-popover { width: min(300px, 92vw); } }

/* A real, unmissable call-out for returning guests — sits above the booking flow rather
   than as a small link inside the header, which is easy to skim past entirely. */
.manage-booking-banner {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 0.6rem;
  background: var(--surface-alt);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  margin-bottom: 1.25rem;
  font-size: 0.92rem;
}
.manage-booking-banner .btn { padding: 0.4rem 0.9rem; font-size: 0.85rem; }

/* Booking steps — one screen at a time so a first-time guest isn't faced with the whole
   form (pitch, extras, questions, payment) all at once. */
.stepper { display: flex; gap: 0.4rem; margin-bottom: 1.25rem; }
.step-dot {
  flex: 1;
  text-align: center;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding-bottom: 0.5rem;
  border-bottom: 3px solid var(--line);
}
.step-dot .num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--surface-alt);
  color: var(--bark);
  margin-right: 0.35rem;
  font-weight: 700;
}
.step-dot.active { color: var(--pine); border-bottom-color: var(--amber); }
.step-dot.active .num { background: var(--amber); color: var(--on-amber); }
.step-dot.done { color: var(--pine-light); border-bottom-color: var(--pine-light); }
.step-dot.done .num { background: var(--pine-light); color: #fff; }
@media (max-width: 640px) {
  .step-dot .label { display: none; }
  .step-dot { padding-bottom: 0.4rem; }
}

.step-nav { display: flex; justify-content: space-between; align-items: center; margin-top: 1.5rem; gap: 0.75rem; }

/* Calendar grid */
table.cal-table { border-collapse: collapse; font-size: 0.78rem; }
table.cal-table th, table.cal-table td { border: 1px solid var(--line); padding: 0.25rem 0.4rem; text-align: center; white-space: nowrap; }
table.cal-table th.cal-pitch-col, table.cal-table td.cal-pitch-col {
  position: sticky; left: 0; background: var(--canvas-light); text-align: left; z-index: 1; min-width: 110px;
}
table.cal-table td.cal-booked { background: var(--tint-good); color: var(--pine-light); font-weight: 500; cursor: grab; }
table.cal-table td.cal-booked:active { cursor: grabbing; }
table.cal-table td.cal-booked.cal-pending { background: var(--tint-warn); color: var(--amber); }
table.cal-table td.cal-free { background: var(--canvas-light); color: var(--line); }
table.cal-table th.cal-weekend { background: var(--surface-alt); }

/* Dashboard stat cards — headline figure plus tomorrow/weekend/week breakdown,
   mirroring the four-across layout of a commercial system's main dashboard. */
.dash-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
  margin-bottom: 1rem;
}
.dash-stat-grid > * { min-width: 0; } /* same grid-item overflow trap as .two-col above */
.dash-stat {
  background: var(--canvas-light);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
}
/* Opens today's printable list for that category (see statCard() in dashboard.js) — only
   the four with somewhere to actually go get the pointer/hover treatment. */
.dash-stat[role="button"] { cursor: pointer; transition: box-shadow 0.15s ease, transform 0.15s ease; }
.dash-stat[role="button"]:hover { box-shadow: 0 3px 10px var(--shadow); transform: translateY(-1px); }
.dash-stat[role="button"]:focus-visible { outline: 2px solid var(--pine); outline-offset: 2px; }
.dash-stat .label {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--bark);
}
.dash-stat .headline {
  font-family: 'Big Shoulders Display', 'Arial Narrow', sans-serif;
  font-size: 2.1rem;
  font-weight: 700;
  color: var(--pine);
  line-height: 1.1;
  margin: 0.2rem 0 0.5rem;
}
.dash-stat .breakdown {
  display: flex;
  gap: 0.9rem;
  border-top: 1px dashed var(--line);
  padding-top: 0.5rem;
}
.dash-stat .breakdown div { font-size: 0.72rem; color: var(--bark); }
.dash-stat .breakdown strong {
  display: block;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.95rem;
  color: var(--ink);
  font-weight: 600;
}

/* Each of the four headline stats gets its own tint from the existing palette (not a
   new accent), so the row reads at a glance rather than as four identical cards —
   the same tinted-fill language already used for status tags/rows, not a new pattern. */
.dash-stat.arrivals {
  background: color-mix(in srgb, var(--pine) 12%, var(--canvas-light));
  border-color: color-mix(in srgb, var(--pine) 30%, var(--line));
}
.dash-stat.arrivals .headline { color: var(--pine); }
.dash-stat.arrivals .breakdown { border-top-color: color-mix(in srgb, var(--pine) 30%, var(--line)); }

.dash-stat.departures {
  background: color-mix(in srgb, var(--dusk) 12%, var(--canvas-light));
  border-color: color-mix(in srgb, var(--dusk) 30%, var(--line));
}
.dash-stat.departures .headline { color: var(--dusk); }
.dash-stat.departures .breakdown { border-top-color: color-mix(in srgb, var(--dusk) 30%, var(--line)); }

.dash-stat.onsite {
  background: color-mix(in srgb, var(--clay) 14%, var(--canvas-light));
  border-color: color-mix(in srgb, var(--clay) 32%, var(--line));
}
.dash-stat.onsite .headline { color: var(--clay); }
.dash-stat.onsite .breakdown { border-top-color: color-mix(in srgb, var(--clay) 32%, var(--line)); }

.dash-stat.occupancy {
  background: color-mix(in srgb, var(--amber) 14%, var(--canvas-light));
  border-color: color-mix(in srgb, var(--amber) 32%, var(--line));
}
.dash-stat.occupancy .headline { color: var(--amber); }
.dash-stat.occupancy .breakdown { border-top-color: color-mix(in srgb, var(--amber) 32%, var(--line)); }

.dash-stat.owed {
  background: color-mix(in srgb, var(--ember) 12%, var(--canvas-light));
  border-color: color-mix(in srgb, var(--ember) 30%, var(--line));
  height: 100%;
}
.dash-stat.owed .headline { color: var(--ember); }
.dash-stat.owed .breakdown { border-top-color: color-mix(in srgb, var(--ember) 30%, var(--line)); }

/* Settings > Overview — launcher tiles for every settings screen, both the ones with their
   own sub-tab here (Branding, Booking policy, Guest WiFi, Staff & access, Your account) and
   the low-frequency ones folded out of the main sidebar (Statics, Channels, Plates, Reviews,
   Import/Export, System, Setup). */
.settings-tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0.75rem;
  margin-top: 0.75rem;
}
/* Groups the tile grid above into labelled sections (Property, Guest experience, etc.) so a
   long flat list of everything the app configures reads as a few short, findable lists instead. */
.settings-section-title {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.09em;
  font-weight: 600; color: var(--muted);
  margin: 1.25rem 0 0;
}
.settings-section-title:first-of-type { margin-top: 0.75rem; }
.settings-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
  text-align: left;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  cursor: pointer;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
  width: auto;
}
.settings-tile:hover { box-shadow: 0 3px 10px var(--shadow); transform: translateY(-1px); }
.settings-tile:focus-visible { outline: 2px solid var(--pine); outline-offset: 2px; }
.settings-tile strong { font-family: 'Fraunces', serif; font-size: 0.98rem; }
.settings-tile span { font-size: 0.78rem; color: var(--muted); }

/* Today's customizable dashboard widgets — a 2-column grid; each widget spans 1 or 2
   columns per its data-width, reorderable/resizable while .editing (see
   initDashboardCustomize() in dashboard.js). */
.dash-widget-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  align-items: stretch; /* a pair in the same row matches height, not each growing on its own */
}
.dash-widget { grid-column: span 1; min-width: 0; display: flex; flex-direction: column; }
.dash-widget[data-width="2"] { grid-column: span 2; }
.dash-widget > .card,
.dash-widget > [id$="-card"] {
  margin-bottom: 0; /* the grid's own gap replaces .card's margin-bottom */
  width: 100%;
  flex: 1; /* fills the row's stretched height, so a shorter card still matches its taller row-mate */
}

/* A widget with a lot to show (a long list, a full week of arrivals) scrolls inside its
   own card once it hits a sensible height, rather than growing the card — and its
   row-mate's height along with it — down the page. "See more" is a click into the full
   page (Bookings, Diary, the booking itself), same as Outstanding balances and Latest
   bookings already do; the dashboard box only ever needs to show a preview. */
#dash-coming-days, #dash-money, #dash-latest, #dash-recent-emails,
#arrivals-list, #departures-list {
  max-height: 360px;
  overflow-y: auto;
}

.dash-widget-toolbar { display: none; }
.dash-widget-grid.editing .dash-widget {
  outline: 1px dashed color-mix(in srgb, var(--pine) 40%, transparent);
  outline-offset: 4px;
  border-radius: var(--radius);
}
.dash-widget-grid.editing .dash-widget-toolbar {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  margin-bottom: 0.4rem;
}
.dash-widget-toolbar button {
  width: auto;
  padding: 0.2rem 0.55rem;
  font-size: 0.76rem;
  background: var(--canvas-light);
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
  color: var(--bark);
  font-family: 'IBM Plex Mono', monospace;
}
.dash-widget-toolbar button:hover { border-color: var(--pine); color: var(--pine); }
.dash-widget-toolbar button:disabled { opacity: 0.35; cursor: default; }
.dash-widget-toolbar button:disabled:hover { border-color: var(--line); color: var(--bark); }

@media (max-width: 720px) {
  .dash-widget-grid { grid-template-columns: 1fr; }
  .dash-widget[data-width="2"] { grid-column: span 1; }
}

/* Occupancy bar used in the coming-days strip */
.occ-bar {
  height: 6px;
  background: var(--surface-alt);
  border-radius: 100px;
  overflow: hidden;
  margin-top: 3px;
}
.occ-bar span { display: block; height: 100%; background: var(--pine-light); border-radius: 100px; }
.occ-bar span.high { background: var(--ember); }
.occ-bar span.mid { background: var(--amber); }

/* Blocked pitches — deliberately taken out of use by staff (maintenance, owner use, etc).
   Visually distinct from "booked" so it's obvious at a glance which is which. */
table.cal-table td.cal-blocked {
  background: repeating-linear-gradient(45deg, var(--surface-alt), var(--surface-alt) 4px, var(--line) 4px, var(--line) 8px);
  color: var(--bark);
  font-weight: 700;
  cursor: pointer;
}
/* ---- Mobile / tablet ----
   The dashboard is used on a phone at the gate as much as on a desktop in the office,
   so tables scroll horizontally rather than squashing, tap targets grow, and the
   multi-column layouts collapse to single column. */
@media (max-width: 820px) {
  body { font-size: 15px; }
  .wrap { padding: 0.75rem; }
  .card { padding: 0.9rem; }
  .two-col { grid-template-columns: 1fr !important; }
  .dash-stat-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
  .dash-stat .headline { font-size: 1.7rem; }

  /* Tables scroll sideways instead of crushing columns to unreadable widths */
  .card table { display: block; overflow-x: auto; white-space: nowrap; }

  /* Bigger tap targets — 44px is the usual minimum for reliable finger taps */
  button.btn { padding: 0.75rem 1.1rem; min-height: 44px; }
  input, select, textarea { padding: 0.7rem; font-size: 16px; } /* 16px stops iOS zooming on focus */
  nav.tabs { gap: 0.4rem; }
  nav.tabs button { padding: 0.55rem 0.85rem; min-height: 40px; }

  header.topbar { padding: 0.55rem 0.7rem 1rem 0.7rem; }
  header.topbar h1 { font-size: 0.92rem; }
  .site-logo { height: 20px; }
  .site-brand-row { gap: 0.4rem; }

  /* Charts on a phone: the pitch column shrinks further so more days fit without
     scrolling, while day cells stay close to their desktop size — they're what's
     actually being tapped and read. */
  .chart-pitch { width: 42px; max-width: 42px; padding: 0 0.25rem; }
  .chart-day { width: 30px; min-width: 30px; height: 28px; }
  .chart-bar { font-size: 0.64rem; line-height: 24px; }
}

@media (max-width: 480px) {
  .dash-stat-grid { grid-template-columns: 1fr 1fr; }
  .dash-stat { padding: 0.75rem; }
  .dash-stat .headline { font-size: 1.5rem; }
  .dash-stat .breakdown { gap: 0.5rem; }
  .dash-stat .breakdown div { font-size: 0.66rem; }
}

/* Admin header: title on the left, search + quick actions on the right, one row — keeps
   the header the same height it always was rather than growing a second row underneath. */
.admin-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.admin-header-title { min-width: 0; } /* lets the title/subtitle truncate instead of forcing the row to wrap */

.header-toolbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  /* Pulled in from the edge so it doesn't crowd the Nimbosoft mark, which sits absolutely
     positioned in the header's top-right corner (see .topbar-brand-mark) rather than in
     this flex row. Reset to 0 once the toolbar wraps onto its own row below the title,
     where the mark is no longer beside it. */
  margin-right: 2.2rem;
}
.header-search { position: relative; flex: 1 1 160px; max-width: 300px; }
.header-search input {
  width: 100%;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.4);
  color: var(--hero-on);
  border-radius: 100px;
  padding: 0.4rem 0.9rem;
  font-family: 'Public Sans', -apple-system, sans-serif;
  font-size: 0.85rem;
}
.header-search input::placeholder { color: var(--hero-on); opacity: 0.68; }
.header-search input:focus { outline: 2px solid rgba(255,255,255,0.6); outline-offset: 1px; }
.header-search .autocomplete-results { color: var(--ink); }
/* Smaller than the site-wide default — this button sits in a header that's now
   deliberately compact, and the full-size button was the one element in it that hadn't
   shrunk to match. */
.header-toolbar .btn { padding: 0.4rem 0.9rem; font-size: 0.85rem; }
@media (max-width: 640px) {
  .admin-header-row { justify-content: flex-start; }
  /* Kept as one row with the search bar (not wrapped onto its own full-width row below) —
     the icon-only button next to it is small enough that there's no need to give up the
     row, and stacking cost more header height than a phone screen can spare. */
  .header-toolbar { width: 100%; margin-top: 0.45rem; margin-right: 0; flex-wrap: nowrap; }
  .header-search { flex: 1 1 auto; max-width: none; }
  /* Icon-only: the label would either force a second row or truncate unreadably at this
     width. The tooltip/aria-label still carry the label for anyone who needs it. */
  #header-new-booking-btn .btn-label { display: none; }
  #header-new-booking-btn { flex-shrink: 0; padding: 0.6rem 0.85rem; min-width: 44px; }
}

/* Autocomplete dropdown for finding existing customers when taking a booking */
.autocomplete-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 50;
  background: var(--canvas-light);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  max-height: 260px;
  overflow-y: auto;
}
.autocomplete-results .ac-item {
  padding: 0.55rem 0.7rem;
  cursor: pointer;
  border-bottom: 1px solid var(--line);
}
.autocomplete-results .ac-item:last-child { border-bottom: none; }
.autocomplete-results .ac-item:hover,
.autocomplete-results .ac-item.active { background: var(--surface-alt); }
.autocomplete-results .ac-name { font-weight: 600; }
.autocomplete-results .ac-meta { font-size: 0.78rem; color: var(--bark); }
.linked-customer {
  background: var(--tint-good);
  border: 1px solid var(--pine-light);
  border-radius: var(--radius);
  padding: 0.6rem 0.75rem;
  margin-top: 0.5rem;
  font-size: 0.85rem;
}

table.cal-table td.cal-owing {
  background: var(--tint-warn);
  color: var(--amber);
  font-weight: 700;
}
table.cal-table td.cal-overdue {
  background: var(--tint-bad);
  color: var(--ember);
  font-weight: 700;
}
/* Solid fill rather than a soft tint — nothing paid at all reads as visually heavier
   than "just the balance left", the same escalation the charts use. */
table.cal-table td.cal-owing-all {
  background: var(--chart-owing-all);
  color: var(--chart-text);
  font-weight: 700;
}
table.cal-table td.cal-overdue-all {
  background: var(--chart-overdue-all);
  color: var(--chart-text);
  font-weight: 700;
}


/* ---- Charts: whole-site booking grid ----
   A wide scrolling grid: pitch rows down the side, days across the top, booking bars
   spanning their dates. Sticky headers so the pitch name and date stay visible while
   scrolling a full year. */
.chart-table { border-collapse: separate; border-spacing: 0; font-size: 0.72rem; }
.chart-table th, .chart-table td { border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }

.chart-table thead th {
  position: sticky; top: 0; z-index: 3;
  background: var(--canvas-light);
  font-family: 'IBM Plex Mono', monospace;
  font-weight: 600; font-size: 0.66rem;
  padding: 2px 0; text-align: center; white-space: nowrap;
}
.chart-table thead tr:first-child th { top: 0; }
.chart-table thead tr:nth-child(2) th { top: 20px; }

/* Pitch name column stays put when scrolling sideways. Fixed narrow width (rather than
   sizing to the longest name) so the booking bars — the thing actually being read — get
   the space instead; long names still reach the reader via the title tooltip. */
.chart-pitch {
  position: sticky; left: 0; z-index: 2;
  background: var(--canvas-light);
  font-family: 'IBM Plex Mono', monospace;
  font-weight: 600;
  padding: 0 0.35rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-right: 2px solid var(--line);
  width: 56px; max-width: 56px;
}
.chart-table thead .chart-pitch { z-index: 4; }

/* --bark rather than --pine: pine is the same green family as --chart-paid, which sits in
   this same table — bark is a distinct muted hue in every theme, so it reads as a structural
   divider rather than looking like another (or the same) booking state. */
.chart-category-row td {
  background: var(--bark); color: var(--on-bark);
  font-family: 'Big Shoulders Display', 'Arial Narrow', sans-serif; font-weight: 700;
  padding: 0.25rem 0.5rem; position: sticky; left: 0;
}

.chart-day { width: 34px; min-width: 34px; height: 26px; padding: 0; position: relative; }
.chart-day.chart-weekend { background: rgba(44, 85, 64, 0.06); }
.chart-day.chart-today { box-shadow: inset 2px 0 0 var(--ember); }
.chart-day:hover { background: rgba(198, 142, 23, 0.15); cursor: crosshair; }

/* Booking bars sit on top of the day cells */
.chart-bar {
  position: absolute; top: 2px; bottom: 2px; left: 1px;
  border-radius: 3px; padding: 0 4px;
  font-size: 0.7rem; line-height: 22px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: grab; z-index: 1; color: #fff;
  box-shadow: 0 1px 2px rgba(0,0,0,0.25);
}
.chart-bar:hover { filter: brightness(1.1); z-index: 5; }
.chart-bar:active { cursor: grabbing; }
.chart-bar.dragging { opacity: 0.45; }

/* The release ("✕") button on a held/unconfirmed bar only appears on hover — hidden the rest
   of the time so a normal chart full of holds doesn't look cluttered with buttons. */
.chart-bar-label { pointer-events: none; }
.chart-bar-release {
  display: none;
  position: absolute; top: 1px; right: 1px; bottom: 1px;
  width: 16px; padding: 0; margin: 0;
  border: 0; border-radius: 3px;
  background: rgba(0,0,0,0.35); color: #fff;
  font-size: 0.7rem; line-height: 1; cursor: pointer;
}
.chart-bar-release:hover { background: rgba(0,0,0,0.6); }
.chart-unconfirmed:hover .chart-bar-release { display: block; }

.chart-paid       { background: var(--chart-paid); color: var(--chart-text); }
.chart-owing      { background: var(--chart-owing); color: #201C16; }
.chart-owing-all  { background: var(--chart-owing-all); color: var(--chart-text); }
.chart-overdue    { background: var(--chart-overdue); color: var(--chart-text); }
.chart-overdue-all{ background: var(--chart-overdue-all); color: var(--chart-text); }
.chart-unconfirmed{ background: var(--chart-unconfirmed); color: var(--chart-text); }
.chart-pending    { background: var(--chart-pending); color: var(--chart-text); }
.chart-blocked    { background: repeating-linear-gradient(45deg,var(--chart-blocked-a),var(--chart-blocked-a) 4px,var(--chart-blocked-b) 4px,var(--chart-blocked-b) 8px); color: var(--ink); }
.chart-external   { background: var(--chart-external); color: var(--chart-text); }

.chart-key {
  display: inline-block; width: 14px; height: 14px;
  border-radius: 3px; vertical-align: middle; margin-right: 3px;
}

/* Where a booking can be dropped */
.chart-day.drop-ok { background: rgba(44, 85, 64, 0.3); }
.chart-day.drop-bad { background: rgba(181, 72, 44, 0.3); }

/* Charts on a large monitor: there's room to spare, so give it to the booking bars —
   wider, taller cells make guest names and date boundaries easier to read at a glance
   instead of squinting at a grid sized for a laptop. */
@media (min-width: 1400px) {
  .chart-pitch { width: 76px; max-width: 76px; }
  .chart-day { width: 46px; min-width: 46px; height: 32px; }
  .chart-bar { font-size: 0.78rem; line-height: 28px; padding: 0 6px; }
  .chart-table { font-size: 0.78rem; }
  .chart-table thead th { font-size: 0.7rem; }
}

/* --- Seasons: paint-the-year calendar, Rules table, rate grid --- */
/* Same pill-toggle look reused generically (.subtabs/.subtab) wherever a tab has too many
   unrelated settings/cards to show at once — Settings and Setup below both use the generic
   version and its shared click handler; Seasons keeps its own bespoke one since switching
   sub-view there also re-renders the Rules table. */
.season-subtabs, .subtabs { display: flex; gap: 0.25rem; flex-wrap: wrap; margin-bottom: 1rem; }
.season-subtab, .subtab {
  /* A visible fill even at rest, not just a border — an outline-only button can blend
     into a custom page background the theme-contrast check elsewhere can't fully guard
     against for every combination, and it shouldn't take clicking a button to see it. */
  background: var(--surface-alt); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.35rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.78rem;
  color: var(--muted); cursor: pointer;
}
.season-subtab.active, .subtab.active { background: var(--pine); color: var(--on-pine); border-color: var(--pine); font-weight: 600; }

.season-pill {
  --pill-color: #5B6154;
  display: inline-flex; align-items: center; gap: 0.4rem;
  background: var(--surface); border: 2px solid var(--line); border-radius: 999px;
  padding: 0.3rem 0.7rem 0.3rem 0.5rem; font-size: 0.82rem; font-weight: 600; color: var(--ink);
  cursor: pointer;
}
.season-pill.armed { border-color: var(--pill-color); box-shadow: 0 0 0 2px color-mix(in srgb, var(--pill-color) 30%, transparent); }
.season-pill-swatch { width: 14px; height: 14px; border-radius: 50%; background: var(--pill-color); display: inline-block; }
.season-pill-edit { opacity: 0.5; font-size: 0.9rem; padding: 0 0.1rem; }
.season-pill-edit:hover { opacity: 1; }
.season-pill.eraser { --pill-color: transparent; color: var(--muted); }
.season-pill.eraser .season-pill-swatch { background: var(--surface-alt); border: 1px dashed var(--line); }

.season-tag {
  display: inline-block; padding: 0.15rem 0.55rem; border-radius: 999px;
  color: #fff; font-size: 0.75rem; font-weight: 600;
}

table.season-calendar { border-collapse: separate; border-spacing: 0; font-size: 0.72rem; }
table.season-calendar th, table.season-calendar td { border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.cal-month-label {
  position: sticky; left: 0; z-index: 2; background: var(--canvas-light);
  font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--bark); padding: 0 0.6rem; white-space: nowrap; text-align: left;
}
.cal-dow {
  position: sticky; top: 0; z-index: 1; background: var(--canvas-light);
  font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; color: var(--muted);
  width: 22px; min-width: 22px; height: 22px; text-align: center; font-weight: 500;
}
.cal-day {
  width: 22px; min-width: 22px; height: 24px; text-align: center; font-size: 0.68rem;
  cursor: pointer; user-select: none; color: var(--ink);
}
.cal-day:hover { outline: 2px solid var(--amber); outline-offset: -2px; }
.cal-day-empty { background: var(--surface-sunken); }

.day-toggle-row { display: flex; gap: 2px; }
.day-toggle {
  width: 28px; height: 24px; border-radius: 3px; border: 1px solid var(--line);
  background: var(--surface); color: var(--muted); font-size: 0.66rem; cursor: pointer; padding: 0;
}
/* Arrival (pine/green) vs departure (dusk/blue) — same colour split the Today dashboard
   already uses for arrivals/departures, so the two day-toggle rows read as distinct
   controls even before anything's selected, not one ambiguous block shown twice. */
.day-toggle.arrival:hover { border-color: var(--pine); }
.day-toggle.arrival.active { background: var(--pine); border-color: var(--pine); color: var(--on-pine); font-weight: 600; }
.day-toggle.departure:hover { border-color: var(--dusk); }
.day-toggle.departure.active { background: var(--dusk); border-color: var(--dusk); color: var(--on-dusk); font-weight: 600; }
.day-toggle-actions { display: flex; gap: 0.5rem; margin-top: 0.25rem; }
.day-toggle-actions a { font-size: 0.72rem; color: var(--muted); text-decoration: underline; cursor: pointer; }
.day-toggle-actions a:hover { color: var(--ink); }

table.rate-grid-table { border-collapse: collapse; width: 100%; }
table.rate-grid-table th, table.rate-grid-table td { padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--line); text-align: left; }
table.rate-grid-table input[type="number"] { width: 6rem; }
