/*
Theme Name: Cornerstone
Text Domain: cornerstone
Version: 0.1.0
Requires PHP: 8.1
*/

/* =========================================================================
   FONTS
   ========================================================================= */
@font-face {
  font-family: 'Inter Variable';
  src: url('assets/fonts/inter-latin-wght-normal.woff2') format('woff2-variations'),
       url('assets/fonts/inter-latin-wght-normal.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter Variable';
  src: url('assets/fonts/inter-latin-ext-wght-normal.woff2') format('woff2-variations'),
       url('assets/fonts/inter-latin-ext-wght-normal.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Metric-matched fallbacks so the swap to Inter moves nothing. Inter is
   font-display: swap and lands ~2 s into a slow-4G load; text set in the
   system font first reflows when it arrives, and with the tour intro now
   painting from the first frame (HO-084 item 1) that reflow shifted
   .tour__content by CLS 0.145 in lab (guardrail 0.1). The overrides scale the
   local face to Inter's advance widths and vertical metrics. Values computed
   from @capsizecss/metrics 4.2.0 (unitsPerEm / ascent / descent / lineGap /
   xWidthAvg: Inter 2048/1984/494/0/978, Roboto 2048/1900/500/0/911, Arial
   2048/1854/434/67/913): size-adjust = Inter xWidthAvg / fallback xWidthAvg,
   ascent-override = 1984 / 2048 / size-adjust, descent likewise from 494.
   Roboto is listed FIRST in --font: on Android a local('Arial') lookup can
   alias to Roboto and would take Arial's numbers; on iOS, macOS and Windows
   local('Roboto') simply fails to match and Arial is next. */
@font-face {
  font-family: 'Inter Fallback Roboto';
  src: local('Roboto');
  size-adjust: 107.35%;
  ascent-override: 90.24%;
  descent-override: 22.47%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'Inter Fallback Arial';
  src: local('Arial');
  size-adjust: 107.12%;
  ascent-override: 90.44%;
  descent-override: 22.52%;
  line-gap-override: 0%;
}

/* =========================================================================
   DESIGN TOKENS
   ========================================================================= */
:root {
  --ink-900: #091B20;
  --ink-800: #0D2E37;
  --ink-700: #123844;
  --line: rgba(255, 255, 255, .10);
  --line-strong: rgba(255, 255, 255, .2);
  --fg: #fff;
  --fg-rgb: 255, 255, 255;
  --fg-72: rgba(255, 255, 255, .72);
  --fg-60: rgba(255, 255, 255, .6);
  --fg-40: rgba(255, 255, 255, .4);
  --accent: #F2A93B;
  --accent-deep: #D9891F;
  --ink-900-rgb: 9, 27, 32;      /* for gradients/alpha of the ground */
  --accent-rgb: 242, 169, 59;    /* for glows */
  --font: 'Inter Variable', Inter, 'Inter Fallback Roboto', 'Inter Fallback Arial', system-ui, sans-serif;
  --gutter: 2.5rem;
  --header-h: 72px;

  --h1: clamp(4.5rem, 12vw, 11.875rem);
  --h2: clamp(2.5rem, 5vw, 4.5rem);
  --h3: clamp(1.5rem, 2.2vw, 2rem);
  --h4: clamp(1.25rem, 1.8vw, 1.75rem);
  --body-xxl: 1.5rem;
  --body-lg: 1.125rem;
  --body: 1rem;
  --body-sm: .875rem;
  --label: .8125rem;

  --radius-img: 4px;
  --radius-pill: 999px;

  --ease-out: power3.out;
}

/* =========================================================================
   PALETTES — reversible. The :root values above are "harbor" (the current
   teal-ink + amber). Each block below overrides only the color tokens; select
   one with html[data-palette] (functions.php: CST_PALETTE constant, or locally
   ?palette=name which sticks in a cookie). Everything else in the build is
   palette-agnostic, so switching back is deleting the attribute.
   ========================================================================= */
html[data-palette="midnight"] {          /* navy ink, gold accent — more "federal" */
  --ink-900: #0A1020; --ink-800: #101A33; --ink-700: #172444;
  --ink-900-rgb: 10, 16, 32;
  --accent: #E6B94A; --accent-deep: #C99A2A; --accent-rgb: 230, 185, 74;
}
html[data-palette="forest"] {            /* deep green ink, warm sand accent — the current green, pushed */
  --ink-900: #0A1A14; --ink-800: #0F261D; --ink-700: #153428;
  --ink-900-rgb: 10, 26, 20;
  --accent: #E3B97C; --accent-deep: #C99A55; --accent-rgb: 227, 185, 124;
}
html[data-palette="granite"] {           /* neutral charcoal, brass accent — most restrained */
  --ink-900: #0F1113; --ink-800: #17191D; --ink-700: #202329;
  --ink-900-rgb: 15, 17, 19;
  --accent: #D6AC5E; --accent-deep: #B88E3F; --accent-rgb: 214, 172, 94;
}
html[data-palette="oxblood"] {           /* deep burgundy ink, pale gold accent — the most distinctive */
  --ink-900: #190D11; --ink-800: #24141A; --ink-700: #321C24;
  --ink-900-rgb: 25, 13, 17;
  --accent: #E8C47C; --accent-deep: #CBA256; --accent-rgb: 232, 196, 124;
}
html[data-palette="potomac"] {           /* steel blue-gray ink, copper accent — cooler ground, warmer metal */
  --ink-900: #101820; --ink-800: #17222D; --ink-700: #1F2E3C;
  --ink-900-rgb: 16, 24, 32;
  --accent: #D98F5C; --accent-deep: #B76F3D; --accent-rgb: 217, 143, 92;
}
html[data-palette="plum"] {              /* deep aubergine ink, rose-gold accent — richest of the set */
  --ink-900: #160F1C; --ink-800: #211729; --ink-700: #2C2038;
  --ink-900-rgb: 22, 15, 28;
  --accent: #E2B491; --accent-deep: #C6935F; --accent-rgb: 226, 180, 145;
}
html[data-palette="espresso"] {          /* warm brown-black ink, cream-gold accent — candlelit */
  --ink-900: #16100A; --ink-800: #221A11; --ink-700: #2F2418;
  --ink-900-rgb: 22, 16, 10;
  --accent: #E7C689; --accent-deep: #C9A25C; --accent-rgb: 231, 198, 137;
}
html[data-palette="iron"] {              /* cool graphite ink, verdigris accent — the dark cousin of marble */
  --ink-900: #101416; --ink-800: #181E21; --ink-700: #222A2E;
  --ink-900-rgb: 16, 20, 22;
  --accent: #52B8A0; --accent-deep: #3A9682; --accent-rgb: 82, 184, 160;
}
html[data-palette="ember"] {             /* indigo-charcoal ink, coral accent — the boldest pairing */
  --ink-900: #0F1119; --ink-800: #161927; --ink-700: #1F2336;
  --ink-900-rgb: 15, 17, 25;
  --accent: #EF8E63; --accent-deep: #D06F42; --accent-rgb: 239, 142, 99;
}
html[data-palette="marble"] {            /* LIGHT: marble ground, ink text, verdigris accent — experimental */
  --ink-900: #F3F4F2; --ink-800: #FFFFFF; --ink-700: #E4E6E2;
  --ink-900-rgb: 243, 244, 242;
  --fg: #21262B; --fg-rgb: 33, 38, 43;
  --fg-72: rgba(33, 38, 43, .78); --fg-60: rgba(33, 38, 43, .64); --fg-40: rgba(33, 38, 43, .44);
  --line: rgba(33, 38, 43, .12); --line-strong: rgba(33, 38, 43, .24);
  --accent: #1F6F63; --accent-deep: #185C52; --accent-rgb: 31, 111, 99;
}
html[data-palette="daylight"] {         /* LIGHT: sea-glass ground, harbor-teal ink, bronze accent */
  --ink-900: #EDF2F3; --ink-800: #FFFFFF; --ink-700: #DEE7E9;
  --ink-900-rgb: 237, 242, 243;
  --fg: #16333C; --fg-rgb: 22, 51, 60;
  --fg-72: rgba(22, 51, 60, .80); --fg-60: rgba(22, 51, 60, .70); --fg-40: rgba(22, 51, 60, .48);
  --line: rgba(22, 51, 60, .12); --line-strong: rgba(22, 51, 60, .24);
  --accent: #8E5E06; --accent-deep: #6F4A05; --accent-rgb: 142, 94, 6;
}
html[data-palette="limestone"] {        /* LIGHT: warm stone ground, navy ink, brass accent */
  --ink-900: #F2F0EA; --ink-800: #FFFFFF; --ink-700: #E6E2D8;
  --ink-900-rgb: 242, 240, 234;
  --fg: #1B2540; --fg-rgb: 27, 37, 64;
  --fg-72: rgba(27, 37, 64, .80); --fg-60: rgba(27, 37, 64, .70); --fg-40: rgba(27, 37, 64, .48);
  --line: rgba(27, 37, 64, .12); --line-strong: rgba(27, 37, 64, .24);
  --accent: #7C5F1D; --accent-deep: #635017; --accent-rgb: 124, 95, 29;
}
html[data-palette="sage"] {             /* LIGHT: green-gray ground, forest ink, clay accent */
  --ink-900: #EDF1E9; --ink-800: #FFFFFF; --ink-700: #DDE3D6;
  --ink-900-rgb: 237, 241, 233;
  --fg: #1C2A21; --fg-rgb: 28, 42, 33;
  --fg-72: rgba(28, 42, 33, .80); --fg-60: rgba(28, 42, 33, .70); --fg-40: rgba(28, 42, 33, .48);
  --line: rgba(28, 42, 33, .12); --line-strong: rgba(28, 42, 33, .24);
  --accent: #96461F; --accent-deep: #7A3917; --accent-rgb: 150, 70, 31;
}
html[data-palette="sky"] {              /* LIGHT: powder-blue ground, navy ink, bronze accent */
  --ink-900: #D8E6F2; --ink-800: #E9F1F8; --ink-700: #C3D7E8;
  --ink-900-rgb: 216, 230, 242;
  --fg: #142A3D; --fg-rgb: 20, 42, 61;
  --fg-72: rgba(20, 42, 61, .80); --fg-60: rgba(20, 42, 61, .74); --fg-40: rgba(20, 42, 61, .50);
  --line: rgba(20, 42, 61, .12); --line-strong: rgba(20, 42, 61, .24);
  --accent: #8A5C14; --accent-deep: #6D4910; --accent-rgb: 138, 92, 20;
}
html[data-palette="sandstone"] {        /* LIGHT: warm sand ground, espresso ink, rust accent */
  --ink-900: #EBDCC3; --ink-800: #F5EBD9; --ink-700: #DECBA8;
  --ink-900-rgb: 235, 220, 195;
  --fg: #2F2312; --fg-rgb: 47, 35, 18;
  --fg-72: rgba(47, 35, 18, .80); --fg-60: rgba(47, 35, 18, .74); --fg-40: rgba(47, 35, 18, .50);
  --line: rgba(47, 35, 18, .12); --line-strong: rgba(47, 35, 18, .24);
  --accent: #9C4A14; --accent-deep: #7C3B10; --accent-rgb: 156, 74, 20;
}
html[data-palette="blossom"] {          /* LIGHT: cherry-blossom pink ground, plum ink, wine accent */
  --ink-900: #F2DCD8; --ink-800: #F9EBE8; --ink-700: #E6C6C0;
  --ink-900-rgb: 242, 220, 216;
  --fg: #3A1F26; --fg-rgb: 58, 31, 38;
  --fg-72: rgba(58, 31, 38, .80); --fg-60: rgba(58, 31, 38, .74); --fg-40: rgba(58, 31, 38, .50);
  --line: rgba(58, 31, 38, .12); --line-strong: rgba(58, 31, 38, .24);
  --accent: #8C2F43; --accent-deep: #712636; --accent-rgb: 140, 47, 67;
}
html[data-palette="wheat"] {            /* LIGHT: wheat-gold ground, umber ink, sienna accent */
  --ink-900: #F0E4BE; --ink-800: #F8F0D8; --ink-700: #E3D19F;
  --ink-900-rgb: 240, 228, 190;
  --fg: #33290F; --fg-rgb: 51, 41, 15;
  --fg-72: rgba(51, 41, 15, .80); --fg-60: rgba(51, 41, 15, .74); --fg-40: rgba(51, 41, 15, .50);
  --line: rgba(51, 41, 15, .12); --line-strong: rgba(51, 41, 15, .24);
  --accent: #9C5310; --accent-deep: #7C420C; --accent-rgb: 156, 83, 16;
}
html[data-palette="celadon"] {          /* LIGHT: celadon-green ground, forest ink, deep teal accent */
  --ink-900: #D9E7DA; --ink-800: #EAF2EA; --ink-700: #C4D8C6;
  --ink-900-rgb: 217, 231, 218;
  --fg: #16301F; --fg-rgb: 22, 48, 31;
  --fg-72: rgba(22, 48, 31, .80); --fg-60: rgba(22, 48, 31, .74); --fg-40: rgba(22, 48, 31, .50);
  --line: rgba(22, 48, 31, .12); --line-strong: rgba(22, 48, 31, .24);
  --accent: #206455; --accent-deep: #1A5346; --accent-rgb: 32, 100, 85;
}
html[data-palette="lilac"] {            /* LIGHT: lilac ground, aubergine ink, violet accent */
  --ink-900: #E4DEEF; --ink-800: #F0EBF7; --ink-700: #D2C8E4;
  --ink-900-rgb: 228, 222, 239;
  --fg: #2A2140; --fg-rgb: 42, 33, 64;
  --fg-72: rgba(42, 33, 64, .80); --fg-60: rgba(42, 33, 64, .74); --fg-40: rgba(42, 33, 64, .50);
  --line: rgba(42, 33, 64, .12); --line-strong: rgba(42, 33, 64, .24);
  --accent: #5F3D8C; --accent-deep: #4C3170; --accent-rgb: 95, 61, 140;
}
html[data-palette="glacier"] {          /* LIGHT: icy blue ground, deep navy ink, terra accent */
  --ink-900: #E8F1F6; --ink-800: #F4FAFC; --ink-700: #D6E6EE;
  --ink-900-rgb: 232, 241, 246;
  --fg: #10293A; --fg-rgb: 16, 41, 58;
  --fg-72: rgba(16, 41, 58, .80); --fg-60: rgba(16, 41, 58, .74); --fg-40: rgba(16, 41, 58, .50);
  --line: rgba(16, 41, 58, .12); --line-strong: rgba(16, 41, 58, .24);
  --accent: #A8482A; --accent-deep: #8A3B22; --accent-rgb: 168, 72, 42;
}
html[data-palette="cornflower"] {       /* LIGHT: saturated periwinkle-blue ground, navy ink, spice accent */
  --ink-900: #C7D7EE; --ink-800: #DCE7F6; --ink-700: #AFC4E2;
  --ink-900-rgb: 199, 215, 238;
  --fg: #16243D; --fg-rgb: 22, 36, 61;
  --fg-72: rgba(22, 36, 61, .80); --fg-60: rgba(22, 36, 61, .74); --fg-40: rgba(22, 36, 61, .50);
  --line: rgba(22, 36, 61, .12); --line-strong: rgba(22, 36, 61, .24);
  --accent: #7E3D0C; --accent-deep: #65310A; --accent-rgb: 126, 61, 12;
}
html[data-palette="duckegg"] {          /* LIGHT: blue-green duck-egg ground, deep teal ink, copper accent */
  --ink-900: #CFE5E4; --ink-800: #E2F0EF; --ink-700: #B8D6D4;
  --ink-900-rgb: 207, 229, 228;
  --fg: #0F2E2E; --fg-rgb: 15, 46, 46;
  --fg-72: rgba(15, 46, 46, .80); --fg-60: rgba(15, 46, 46, .74); --fg-40: rgba(15, 46, 46, .50);
  --line: rgba(15, 46, 46, .12); --line-strong: rgba(15, 46, 46, .24);
  --accent: #884624; --accent-deep: #6D381D; --accent-rgb: 136, 70, 36;
}
html[data-palette="mist"] {             /* LIGHT: blue-gray ground, slate ink, brick accent */
  --ink-900: #DCE2EA; --ink-800: #EAEEF4; --ink-700: #C8D1DD;
  --ink-900-rgb: 220, 226, 234;
  --fg: #1C2634; --fg-rgb: 28, 38, 52;
  --fg-72: rgba(28, 38, 52, .80); --fg-60: rgba(28, 38, 52, .74); --fg-40: rgba(28, 38, 52, .50);
  --line: rgba(28, 38, 52, .12); --line-strong: rgba(28, 38, 52, .24);
  --accent: #963A2C; --accent-deep: #782E23; --accent-rgb: 150, 58, 44;
}
/* Long View — Trevor's brand kit, direction 02 (Sep 7, 2026): Cornerstone Navy
   #162A43, Heritage Gold #B9964C, Warm White #FAFAF8, Mist #E6EBEE. Two
   renderings of the same kit, both reversible like every palette here, and
   both swap the text wordmark for his logo (inc/brand-longview.php). */
html[data-palette="longview"] {         /* DARK: reversed on navy — navy ground, warm-white type, gold accent */
  --ink-900: #162A43; --ink-800: #1B3350; --ink-700: #243F60;
  --ink-900-rgb: 22, 42, 67;
  --fg: #FAFAF8; --fg-rgb: 250, 250, 248;
  --fg-72: rgba(250, 250, 248, .78); --fg-60: rgba(250, 250, 248, .66); --fg-40: rgba(250, 250, 248, .44);
  --line: rgba(250, 250, 248, .12); --line-strong: rgba(250, 250, 248, .24);
  --accent: #B9964C; --accent-deep: #9A7A3A; --accent-rgb: 185, 150, 76;
}
html[data-palette="longview-light"] {   /* LIGHT: the kit's own balance — warm-white ground, navy type, mist panels; accent is the gold deepened to clear 4.5:1 on warm white (the kit itself says no small gold type on white) */
  --ink-900: #FAFAF8; --ink-800: #FFFFFF; --ink-700: #E6EBEE;
  --ink-900-rgb: 250, 250, 248;
  --fg: #162A43; --fg-rgb: 22, 42, 67;
  --fg-72: rgba(22, 42, 67, .82); --fg-60: rgba(22, 42, 67, .72); --fg-40: rgba(22, 42, 67, .68); /* .68 not .50: the step numerals on /private-tours/ use this rung as text */
  --line: rgba(22, 42, 67, .12); --line-strong: rgba(22, 42, 67, .24);
  --accent: #8A6D2E; --accent-deep: #6E5624; --accent-rgb: 138, 109, 46;
}
/* The kit's type: Baskerville display (Georgia is its own stated fallback), no
   webfont fetched — zero external requests holds. Body stays Inter (the kit's
   Arial is a near-equivalent system grotesque). Inter's tight tracking is
   loosened for the serif. */
html[data-palette^="longview"] { --font-display: Baskerville, 'Libre Baskerville', 'Times New Roman', Georgia, serif; }
html[data-palette^="longview"] h1, html[data-palette^="longview"] h2, html[data-palette^="longview"] h3,
html[data-palette^="longview"] .h3, html[data-palette^="longview"] .hero__title, html[data-palette^="longview"] .stat__num,
html[data-palette^="longview"] .ft__links a { font-family: var(--font-display); font-weight: 400; letter-spacing: -.01em; }
html[data-palette^="longview"] .hero__title { letter-spacing: -.02em; }
/* The logo itself. Letters ride currentColor (white on the dark header, navy
   once the light header is solid); the gold is the kit's, fixed. */
.lv-logo { display: block; height: auto; }
.lv-logo .lv-gold { fill: var(--lv-gold, #B9964C); } /* the kit's gold, deepened on the light rendering (below) so TOURS clears 4.5:1 at header size */
.lv-logo .lv-ink { fill: currentColor; }
.hd__logo .wordmark--longview { display: inline-flex; align-items: center; }
.hd__logo svg.lv-logo--primary { width: 15.5rem; height: auto; } /* out-specifies .hd__logo svg {20px} below */
.hd__logo svg.lv-logo--emblem { display: none; width: 2.75rem; height: auto; }
html[data-palette^="longview"] .hd__in { grid-template-columns: 18rem 1fr minmax(8rem, auto); } /* the kit's primary mark wants width; 13.75rem was sized for the text wordmark */
@media (max-width: 1023px) { html[data-palette^="longview"] .hd__in { grid-template-columns: 1fr auto; } }
@media (max-width: 767px) {
  .hd__logo svg.lv-logo--primary { display: none; }
  .hd__logo svg.lv-logo--emblem { display: block; }
}
/* The kit's footer is a navy block whatever the page ground; on the light
   rendering that also keeps the footer's fixed white type readable. */
html[data-palette="longview-light"] .ft { background: #162A43; --fg: #FAFAF8; --fg-rgb: 250, 250, 248; --fg-72: rgba(250, 250, 248, .78); --fg-60: rgba(250, 250, 248, .66); --fg-40: rgba(250, 250, 248, .44); --line: rgba(250, 250, 248, .12); --line-strong: rgba(250, 250, 248, .24); --accent: #B9964C; --lv-gold: #B9964C; }
.ft__wordmark .wordmark--longview { display: block; }
.ft__wordmark .lv-logo--primary { width: 17rem; max-width: 100%; height: auto; }

html[data-palette="periwinkle"] {       /* LIGHT: blue-violet ground, indigo ink, mulberry accent */
  --ink-900: #D4D8F0; --ink-800: #E4E7F7; --ink-700: #BEC4E5;
  --ink-900-rgb: 212, 216, 240;
  --fg: #1F2247; --fg-rgb: 31, 34, 71;
  --fg-72: rgba(31, 34, 71, .80); --fg-60: rgba(31, 34, 71, .74); --fg-40: rgba(31, 34, 71, .50);
  --line: rgba(31, 34, 71, .12); --line-strong: rgba(31, 34, 71, .24);
  --accent: #853650; --accent-deep: #6A2B40; --accent-rgb: 133, 54, 80;
}
/* The flips below apply to ALL light palettes via html[data-scheme="light"],
   which functions.php (cst_palette_attr / the inline first-paint script)
   sets whenever the active palette is in cst_light_palettes() — marble,
   daylight, limestone, sage. Every light palette needs the same component
   flips the dark ones do not. */
/* .hd__nav a / .hd__logo are hardcoded white in the base header rules
   (COMPONENTS — header, above), because the reference design assumes the
   fixed, pre-scroll header always sits over something dark: a photo hero,
   or — on the dark palettes — the page ground itself, which is dark ink by
   default. That assumption breaks on a light palette's HEROLESS pages: .hero
   only ever renders on the site's front page (inc/render.php
   cst_render_tour_index()'s Home branch, gated off /tours/ by is_page();
   functions.php's body.has-hero mirrors that exact condition, the same one
   cst_preload_hero() already uses for the hero LCP preload). Everywhere
   else, the transparent pre-scroll header sits directly on the light
   palette's own light ground, and forcing white there painted the header
   invisible (surfaced live on /contact/?palette=daylight: .hd__logo
   computed rgb(255,255,255) with no .hero element on the page and .hd not
   yet .is-active). Scoped to body.has-hero here; heroless pages get their
   own pre-scroll fallback below, to var(--fg) — the same colour the
   .hd.is-active override already switches to once scrolled, so there is no
   colour jump at the 20px mark on those pages either. */
html[data-scheme="light"] body.has-hero .hd__nav a:not(.btn), html[data-scheme="light"] body.has-hero .hd__logo,
html[data-scheme="light"] .hero__title, html[data-scheme="light"] .hero__sub { color: #fff; } /* over photos */
html[data-scheme="light"] body:not(.has-hero) .hd__nav a:not(.btn), html[data-scheme="light"] body:not(.has-hero) .hd__logo { color: var(--fg); text-shadow: none; }
html[data-scheme="light"] .hd.is-active .hd__nav a:not(.btn), html[data-scheme="light"] .hd.is-active .hd__logo { color: var(--fg); text-shadow: none; }
html[data-scheme="light"] .hd.is-active { background: rgba(var(--ink-900-rgb), .72); }
html[data-scheme="light"] .btn--primary { background: var(--fg); color: #fff; }
html[data-scheme="light"] .btn--primary:hover, html[data-scheme="light"] .btn--primary:focus-visible { background: var(--accent); color: #fff; }
html[data-scheme="light"] .btn--ghost { background: rgba(var(--fg-rgb), .06); color: var(--fg); }
html[data-scheme="light"] .mnav { background: var(--ink-900); }
html[data-scheme="light"] h1, html[data-scheme="light"] h2, html[data-scheme="light"] h3, html[data-scheme="light"] .stat__num,
html[data-scheme="light"] .review-card__name, html[data-scheme="light"] .rw__score, html[data-scheme="light"] .rw__count,
html[data-scheme="light"] .tour-card__price b, html[data-scheme="light"] .side__price b, html[data-scheme="light"] .review-card__quote { color: var(--fg); }
html[data-scheme="light"] .private__in h2, html[data-scheme="light"] .private__lead { color: #fff; } /* always over .private__bg's photo (inc/render.php) — the section only exists on the home page, with that photo built in */
/* .err404 (inc/render.php cst_render_404()) has no background photo — it is
   a plain section on the page ground, same "over photo" mistake as the
   header rule above, just not scroll-gated at all: it was swept into the
   forced-white list alongside .private__in's photo-backed neighbours
   without checking .err404 actually had one. Dropped; h1/p have no color
   of their own in the base rules, so they already inherit color: var(--fg)
   from body, correct on every palette. */
/* .private::after's scrim reads --ink-900-rgb so it can darken whichever
   photo sits behind it, but under a light palette --ink-900 is near-white
   — the scrim washes OUT instead of darkening, leaving the forced-white H2
   and lead text above (this rule) at ~3.2:1 on the photo's lighter sky
   tones. The intent is "always dark enough for forced-white text", not
   "match the ground colour", so pin it to the harbor ink regardless of
   the active palette. */
html[data-scheme="light"] .private::after {
  background: linear-gradient(to bottom, rgba(9, 27, 32, .45), rgba(9, 27, 32, .95));
}
/* Same "over photo" assumption, inverted: the base .hd__burger span
   (COMPONENTS — header) is hardcoded white for the same photo/dark-ground
   reasons as .hd__nav a above, so on a light palette's HERO page (the front
   page, body.has-hero) white is correct and should NOT be touched. Forcing
   var(--fg) here unconditionally got the heroless case right (dark ink is
   legible on the light ground, with or without scroll) but was never
   checked against the front page's hero photo — a burger in ink over
   whichever part of the sky happens to be under it. Scoped to
   body:not(.has-hero) so the front page keeps the base white. */
html[data-scheme="light"] body:not(.has-hero) .hd__burger span { background: var(--fg); }
/* The map basemap (.map__parks/.map__water/.map__boundary/.map__roads,
   below) needs no override here: it is built on --line/--line-strong
   directly, which the marble block above already redefines correctly
   (trap: contrast ladders/colours do not transfer between backgrounds —
   fixed at the token, not patched per palette). */

@media (max-width: 767px) {
  :root {
    --gutter: 1.25rem;
  }
}

/* =========================================================================
   RESET / BASE
   ========================================================================= */
*, *::before, *::after {
  box-sizing: border-box;
  min-width: 0; /* grid/flex items default to min-width:auto and blow out their track otherwise */
}
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: auto;
}
body {
  margin: 0;
  background: var(--ink-900);
  color: var(--fg);
  font-family: var(--font);
  font-size: var(--body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
main, section, article, header, footer, nav, figure { display: block; }
img, svg, video, canvas { max-width: 100%; display: block; }
img { border-radius: var(--radius-img); height: auto; }
a { color: inherit; text-decoration: none; }
ul, ol { margin: 0; padding: 0; list-style: none; }
button, input, textarea, select { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; }
h1, h2, h3, h4, h5, p { margin: 0; font-weight: 400; }

h1 { font-size: var(--h1); font-weight: 600; letter-spacing: -.06em; line-height: 1; }
h2 { font-size: var(--h2); font-weight: 600; letter-spacing: -.03em; line-height: 1.05; }
h3 { font-size: var(--h3); font-weight: 600; letter-spacing: -.02em; line-height: 1.15; }
h4 { font-size: var(--h4); font-weight: 600; letter-spacing: -.02em; line-height: 1.2; }

/* motion.js wraps every SplitText "line" in one of these to mask the reveal
   (y:100% -> y:0%). A mask sized to exactly the line's own line-height box
   clips real glyph ink: Inter's font metrics (ascent+descent) run taller
   than our tight heading line-heights (h1:1, h2:1.05, h3:1.15), so with zero
   headroom the negative half-leading pushes ascenders/descenders outside the
   box and the overflow:hidden mask slices them -- worst on h1 (line-height:1
   has no cushion at all) and on any descender (g/j/p/q/y) or accented
   capital. Padding + equal-and-opposite negative margin gives the mask
   headroom on both edges without moving the line itself or changing the
   mask's footprint in the flow (padding grows the border-box, the matching
   negative margin pulls the margin-box back to the original size), so
   surrounding layout and the slide-reveal animation are pixel-identical. */
.m-line-mask {
  overflow: hidden;
  padding: .22em 0;
  margin: -.22em 0;
}

.skip {
  position: absolute;
  left: 1rem;
  top: -4rem;
  z-index: 9999;
  background: #fff;
  color: var(--ink-900);
  padding: .75rem 1.25rem;
  border-radius: var(--radius-pill);
  font-size: var(--body-sm);
  font-weight: 600;
  transition: top .2s ease;
}
.skip:focus {
  top: 1rem;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* =========================================================================
   UTILITY
   ========================================================================= */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* =========================================================================
   TYPOGRAPHY CLASSES
   ========================================================================= */
.h1 { font-size: var(--h1); font-weight: 600; letter-spacing: -.06em; line-height: 1; margin: 0; }
.h2 { font-size: var(--h2); font-weight: 600; letter-spacing: -.03em; line-height: 1.05; margin: 0; }
.h3 { font-size: var(--h3); font-weight: 600; letter-spacing: -.02em; line-height: 1.15; margin: 0; }
.h4 { font-size: var(--h4); font-weight: 600; letter-spacing: -.02em; line-height: 1.2; margin: 0; }
.body-xxl { font-size: var(--body-xxl); font-weight: 500; line-height: 1.3; letter-spacing: -.01em; }
.body-lg { font-size: var(--body-lg); font-weight: 500; line-height: 1.35; letter-spacing: -.01em; }
.body { font-size: var(--body); font-weight: 400; line-height: 1.5; }
.body-sm { font-size: var(--body-sm); font-weight: 400; line-height: 1.35; }
.label {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: var(--label);
  font-weight: 500;
  line-height: 1.2;
  color: var(--fg-72);
}
.label svg { width: 16px; height: 16px; stroke: currentColor; flex-shrink: 0; }
.fg-72 { color: var(--fg-72); }
.fg-60 { color: var(--fg-60); }
.fg-40 { color: var(--fg-40); }

/* SplitText "words,chars" / "lines,words,chars" (data-m="type"/"wash"):
   words must stay inline-block so a line can only break BETWEEN words,
   never mid-word through a char span. */
.split-word { display: inline-block; }

/* =========================================================================
   LAYOUT
   ========================================================================= */
.sec {
  position: relative;
  padding: 8rem var(--gutter);
  background: var(--ink-900);
}
.sec--alt { background: var(--ink-800); }

/* The templates build #smooth-wrapper > #smooth-content > main with WP group
   blocks; kill the block-gap margins WordPress's flow layout would add. */
#smooth-wrapper, #smooth-content, #main, .entry-content { margin: 0; padding: 0; max-width: none; }
#smooth-content > * + *, #main > * + *, .entry-content > * + * { margin-block-start: 0; }

.sec__head {
  display: grid;
  grid-template-columns: 22rem 1fr;
  gap: 2rem;
  align-items: start;
}
.sec__head .label { padding-top: .3em; }
.sec__head-col {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.grid-lines {
  position: relative;
}
.grid-lines > * {
  border-top: 1px solid var(--line);
}
.grid-lines > *:first-child {
  border-top: none;
}

/* decorative hairlines, drawn by motion.js */
.dec {
  position: absolute;
  background: var(--line);
  pointer-events: none;
}
.dec--x {
  height: 1px;
  width: 0%;
}
.dec--y {
  width: 1px;
  height: 0%;
}
html.no-js .dec--x, html.motion-off .dec--x { width: 100% !important; }
html.no-js .dec--y, html.motion-off .dec--y { height: 100% !important; }

/* Hairline grid placement. The .dec spans motion.js animates carry no
   positioning of their own (only width/height); each grid container places
   its own decs by document order (span:nth-of-type, since every dec is a
   <span> and no other top-level <span> sits in these grids). Order follows
   inc/render.php exactly — see the comment on each block. */
.routes__grid,
.featured__grid,
.how__grid,
.reviews__grid,
.reviews {
  position: relative;
}

/* routes__grid: dec-x(top), N route-cards, dec-x(bottom), N-1 dec-y — the grid
   carries --n and each vertical rule carries --i (1..N-1), so any number of
   routes lays out and rules itself; nothing here assumes four. */
.routes__grid > .dec--top { top: 0; left: 0; right: 0; }
.routes__grid > .dec--bottom { bottom: 0; left: 0; right: 0; }
.routes__grid > .dec--row { position: relative; grid-column: 1 / -1; height: 1px; top: auto; }
.routes__grid > .dec--y { top: 0; bottom: 0; left: calc(100% * var(--i, 1) / var(--n, 4)); }

/* featured__grid: dec-x(top), tour-cards two-up, a .dec--row after every completed
   row (flows in the grid so it follows the real row boundary — cards differ in
   height), dec-x(bottom), dec-y(centre). Works for any number of cards. */
.featured__grid > .dec--top { top: 0; left: 0; right: 0; }
.featured__grid > .dec--row { position: relative; grid-column: 1 / -1; height: 1px; top: auto; }
.featured__grid > .dec--bottom { bottom: 0; left: 0; right: 0; }
.featured__grid > .dec--y { top: 0; bottom: 0; left: 50%; }

/* how__grid: dec-x(top), 4x how__item, dec-y x3 (between the 4 columns) */
.how__grid > span:nth-of-type(1) { top: 0; left: 0; right: 0; }
.how__grid > span:nth-of-type(2) { top: 0; bottom: 0; left: 25%; }
.how__grid > span:nth-of-type(3) { top: 0; bottom: 0; left: 50%; }
.how__grid > span:nth-of-type(4) { top: 0; bottom: 0; left: 75%; }

/* reviews__grid: dec-x(top), dec-y(centre, between the 2 review-card columns), 4x review-card */
.reviews__grid > span:nth-of-type(1) { top: 0; left: 0; right: 0; }
.reviews__grid > span:nth-of-type(2) { top: 0; bottom: 0; left: 50%; }

/* reviews__divider: direct child of .reviews (grid-template-columns: 1fr 22rem, gap 4rem),
   centred in that gap so it doesn't depend on the 1fr column's resolved width. */
.reviews__divider { top: 0; bottom: 0; right: calc(22rem + 2rem); }

/* private__in: single dec-x, sits right under the +group/+pace/+guide list */
.private__in { position: relative; }
.private__in > .dec--x { left: 0; right: 0; bottom: 0; }

.container {
  max-width: 90rem;
  margin: 0 auto;
}

/* =========================================================================
   COMPONENTS — buttons
   ========================================================================= */
.btn {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
  height: 48px;
  padding: 0 1.5rem;
  border-radius: var(--radius-pill);
  font-weight: 500;
  font-size: .9375rem;
  white-space: nowrap;
  border: none;
  transition: background-color .2s ease, color .2s ease, border-color .2s ease, box-shadow .25s ease;
}
.btn--primary {
  background: #fff;
  color: var(--ink-800);
}
/* Primary buttons light up amber on hover (Trevor, Aug 19: "illuminate that
   in orange") — ground to accent, ink text, and a soft glow ring. */
.btn--primary:hover,
.btn--primary:focus-visible {
  background: var(--accent);
  color: var(--ink-900);
  box-shadow: 0 0 0 4px rgba(var(--accent-rgb), .28), 0 6px 24px rgba(var(--accent-rgb), .35);
}
.btn--ghost {
  background: rgba(255, 255, 255, .08);
  color: #fff;
  border: 1px solid var(--line-strong);
}
.btn--ghost:hover {
  background: var(--ink-700);
}
.btn__icon {
  width: 8px;
  height: 8px;
  display: inline-flex;
  flex-shrink: 0;
  transition: transform .2s ease;
}
.btn__icon svg, .btn__icon path { width: 100%; height: 100%; }
.btn:hover .btn__icon {
  transform: rotate(90deg);
}

/* =========================================================================
   COMPONENTS — header
   ========================================================================= */
/* Reference header, exactly: no ground at the top of the page, and once the
   page has scrolled 20px (.is-active, site.js) the bar becomes frosted glass —
   backdrop-filter: blur(12px) over whatever passes under it, still no tint.
   (An earlier version tinted it 85% ink on scroll; that reads as a solid bar,
   not the glass Trevor asked for.) The blur is only ever *behind* the bar, so
   the logo, links and CTA stay crisp. */
.hd {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  background: transparent;
  transition: backdrop-filter .3s ease, -webkit-backdrop-filter .3s ease;
}
.hd.is-active {
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
/* The mobile menu overlay needs a real ground of its own (it is not the bar). */
.hd__in {
  position: relative;
  display: grid;
  grid-template-columns: 13.75rem 1fr minmax(8rem, auto);
  align-items: center;
  height: var(--header-h);
  padding: 0 var(--gutter);
}
.hd__logo {
  display: inline-flex;
  justify-self: start; /* Trevor, Sep 7: as a grid item the link was stretching across the whole first column, so on a phone its tap target ran almost to the menu button; size it to the wordmark only */
  align-items: center;
  gap: .5rem;
  color: #fff;
  font-weight: 600;
  font-size: 1.125rem;
  letter-spacing: -.02em;
}
.hd__logo svg { width: 20px; height: 20px; flex-shrink: 0; }
/* Wordmark is a live control (Trevor, Aug 19): on hover the text and the
   waypoint mark warm to amber (reference: .header__l:hover → brand-accent,
   .2s) and the mark's inner dot pings once, like a waypoint lighting up. */
.hd__logo .wordmark__text,
.hd__logo .wordmark__mark svg { transition: color .2s ease, stroke .2s ease, transform .2s ease; }
.hd__logo .wordmark__mark svg circle:last-child { transform-origin: 8px 8px; transform-box: view-box; }
.hd__logo:hover .wordmark__text,
.hd__logo:focus-visible .wordmark__text { color: var(--accent); }
.hd__logo:hover .wordmark__mark svg,
.hd__logo:focus-visible .wordmark__mark svg { color: var(--accent); transform: rotate(90deg); }
.hd__logo:hover .wordmark__mark svg circle:last-child { animation: wm-ping .6s ease-out 1; }
@keyframes wm-ping {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.9); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .hd__logo:hover .wordmark__mark svg { transform: none; }
  .hd__logo:hover .wordmark__mark svg circle:last-child { animation: none; }
}
.hd__nav {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 2rem;
  padding-right: 2rem;
}
.hd__nav a:not(.btn) {
  font-size: 1rem;              /* Trevor, Aug 19: a little larger than the reference's .875rem */
  font-weight: 600;
  letter-spacing: -.02em;
  color: #fff;                    /* solid white, like the reference's .color-white links */
  text-shadow: 0 1px 10px rgba(0, 0, 0, .35); /* keeps white legible over a bright sky before the glass kicks in */
  transition: color .2s ease;
}
.hd__nav a:not(.btn):hover {
  color: var(--accent);
}
.hd__cta {
  justify-self: end;
}
.hd__burger {
  display: none;
  /* Trevor, Sep 7: the tap target was the 24x16 drawing itself. The box is
     now 44x44 (the two lines still draw 24 wide, 14.5 apart, so the open
     state's translateY(7px) cross is unchanged); the negative margin keeps
     the lines on the same gutter line as before. */
  width: 44px;
  height: 44px;
  margin-right: -10px;
  position: relative;
  justify-self: end;
}
.hd__burger span {
  position: absolute;
  left: 10px; right: 10px;
  height: 1.5px;
  background: #fff;
  transition: transform .3s ease, opacity .2s ease;
}
.hd__burger span:nth-child(1) { top: 14px; }
.hd__burger span:nth-child(2) { bottom: 14px; }
.hd__burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.hd__burger[aria-expanded="true"] span:nth-child(2) { transform: translateY(-7px) rotate(-45deg); }
/* Group travel (Trevor, Aug 20) took the desktop nav from four links to
   five. .hd__in reserves a fixed 13.75rem logo column and a fixed CTA
   column, so the nav's own track is fixed too — at 768-1023px five links
   at 2rem gap wrap onto a second line no matter how tight the gap goes
   (the link text alone already exceeds the track). Hand that range to the
   burger, same as every other tablet-tier rule in this file (1023px). */
@media (max-width: 1023px) {
  .hd__in { grid-template-columns: 1fr auto; }
  .hd__nav, .hd__cta, .hd__dec--3 { display: none; }
  .hd__burger { display: block; }
}

.hd__dec--1 {
  grid-column: 1 / -1;
  align-self: end;
  bottom: 0; left: 0; right: 0;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
}
.hd__dec--2 {
  grid-column: 1 / 2;
  justify-self: end;
  top: 0; bottom: 0;
  height: 100%;
  transform: scaleY(0);
  transform-origin: center top;
}
.hd__dec--3 {
  grid-column: 2 / 3;
  justify-self: end;
  /* Column 3 (minmax(8rem, auto)) is sized to the CTA pill exactly, so the
     nav/CTA column boundary lands flush on the button's own left edge --
     the button was rendering right on top of this line. .hd__nav already
     keeps its links clear of the line with padding-right; give the line the
     same clearance from the other side instead of moving the button. */
  margin-right: 1rem;
  top: 0; bottom: 0;
  height: 100%;
  transform: scaleY(0);
  transform-origin: center top;
}
html.no-js .hd__dec, html.motion-off .hd__dec { transform: none !important; }

/* mobile nav overlay */
.mnav {
  display: none;
}

/* =========================================================================
   COMPONENTS — hero
   ========================================================================= */
.hero {
  position: relative;
  overflow: hidden;
  aspect-ratio: 1440 / 922;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ink-900);
}
.hero__back {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 50%;
  border-radius: 0;
  z-index: 0;
}
/* Depth trick (redesign-brief.md "Hero depth trick"): .hero__text is
   deliberately given NO z-index of its own (stays "auto"), so it never
   forms its own stacking context — its children's z-index values compete
   directly with .hero__back (z0) and .hero__front (z3) as if the wrapper
   weren't there. That lets the H1 (z2) sit BEHIND the foreground image
   while the sub-copy and button (z4) sit IN FRONT of it, even though all
   three are DOM siblings inside one wrapper. Do not add z-index to
   .hero__text; that would trap all three children at one level again. */
.hero__title {
  position: relative;
  z-index: 2;
  text-align: center;
  color: #fff;
  margin: 0 0 var(--hero-title-lift, 8vw);
}
/* "Experience" rides above the giant word at heading scale, same block. */
.hero__kicker {
  display: block;
  font-size: clamp(1.5rem, 3vw, 3rem);
  font-weight: 500;
  letter-spacing: -.01em;
  line-height: 1;
  margin-bottom: .1em;
  opacity: .92;
}
.hero__kicker--after { margin: .15em 0 0; }
/* Per-photo placement (Trevor, Aug 19): Lincoln sunset — the memorial must read
   clean, so the whole text stack sits BELOW the horizon on the reflection:
   "Experience / Washington / with us", a smaller word, in front of the water
   (z4 — no depth trick here, the water is not a subject to hide behind).
   The hero fills the viewport so the composition is relative to the screen,
   and the photo is anchored low (object-position 66%) so the memorial sits
   in the upper half on any aspect, 1920×850 included. */
.hero--hero-lincoln-sunset { aspect-ratio: auto; height: 100svh; min-height: 40rem; --hero-title-lift: 1.25rem; }
.hero--hero-lincoln-sunset .hero__back,
.hero--hero-lincoln-sunset .hero__front { object-position: center 66%; }
.hero--hero-lincoln-sunset .hero__title { z-index: 4; font-size: clamp(3.5rem, 8vw, 8.5rem); text-shadow: 0 2px 30px rgba(0, 0, 0, .35); }
.hero--hero-lincoln-sunset .hero__kicker { font-size: clamp(1.375rem, 2.2vw, 2.25rem); }
.hero--hero-lincoln-sunset .hero__text { padding-bottom: 6vh; }
.hero--hero-lincoln-sunset .hero__sub { text-shadow: 0 1px 24px rgba(0, 0, 0, .6); margin-bottom: 1.25rem; }

/* hero-monument-blossoms (Trevor, Aug 19 eve): Monument over the Tidal Basin under
   cherry blossoms. Same grammar as the lincoln-sunset variant; the photo is much
   lighter (pale spring sky), so the shadows work harder for text legibility. The
   -front cut is the water + blossom treeline band (bottom half), so the H1 sinks
   into the Basin like the reference trick. */
.hero--hero-monument-blossoms { aspect-ratio: auto; height: 100svh; min-height: 40rem; --hero-title-lift: 1.25rem; }
.hero--hero-monument-blossoms .hero__back,
.hero--hero-monument-blossoms .hero__front { object-position: center 42%; }
.hero--hero-monument-blossoms .hero__title { z-index: 4; font-size: clamp(3.5rem, 8vw, 8.5rem); text-shadow: 0 2px 34px rgba(0, 0, 0, .5); }
.hero--hero-monument-blossoms .hero__kicker { font-size: clamp(1.375rem, 2.2vw, 2.25rem); }
.hero--hero-monument-blossoms .hero__text { padding-bottom: 6vh; }
.hero--hero-monument-blossoms .hero__sub { text-shadow: 0 1px 26px rgba(0, 0, 0, .7); margin-bottom: 1.25rem; }
/* Top scrim: the blossom sky is nearly white, so the fixed header's white wordmark
   and nav were unreadable until scroll darkened the header. Ink gradient over the
   top band only (text sits mid-frame, untouched). Decorative, no pointer events. */
.hero--hero-monument-blossoms::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 11rem;
  z-index: 4;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(9, 27, 32, .62), rgba(9, 27, 32, 0));
}
/* Long View keeps the glass header (Trevor, Sep 7), so the logo's gold has
   only this scrim between it and the blossom sky: navy, deeper and taller
   than the shared one, and still fading out well above the hero text. Scoped
   to the palette; the shared scrim above is untouched. */
html[data-palette="longview"] .hero--hero-monument-blossoms::after {
  height: 14rem;
  background: linear-gradient(to bottom, rgba(22, 42, 67, .96) 0, rgba(22, 42, 67, .9) 4.5rem, rgba(22, 42, 67, .45) 9rem, rgba(22, 42, 67, 0));
}

/* hero-monument-blossoms-dark (Trevor, Sep 7): the same photo with his darker-water edit,
   and "make sure the text does not blend in with the image, if it does, we should move the
   text away from the center." It did: centred, "Experience" and half of "Washington" sat on
   the pale sky and the Monument's reflection. The whole stack now sits bottom-LEFT on the
   dark water, off the reflection, in front of everything (z4, no depth trick, as the
   lincoln-sunset variant reasons). The photo is anchored a little lower (object-position
   52%) so more water is in frame under the text; the header scrim rules above are reused by
   the shared selector below. */
.hero--hero-monument-blossoms-dark { aspect-ratio: auto; height: 100svh; min-height: 40rem; --hero-title-lift: 1rem; }
.hero--hero-monument-blossoms-dark .hero__back,
.hero--hero-monument-blossoms-dark .hero__front { object-position: center 52%; }
.hero--hero-monument-blossoms-dark .hero__text { align-items: flex-start; text-align: left; padding: 0 var(--gutter) 7vh; }
.hero--hero-monument-blossoms-dark .hero__title { z-index: 4; text-align: left; font-size: clamp(3.5rem, 7.5vw, 8rem); text-shadow: 0 2px 30px rgba(0, 0, 0, .45); }
.hero--hero-monument-blossoms-dark .hero__kicker { font-size: clamp(1.375rem, 2.2vw, 2.25rem); }
.hero--hero-monument-blossoms-dark .hero__sub { text-align: left; margin-left: 0; text-shadow: 0 1px 24px rgba(0, 0, 0, .6); margin-bottom: 1.25rem; }
.hero--hero-monument-blossoms-dark::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 12rem;
  z-index: 4;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(9, 27, 32, .72), rgba(9, 27, 32, 0)); /* QC, Sep 7: .62 left the 16px nav under 4.5:1 on the widest screens */
}
/* QC, Sep 7: at 768 and 1280 the pale reflected treeline crosses the top of the title, and the
   bottom band alone left its brightest patches under 4.5:1. A soft dark pool behind the text
   block, left and low, fading out well before the Monument's reflection. Palette-aware via
   --ink-900-rgb; z3 sits over the photo and under the text. */
.hero--hero-monument-blossoms-dark::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: radial-gradient(ellipse 64% 60% at 20% 82%, rgba(var(--ink-900-rgb), .66) 0%, rgba(var(--ink-900-rgb), .4) 42%, rgba(var(--ink-900-rgb), 0) 74%);
}
/* Dark palettes only (Trevor, Sep 10: "make it blend directly into the background, almost
   like there is no photo at all"): a second layer under the text pool that fades to the page
   ground at 100% on the last rows, so the hero's edge and the section below are one colour.
   Not on the -light palettes: there the ground is near-white and the hero text is white, so
   the same fade would wash the headline out (QC, Sep 10: 1.4:1); they keep the pool alone. */
html:not([data-palette$="-light"]) .hero--hero-monument-blossoms-dark::before {
  /* Trevor, Sep 10 ("the blend looks choppy when you scroll"): a long 8-bit alpha ramp over
     near-flat navy can show as stepped bands that crawl as the page moves. Two guards:
     (1) a faint film-grain layer (SVG turbulence, ~5% alpha, 160px tile) on top dithers the
     ramp so no two rows share a band; (2) the pseudo-element is promoted to its own
     compositor layer so ScrollSmoother's transform never re-rasterises the gradients. */
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 .05 0'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>") repeat,
    linear-gradient(to top, rgba(var(--ink-900-rgb), 1) 0, rgba(var(--ink-900-rgb), .94) 7%, rgba(var(--ink-900-rgb), .62) 18%, rgba(var(--ink-900-rgb), .22) 30%, rgba(var(--ink-900-rgb), 0) 42%),
    radial-gradient(ellipse 64% 60% at 20% 82%, rgba(var(--ink-900-rgb), .66) 0%, rgba(var(--ink-900-rgb), .4) 42%, rgba(var(--ink-900-rgb), 0) 74%);
  will-change: transform;
  transform: translateZ(0);
}
html[data-palette="longview"] .hero--hero-monument-blossoms-dark::after {
  height: 14rem;
  background: linear-gradient(to bottom, rgba(22, 42, 67, .96) 0, rgba(22, 42, 67, .9) 4.5rem, rgba(22, 42, 67, .45) 9rem, rgba(22, 42, 67, 0));
}
/* No -front cut for this variant: the title is in front anyway, so the fallback band is the
   only thing the placeholder would draw. A soft bottom scrim instead, so the water reads dark
   under the text on every screen and flows into the page ground below. */
.hero--hero-monument-blossoms-dark .hero__front--fallback { height: 46%; background: linear-gradient(to top, rgba(var(--ink-900-rgb), .72) 0%, rgba(var(--ink-900-rgb), .35) 45%, rgba(var(--ink-900-rgb), 0) 100%); }
.hero__front {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 50%;
  border-radius: 0;
  z-index: 3;
  pointer-events: none;
}
.hero__front--fallback {
  /* No real front cutout wired in yet (SPEC.md: hero-capitol-reflect-front
     assets exist under assets/img/ but inc/render.php still only emits this
     placeholder div) — bottom-band gradient stand-in so sub-copy/button
     still land on a dark, legible ground. */
  top: auto;
  height: 42%;
  background: linear-gradient(to top, var(--ink-900) 0%, rgba(var(--ink-900-rgb), .78) 45%, rgba(var(--ink-900-rgb), 0) 100%);
}
.hero__text {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding: 0 var(--gutter) 8rem;
  text-align: center;
}
.hero__sub {
  position: relative;
  z-index: 4;
  max-width: 34rem;
  margin: 0 0 1.5rem;
  color: #fff;
  text-shadow: 0 1px 24px rgba(0, 0, 0, .45);
}
.hero__text > .btn {
  position: relative;
  z-index: 4;
}

/* =========================================================================
   COMPONENTS — routes
   ========================================================================= */
.routes__grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--n, 4), minmax(0, 1fr));
  margin-top: 4rem;
}
.route-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 2rem 1.5rem;
}
/* EDIT 1: the head row used to pair a short name with an inline "/ from $X"
   (display:flex space-between). The card now carries the tour's full proper
   title, which runs long ("US Capitol Guided Tour with Entry Tickets"), so
   the head is a simple stack and price/rating/duration moved to their own
   hairline rows below the image (.route-card__facts) — the same visual
   grammar .tour-card__props already uses two sections down. */
.route-card__head {
  display: flex;
  flex-direction: column;
  gap: .35rem;
  margin-bottom: 1.5rem;
}
.route-card__head h3 {
  font-size: var(--h4); /* full titles at h3 scale ran three lines and crowded the card */
}
.route-card__eyebrow {
  font-size: var(--label);
  font-weight: 500;
  color: var(--fg-60);
}
.route-card__img {
  border-radius: var(--radius-img);
  overflow: hidden;
  aspect-ratio: 4 / 5;
}
.route-card__img img { width: 100%; height: 100%; object-fit: cover; }
.route-card__facts {
  display: flex;
  flex-direction: column;
  margin-top: 1.5rem;
}
.route-card__cta { align-self: flex-start; margin-top: 1.5rem; } /* Trevor, Sep 7: Explore tour pill on the best-seller cards */
.route-card:hover .route-card__cta, .route-card:focus-visible .route-card__cta { background: var(--ink-700); }
.route-card__facts > li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  border-top: 1px solid var(--line);
  padding: .6rem 0;
  font-size: var(--body-sm);
  color: var(--fg-72);
}
.route-card__facts b { color: var(--fg); font-weight: 600; }
.route-card__facts .stars { margin-right: .35rem; }
.route-card__facts svg {
  width: 16px;
  height: 16px;
  stroke: currentColor;
  flex-shrink: 0;
}

/* =========================================================================
   COMPONENTS — about / stats
   ========================================================================= */
.about__body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4rem;
  margin-top: 4rem;
  align-items: stretch;      /* the copy column runs the full height of the photo… */
}
.about__media {
  border-radius: var(--radius-img);
  overflow: hidden;
  aspect-ratio: 4 / 5;
}
.about__media img { width: 100%; height: 100%; object-fit: cover; }
.about__content { display: flex; flex-direction: column; min-height: 0; }
/* Same bounded-height treatment applies wherever the wall lands outside the
   About flex column — currently just the tour-page reviews section (#reviews)
   — so it never grows into an unbounded, page-length track. */
.about__content > .rw--wall, #reviews .rw--wall { flex: 1 1 auto; min-height: 22rem; }
.about__content > .rw--wall .rw__cols, #reviews .rw--wall .rw__cols { min-height: 16rem; }   /* …and the review wall fills what the copy and stats leave */

.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2rem;
  margin-top: 3rem;
}
.stat {
  display: flex;
  flex-direction: column;
}
.stat__num {
  display: block;
  font-size: clamp(2.5rem, 4vw, 4rem);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1;
  color: #fff;
}
.stat__label {
  display: block;
  margin-top: .5rem;
  color: var(--fg-60);
  font-size: var(--body-sm);
}
.about__foot {
  display: flex;
  align-items: center;
  gap: 2.5rem;
  flex-wrap: wrap;
  margin-top: 4rem;
  padding-top: 2rem;
  border-top: 1px solid var(--line);
}
.about__foot img { height: 1.25rem; width: auto; opacity: .6; }

/* =========================================================================
   COMPONENTS — featured tours
   ========================================================================= */
.featured__head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
  flex-wrap: wrap;
}
.featured__head p { max-width: 24rem; }
.featured__grid {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  margin-top: 4rem;
}
.tour-card {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 2rem;
  padding: 2.5rem;
}
.tour-card__body {
  display: flex;
  flex-direction: column;
}
.tour-card__title { margin-bottom: 1rem; }
.tour-card__price {
  color: var(--fg-72);
  font-size: var(--body-lg);
  margin-bottom: 1.5rem;
}
.tour-card__price b { color: #fff; font-weight: 600; }
.tour-card__props {
  display: flex;
  flex-direction: column;
  margin-bottom: 2rem;
}
.tour-card__props > * {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  border-top: 1px solid var(--line);
  padding: .75rem 0;
  font-size: var(--body-sm);
  color: var(--fg-72);
}
.tour-card__props svg {
  width: 16px;
  height: 16px;
  stroke: currentColor;
  flex-shrink: 0;
}
/* .stars: five <span class="star"><svg></span>. The global `svg{display:block}`
   reset otherwise forces each star onto its own line inside an inline
   container (a block box inside inline content always wraps); inline-flex
   keeps all five in a row. Used here and in review-card. */
.stars {
  display: inline-flex;
  align-items: center;
  gap: .125rem;
  vertical-align: middle;
}
.star svg {
  width: 14px;
  height: 14px;
  stroke: none;
  fill: var(--fg-40);
}
.star.is-filled svg {
  fill: var(--accent);
}
.tour-card__visual {
  border-radius: var(--radius-img);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  align-self: start;
}
.tour-card__visual img { width: 100%; height: 100%; object-fit: cover; }
.tour-card__options {
  margin-top: 1.5rem;
  display: flex;
  flex-direction: column;
}
.tour-card__options > * {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--body-sm);
  color: var(--fg-60);
  border-top: 1px solid var(--line);
  padding: .5rem 0;
}

/* =========================================================================
   COMPONENTS — private tours
   ========================================================================= */
.private {
  position: relative;
  overflow: hidden;
  padding: 0;
}
.private__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0;
  z-index: 0;
}
.private::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to bottom, rgba(var(--ink-900-rgb), .2), rgba(var(--ink-900-rgb), .95));
}
.private__in {
  position: relative;
  z-index: 2;
  padding: 8rem var(--gutter) 0;
}
.private__lead { margin-top: 1.5rem; max-width: 44rem; }

/* EDIT D2 (Trevor, Aug 20, live-review rebuild): three clickable disclosure
   rows replace the old +Your group/+Your pace/+Your guide list, in the
   same visual position over the photo band. Deliberately NOT the --fg/
   --fg-72/--fg-40 tokens anywhere in here (unlike most components): those
   are palette-reactive (dark ink under marble, for marble's own light
   ground), but this whole block sits on the fixed-dark .private::after
   scrim on EVERY palette (that scrim is already pinned to the harbor ink
   regardless of data-palette, further up this file) — so token colors
   would silently go dark-on-dark under marble, the exact trap that forced
   the .private__in h2/.private__lead override above. Hardcoding the same
   numeric values the dark palette's tokens already resolve to sidesteps
   that without adding another marble override rule.

   The "+" (site.js sets aria-expanded) rotates 45deg into an "x" on open —
   a plus rotated a half of 90deg IS an x, so one `transform: rotate()` on
   the glyph does the whole morph. That is the same rotate-on-toggle move
   the FAQ's own `details summary::after` already uses elsewhere in this
   file, and the same primitive the .btn__icon sparkle uses on hover
   (transform: rotate(90deg)) — reusing the site's own signature rather
   than inventing a new one, per Trevor's ask.

   The panel reveal is a CSS grid-template-rows 0fr -> 1fr tween (a plain
   height auto-animates by animating the fr unit of a 1-row grid, no
   GSAP/JS height math needed for something this simple), gated by :has()
   reading the trigger's own aria-expanded — one source of truth, no
   separate .is-open class to fall out of sync. `min-height: 0` on the
   grid item (.private__acc-panel) is required for the 0fr row to actually
   reach zero — the same "grid items refuse to shrink below their content"
   default this file's own trap notes call out for min-width, just the
   vertical axis of the identical bug. `visibility` is paired with the
   height tween, delayed on close only (mirrors .mnav below), so a closed
   panel's link drops out of the tab order instead of sitting there
   focusable-but-invisible.

   QC (Aug 20, sampled against real composited pixels, not assumed ones —
   this sits on a PHOTO, not a flat ground, so the trap's "measure against
   the actual scrim" warning applies literally): the resting "+" at
   fg-40's rgba(255,255,255,.4) measured 3.34:1 harbor / 3.52:1 marble,
   under the 4.5:1 floor, in the state nearly every pageview sees. Raised
   to fg-72's rgba(255,255,255,.78) below — same hardcode-not-token
   reasoning as the rest of this block, just the site's established
   low-emphasis floor instead of the decorative one.

   QC also caught the hover/open accent state: var(--accent) is verdigris
   #1F6F63 under marble, which is only safe on marble's own LIGHT ground —
   on this block's forced-dark scrim it measured 2.52-2.61:1, so the exact
   moment a marble guest interacts, legibility drops. Harbor's amber
   --accent already clears 6.92:1 on the same dark ground, so
   --acc-scrim-accent below hardcodes that amber value for BOTH palettes
   here, rather than reverting the .private::after dark-scrim pin (QC:
   don't) or inventing a new "lightened verdigris" hex (CLAUDE.md: every
   colour used is a verified token, not a new hex — amber already is one,
   --accent's own harbor value, just referenced as a literal so it stops
   being palette-reactive in a spot where the ground never actually
   changes). */
.private__acc {
  margin-top: 2.5rem;
  --acc-scrim-accent: #F2A93B; /* harbor's own --accent value, hardcoded for marble too — see block comment */
}
.private__acc-item { border-top: 1px solid var(--line-strong); }
.private__acc-item:last-child { border-bottom: 1px solid var(--line-strong); }
.private__acc-h { margin: 0; }
.private__acc-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.25rem 0;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.private__acc-label {
  font-size: var(--h3);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.15;
  color: #fff;
  transition: color .2s ease;
}
.private__acc-icon {
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font-size: 1.75rem;
  font-weight: 400;
  line-height: 1;
  /* QC pixel-sampled resting fg-72 white fine everywhere (7.5-9.3:1), but a
     bare glyph on the raw photo means an accent colour's contrast rides on
     wherever the photo happens to be bright underneath it -- QC's sampling
     caught the open/not-hovered amber state at 3.56:1 on a bright patch at
     390px, after the rest of this fix already passed. A fixed, always-on
     dark chip (independent of scroll position, viewport, or which frame of
     the photo is showing) decouples every state's contrast from the photo
     entirely, rather than chasing whichever crop is worst-case. Same literal
     harbor-ink as the panel scrim above, for the same reason: --ink-900
     washes near-white under marble. */
  background: rgba(9, 27, 32, .85);
  color: rgba(255, 255, 255, .78); /* fg-72's dark-palette value, hardcoded — see block comment above. Was fg-40's .4 (3.34:1 harbor / 3.52:1 marble, QC fail); .78 clears 4.5:1 against this chip. */
  transition: transform .3s ease, color .2s ease;
}
.private__acc-btn:hover .private__acc-label, .private__acc-btn:focus-visible .private__acc-label,
.private__acc-btn:hover .private__acc-icon, .private__acc-btn:focus-visible .private__acc-icon {
  color: var(--acc-scrim-accent); /* hardcoded amber on every palette — var(--accent) resolves to verdigris under marble, which fails on this forced-dark ground; see block comment above */
}
.private__acc-btn[aria-expanded="true"] .private__acc-icon {
  color: var(--acc-scrim-accent);
  transform: rotate(45deg);
}
.private__acc-panel-wrap {
  display: grid;
  grid-template-rows: 0fr;
  visibility: hidden;
  transition: grid-template-rows .35s ease, visibility 0s linear .35s;
}
.private__acc-item:has(.private__acc-btn[aria-expanded="true"]) .private__acc-panel-wrap {
  grid-template-rows: 1fr;
  visibility: visible;
  transition: grid-template-rows .35s ease, visibility 0s;
}
.private__acc-panel {
  min-height: 0; /* the 0fr row above cannot reach zero without this — trap's min-width:0, vertical axis */
  overflow: hidden;
}
.private__acc-panel-in {
  padding: .25rem 1.25rem 1.5rem;
  margin-bottom: 1.25rem;
  border-radius: var(--radius-img);
  /* Literal dark fill, not var(--ink-900): that token goes near-white under
     marble (see the .private::after comment above) and this scrim needs to
     read the same on every palette, since it sits on the same forced-dark
     ground on every palette. */
  background: rgba(9, 27, 32, .82);
}
.private__acc-panel p {
  color: rgba(255, 255, 255, .78); /* fg-72's dark-palette value, hardcoded — see block comment above */
  max-width: 34rem;
}
.private__acc-link {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
  margin-top: 1rem;
  color: #fff;
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color .2s ease;
}
.private__acc-link:hover, .private__acc-link:focus-visible { color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
  .private__acc-panel-wrap { transition: none; }
}

/* EDIT D (Trevor, Aug 20 rebuild): .private__body is the solid ground —
   the bg image + gradient (z0/z1) are absolutely covering the whole
   .private section, so an opaque background here simply paints over them
   and everything below (steps, actions) reads as sitting on plain ground
   rather than a fading photo, without needing the image itself to stop
   short. Was .private__steps's own job alone before the rebuild gave the
   band more to hold. EDIT D2: the three-card grid this comment used to
   describe is gone — its copy moved into the accordion above. */
.private__body {
  position: relative;
  z-index: 2;
  background: var(--ink-900);
  margin-top: 5rem;
  border-top: 1px solid var(--line);
}
.private__steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 2rem;
  padding: 3rem var(--gutter);
}
.private__actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.5rem;
  padding: 0 var(--gutter) 3rem;
}
.step__num {
  font-size: var(--h3);
  font-weight: 600;
  color: var(--fg-40);
  display: block;
  margin-bottom: .75rem;
}

/* EDIT B/C: the private-tours route strip (was unstyled before — the old
   markup relied only on base heading/paragraph styles) and its cross-link
   to /group-travel/. */
.book-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2rem;
  margin-top: 3rem;
}
.book-grid__item {
  padding: 2rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-img);
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.book-grid__item h3 { font-size: var(--h4); }
.book-grid__item p { color: var(--fg-72); }
.book-grid__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  margin-top: auto;
  padding-top: .5rem;
}
.cross-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}
.cross-link p { max-width: 32rem; margin: 0; }

/* =========================================================================
   COMPONENTS — /private-tours/ (revamp, Sep 2026). Reuses grammar rather
   than inventing it: .pv-hero is the page's single photo strip; .ways__grid
   is the .how__grid hairline grammar at three columns (dec-x top, dec-y at
   1/3 and 2/3); .book-grid--img gives the routes grid an image head; the
   full-day accordion is .opt-acc verbatim with a modifier (the price slot
   carries the day's length); .vehicles__grid is a bordered-card grid like
   .book-grid; the custom checklist is .included as-is.
   ========================================================================= */
.pv-hero { padding-top: 0; padding-bottom: 0; }
.pv-hero__img {
  aspect-ratio: 21 / 9;
  max-width: 90rem;
  margin: 0 auto;
  border-radius: var(--radius-img);
  overflow: hidden;
}
.pv-hero__img img { width: 100%; height: 100%; object-fit: cover; }

.ways__grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 4rem;
}
.ways__grid > span:nth-of-type(1) { top: 0; left: 0; right: 0; }
.ways__grid > span:nth-of-type(2) { top: 0; bottom: 0; left: 33.333%; }
.ways__grid > span:nth-of-type(3) { top: 0; bottom: 0; left: 66.666%; }
.ways__item {
  display: flex;
  flex-direction: column;
  padding: 2rem 2rem 2.5rem 0;
}
.ways__item + .ways__item { padding-left: 2rem; }
.ways__item .label { color: var(--fg-40); margin-bottom: 1.5rem; }
.ways__item h3 {
  font-size: var(--h4);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.2;
  margin-bottom: .75rem;
}
.ways__item p { color: var(--fg-72); }
.ways__link {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
  margin-top: auto;
  padding-top: 1.5rem;
  color: var(--fg);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color .2s ease;
}
.ways__link svg { width: 16px; height: 16px; flex-shrink: 0; transition: transform .2s ease; }
.ways__link:hover, .ways__link:focus-visible { color: var(--accent); }
.ways__link:hover svg, .ways__link:focus-visible svg { transform: translateX(3px); }

.book-grid--img .book-grid__item { padding: 0; gap: 0; overflow: hidden; }
.book-grid__img {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
}
.book-grid__img img { width: 100%; height: 100%; object-fit: cover; transition: transform .6s ease; }
.book-grid__item:hover .book-grid__img img { transform: scale(1.03); }
.book-grid__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: .75rem;
  padding: 1.5rem 2rem 2rem;
}
.book-grid__meta { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; margin: 0; }
.book-grid__meta .label { color: var(--fg-60); }
.book-grid__meta .label svg { stroke: var(--accent); }
.book-grid__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .25rem .5rem;
  margin-top: .25rem;
  color: var(--fg-72);
}
.book-grid__price b { font-size: var(--h4); font-weight: 600; color: var(--fg); letter-spacing: -.02em; }
.book-grid__price + .book-grid__price { margin-top: .5rem; padding-top: .75rem; border-top: 1px solid var(--line); }
.book-grid__price-for { flex-basis: 100%; font-size: var(--body-sm); color: var(--fg-60); }

/* Full-day accordion: .opt-acc verbatim (see the tour page block below),
   the price slot printing the day's length in label type instead of a
   dollar figure; no price is shown for a full day (quoted per party). The
   schedule rows put the clock time in the .itinerary__num column. */
.opt-acc--itin { max-width: 64rem; margin-top: 3rem; }
.opt-acc--itin .opt-acc__price {
  font-size: var(--label);
  font-weight: 500;
  color: var(--fg-60);
  align-self: center;
  white-space: nowrap;
}
.opt-acc--itin .opt-acc__row { padding-right: 2rem; } /* the length label is small text, keep it clear of "Click to expand" */
.opt-acc--itin .itinerary__row { grid-template-columns: 4.5rem minmax(0, 1fr) minmax(0, 16rem); }
.opt-acc--itin .itinerary__num { font-variant-numeric: tabular-nums; }
.opt-acc--itin .opt-acc__hint { margin-top: 1.5rem; }

.custom__handles { margin-top: 4rem; max-width: 64rem; }
.custom__handles h3 { font-size: var(--h3); }
.pv-faq > h2 { margin-bottom: 2rem; }
/* On the wide layout the accordion and the checklist start where the section
   head's text column starts (.sec__head: 22rem label column + 2rem gap), so
   the page reads as one left edge for labels and one for content. */
@media (min-width: 1024px) {
  .opt-acc--itin, .custom__handles { margin-left: 24rem; }
}

.vehicles__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr)); /* six classes, two even rows */
  gap: 1.5rem;
  margin-top: 3rem;
}
.vehicle {
  display: flex;
  flex-direction: column;
  gap: .75rem;
  padding: 2rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-img);
}
.vehicle__icon { display: inline-flex; width: 2rem; height: 2rem; color: var(--accent); margin-bottom: .25rem; }
.vehicle__icon svg { width: 100%; height: 100%; }
.vehicle h3 {
  font-size: var(--h4);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.2;
}
.vehicle__cap { display: inline-flex; align-items: center; gap: .5rem; color: var(--fg); font-weight: 500; margin: 0; }
.vehicle__cap svg { width: 16px; height: 16px; stroke: var(--accent); flex-shrink: 0; }
.vehicle > p:last-child { color: var(--fg-72); }
.vehicles__note { max-width: 44rem; margin-top: 2.5rem; }

@media (max-width: 1023px) {
  .ways__grid { grid-template-columns: 1fr; }
  .ways__grid > span:nth-of-type(2), .ways__grid > span:nth-of-type(3) { display: none; }
  .ways__item { padding: 2rem 0; border-top: 1px solid var(--line); }
  .ways__item + .ways__item { padding-left: 0; }
  .ways__item:first-child { border-top: none; }
}
@media (max-width: 1023px) {
  .vehicles__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 767px) {
  .pv-hero__img { aspect-ratio: 4 / 3; }
  .vehicles__grid { grid-template-columns: 1fr; }
  .opt-acc--itin .itinerary__row { grid-template-columns: 4rem minmax(0, 1fr); }
  .opt-acc--itin .itinerary__note { grid-column: 2; }
}
@media (max-width: 767px) {
  .book-grid { grid-template-columns: 1fr; }
}

/* =========================================================================
   COMPONENTS — map
   ========================================================================= */
.map__stage {
  /* Full-bleed (Trevor, Aug 19 eve: "widen the map so it fills the page") — the
     stage breaks out of the content column to the viewport edges; the viewBox is
     the full geometry width so real roads/parks fill the sides instead of empty ink. */
  position: relative;
  width: 100vw;
  margin: 4rem calc(50% - 50vw) 0;
}
.map__stage svg {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 0;
}
.map__stage svg text {
  font-family: var(--font);
}

/* EDIT 6 (map-geo rebuild) — basemap is now real OpenStreetMap geometry
   (inc/map-geo.php's cst_map_geo(), baked at build time), four merged
   <path> elements instead of thousands of individual ones. All decorative
   (aria-hidden on the wrapping <g> in inc/render.php); every colour is a
   design token so the map reads correctly on every palette instead of
   hardcoding a hex (base tokens already flip correctly per-palette).
   pointer-events:none is load-bearing, not cosmetic: aria-hidden keeps it
   out of the accessibility tree but does nothing for hit-testing, and the
   water/parks fills are wide enough to geometrically sit under a pin —
   without this the basemap would win the hover/click over the real link on
   top of it (caught live on the old hand-drawn map: the Anacostia stroke
   was eating the Capitol pin's hover before this rule existed). Render
   order in the markup is parks, water, boundary, roads — each layer painted
   over the last, roads on top and faintest so the street grid reads without
   competing with the pins. */
.map__basemap { pointer-events: none; }
.map__parks { fill: var(--line); stroke: none; }
.map__water { fill: var(--line-strong); stroke: none; }
.map__boundary { fill: none; stroke: var(--line-strong); stroke-width: 1; stroke-linejoin: round; }
.map__roads { fill: none; stroke: var(--line); stroke-width: .6; stroke-linecap: round; opacity: .55; }
.map__landmark { fill: var(--fg-40); }
/* fg-60, not fg-40: measured against the marble ground (trap — contrast
   ladders do not transfer between backgrounds, and a "hint" label is still
   real text, not a decorative glyph like the rw__sources dot). fg-40 came
   out at 2.59:1 on marble, under even the large-text 3:1 floor; fg-60 is
   6.95:1 dark / 4.51:1 marble, matching the site's established low-emphasis
   floor (.ft__legal a, .rw__updated). */
.map__label {
  font-size: 11px;
  fill: var(--fg-60);
  letter-spacing: -.01em;
}

/* Tour pins — real <a> elements (EDIT 5: selectable, keyboard-focusable,
   linking straight to /{slug}/). Dot/glow initial state is set by motion.js
   (gsap.set scale:0 with svgOrigin), NOT here: a CSS transform on an SVG
   element overrides the transform attribute GSAP animates, so a CSS
   scale(0) would keep them invisible forever. */
.map__point { cursor: pointer; }
.map__dot { fill: var(--accent); transition: fill .15s ease; }
.map__glow { opacity: .3; transition: opacity .15s ease; }
.map__point-label {
  font-size: 12px;
  fill: var(--fg-72);
  pointer-events: none;
}
.map__point:hover .map__dot,
.map__point:focus-visible .map__dot { fill: var(--accent-deep); stroke: var(--fg); stroke-width: 2; }
.map__point:hover .map__glow,
.map__point:focus-visible .map__glow { opacity: .55; }
/* The <a>'s own box includes the (invisible until hover) info card off to
   one side, so the browser's default focus outline would ring that whole
   region rather than the dot — the dot's own accent ring above is the real
   focus indicator (WCAG 2.4.7), this just removes the mismatched box. */
.map__point:focus { outline: none; }

/* Hover/focus info card: tour name, price, rating — an SVG-native
   rect+text (no foreignObject) so it never depends on an external font or
   asset. Off-screen-safe: inc/render.php's cst_map_card_pos() flips it left
   of the pin and clamps it inside the 0 0 1200 800 viewBox before this ever
   has to paint it out of bounds. */
.map__pin-card { opacity: 0; transition: opacity .15s ease; pointer-events: none; }
.map__point:hover .map__pin-card,
.map__point:focus-visible .map__pin-card,
.map__point:focus .map__pin-card { opacity: 1; }
.map__pin-card-bg { fill: var(--ink-800); stroke: var(--line-strong); stroke-width: 1; }
.map__pin-card-title { font-size: 13px; font-weight: 600; fill: var(--fg); }
.map__pin-card-price { font-size: 11px; fill: var(--fg-72); }
.map__pin-card-rating { font-size: 11px; fill: var(--accent); }

/* ODbL requires attribution wherever the OSM-derived geometry appears;
   quiet and out of the way (lower-right of the stage), not styled as body
   copy — fg-40/body-sm same as every other "not the point" caption on the
   site (.rw__sources, .rw__updated). */
.map__attribution {
  position: absolute;
  right: .25rem;
  bottom: -1.5rem;
  margin: 0;
  line-height: 1;
}
.map__attribution a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.map__attribution a:hover, .map__attribution a:focus-visible { color: var(--fg-60); }

.map__legend {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 1.5rem;
  margin-top: 2rem;
}
.map__legend a {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 44px; /* the redundant, guaranteed-tappable accessible path alongside the SVG pins */
  border-top: 1px solid var(--line);
  padding-top: 1rem;
  color: var(--fg);
  font-weight: 500;
  transition: color .2s ease;
}
.map__legend a:hover, .map__legend a:focus-visible { color: var(--accent); }
.map__legend small {
  display: block;
  color: var(--fg-60);
  font-size: var(--body-sm);
  margin-top: .25rem;
  font-weight: 400;
}

/* QC pass (tap targets): the mobile crop in site.js (mapMobileZoom) zooms
   the SAME 1200x800 viewBox in to make the pins tappable, which scales the
   in-SVG <text> labels right along with everything else — tight enough for
   ~40px pins and it pushes "Lincoln Assassination" and "Capitol Hill" past
   the stage's right edge (an SVG's default overflow is hidden, so this
   clips rather than causing page overflow, but it still reads broken).
   The map__legend links below already give every route's full name as
   real, always-visible, always-tappable text, so the in-map label is pure
   decoration at this width — hide it rather than shrink the crop back down
   and lose the tap-target fix it exists for. */
@media (max-width: 767px) {
  .map__point-label { display: none; }
}

/* =========================================================================
   COMPONENTS — how it works
   ========================================================================= */
.how__head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
  flex-wrap: wrap;
}
.how__grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  margin-top: 4rem;
}
.how__item {
  padding: 2rem 1.5rem 0 0;
}
.how__item .label { color: var(--fg-40); margin-bottom: 1.5rem; }
/* PHP prints these item headings as <h3> (was <h4>); keep them at the h4
   SCALE — they're small item labels, not section headings. */
.how__item h3, .step h3, .tour__content .why-item h3 {
  font-size: var(--h4);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.2;
}
.how__item h3 { margin-bottom: .75rem; }
.how__item p { color: var(--fg-72); }

/* =========================================================================
   COMPONENTS — group travel (EDIT D). group-grid follows the same
   dec-x(top)/dec--row/dec-x(bottom)/dec-y(centre) grammar as .featured__grid;
   the how-it-works row below reuses .how__grid as-is. */
.group-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: 4rem;
}
.group-grid > .dec--top { top: 0; left: 0; right: 0; }
.group-grid > .dec--row { position: relative; grid-column: 1 / -1; height: 1px; top: auto; }
.group-grid > .dec--bottom { bottom: 0; left: 0; right: 0; }
.group-grid > .dec--y { top: 0; bottom: 0; left: 50%; }
.group-card { padding: 2.5rem; }
.group-card h2 { font-size: var(--h3); margin-bottom: 1rem; }
.group-card p { color: var(--fg-72); }
.group-card p + p { margin-top: .75rem; }

.group-cta__in {
  max-width: 42rem;
  margin: 0 auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
}
.group-cta__in p a {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color .2s ease;
}
.group-cta__in p a:hover, .group-cta__in p a:focus-visible { color: var(--accent); }

@media (max-width: 1023px) {
  .group-grid > .dec--y { display: none; }
}
@media (max-width: 767px) {
  .group-grid { grid-template-columns: 1fr; }
  .group-grid > .dec--row { display: none; }
  .group-card:not(:first-child) { border-top: 1px solid var(--line); padding-top: 2rem; }
}

/* =========================================================================
   COMPONENTS — reviews
   ========================================================================= */
.reviews {
  display: grid;
  grid-template-columns: 1fr 22rem;
  gap: 4rem;
}
.reviews__grid {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
}
.review-card {
  padding: 2rem;
}
.review-card__quote { margin: 0; font-size: var(--body-lg); color: #fff; } /* margin:0 — the UA blockquote default (1em 40px) indented every grid card's quote by 40px (Trevor, Aug 21: "the reviews look janky") */
.review-card__name {
  margin-top: 1.5rem;
  font-weight: 600;
  color: #fff;
}
.review-card__platform {
  color: var(--fg-60);
  font-size: var(--body-sm);
}
.reviews__side .label { margin-bottom: 1.5rem; }

/* Review widget (.rw) — one component, site-wide (home About block, tour
   pages, About, Private). Head line: score · stars · count · platforms
   [· route count]; then cards in a hairline row. */
.rw { margin-top: 3rem; }
.rw__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem 1rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--line);
}
.rw__score { font-size: var(--h3); font-weight: 600; letter-spacing: -.02em; line-height: 1; color: #fff; }
.rw__head .stars { align-self: center; }
.rw__count { font-weight: 600; color: #fff; }
.rw__sources::before, .rw__route::before, .rw__updated::before { content: '·'; margin-right: 1rem; color: var(--fg-40); }
.rw__updated { font-size: var(--body-sm); }
.rw__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 0;
}
/* Tour page: the widget sits in the 856px content column, where auto-fit
   fitted only two 18rem cards per row and orphaned the third on its own
   row with a hairline hanging off it. Three equal columns, one row, no
   bottom rule; a single stacked column below 768px. */
.tour__content .rw__grid { grid-template-columns: repeat(3, 1fr); }
.tour__content .rw__grid .review-card { border-bottom: none; }
.rw__grid .review-card { padding: 2rem 2rem 2rem 0; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.rw__grid .review-card:not(:first-child) { padding-left: 2rem; }
.rw__grid .review-card:last-child { border-right: none; }
.rw__grid .review-card .stars { margin-bottom: 1rem; }
.rw__grid .review-card__quote { font-size: var(--body); color: var(--fg-72); }
.about__reviews { margin-top: 3rem; }

/* Rotating variant: one card visible at a time, cross-fading (site.js cycles
   .is-current every data-rotate ms; pauses on hover/focus; reduced motion shows
   the first card and stops). Fills the About column under the stats. */
.rw--rotate { margin-top: 3rem; }
.rw--rotate .rw__grid { display: grid; grid-template-columns: 1fr; }
.rw--rotate .review-card {
  grid-area: 1 / 1;
  padding: 2rem 0 0;
  border: none;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .6s ease, transform .6s ease;
  pointer-events: none;
}
.rw--rotate .review-card.is-current { opacity: 1; transform: none; pointer-events: auto; }
/* Reveal engine handles the very first paint; after that the rotation owns opacity. */
.rw--rotate .rw__grid .review-card { visibility: visible; }
.rw--rotate .rw__grid .review-card, .rw--rotate .rw__grid .review-card:not(:first-child) { padding: 2rem 0 0; }
.rw--rotate .review-card__quote { margin: 0; font-size: var(--body-lg); color: #fff; }
.rw__dots { display: flex; gap: .5rem; margin-top: 1.5rem; }
.rw__dot {
  width: 8px; height: 8px; padding: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .25);
  transition: background .2s ease, transform .2s ease;
}
.rw__dot.is-current { background: var(--accent); transform: scale(1.25); }
.rw__dot:hover { background: rgba(255, 255, 255, .6); }
@media (prefers-reduced-motion: reduce) {
  .rw--rotate .review-card { transition: none; }
}

/* Review wall (.rw--wall): two columns of cards drifting upward at different
   speeds inside a masked viewport; pauses on hover; static (scrollable) under
   reduced motion. Fills the About column beside the photo. */
.rw--wall { display: flex; flex-direction: column; margin-top: 2.5rem; }
.rw--wall .rw__cols {
  flex: 1 1 auto;
  min-height: 0;
  contain: size;             /* contributes no intrinsic height — it takes the leftover, never makes the row taller */
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 2.5rem;
  overflow: hidden;
  padding-top: 1rem;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 6%, #000 90%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 6%, #000 90%, transparent 100%);
}
.rw--wall .rw__col { min-height: 0; overflow: hidden; }
.rw--wall .rw__track {
  display: flex;
  flex-direction: column;
  animation: rw-drift var(--dur, 80s) linear infinite;
  will-change: transform;
}
.rw--wall:hover .rw__track, .rw--wall:focus-within .rw__track { animation-play-state: paused; }
@keyframes rw-drift { from { transform: translateY(0); } to { transform: translateY(-50%); } }
.rw--wall .review-card {
  padding: 1.5rem 0;
  border-bottom: 1px solid var(--line);
  border-right: none;
  margin: 0;
}
.rw--wall .rw__grid .review-card:not(:first-child), .rw--wall .review-card:not(:first-child) { padding-left: 0; }
.rw--wall .review-card__quote { margin: 0; font-size: var(--body); color: var(--fg-72); }
.rw--wall .review-card .stars { margin-bottom: .75rem; }
.rw--wall .review-card__name { margin-top: 1rem; }
@media (prefers-reduced-motion: reduce) {
  .rw--wall .rw__track { animation: none; }
  /* .rw__col's own overflow:hidden was clipping the (now-static) track to the
     column height with nothing left to scroll — .rw__cols had overflow:auto
     but nothing overflowed IT, only its children. Let the column itself size
     to its real content and hand scrolling to .rw__cols. The seamless-loop
     second copy of the list is dead weight once nothing is animating, so it
     is dropped from layout entirely here — otherwise every review would
     render twice for reduced-motion users. */
  .rw--wall .rw__col { overflow: visible; }
  .rw--wall .rw__dup { display: none; }
  .rw--wall .rw__cols { overflow: auto; mask-image: none; -webkit-mask-image: none; }
}
@media (max-width: 1023px) {
  .about__content > .rw--wall, #reviews .rw--wall { min-height: 28rem; max-height: 32rem; }
  .rw--wall .rw__cols { grid-template-columns: 1fr; }
  .rw--wall .rw__col:last-child { display: none; }
}
.sec--reviews-widget h2 { margin-bottom: 1rem; }
@media (max-width: 767px) {
  .rw__grid .review-card { padding: 1.5rem 0; border-right: none; }
  .rw__grid .review-card:not(:first-child) { padding-left: 0; }
}

/* =========================================================================
   COMPONENTS — footer (EDIT A, Aug 19: photo dropped, four-column hairline
   grid replaces the old flex row — see inc/render.php cst_render_footer()
   for the reasoning)
   ========================================================================= */
.ft {
  background: var(--ink-900);
  padding: 6rem var(--gutter) 0;
}
.ft__grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 2rem;
  --n: 4;
  padding: 3rem 0;
}
.ft__grid > .dec--top { top: 0; left: 0; right: 0; }
.ft__grid > .dec--y { top: 0; bottom: 0; left: calc(100% * var(--i, 1) / var(--n, 4)); }
.ft__col {
  display: flex;
  flex-direction: column;
  gap: .85rem;
}
.ft__col-h {
  margin-bottom: .15rem;
}
.ft__col-h--sp { margin-top: 1.25rem; }
.ft__wordmark {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-weight: 600;
  font-size: 1.0625rem;
  letter-spacing: -.02em;
  color: #fff;
}
.ft__wordmark svg { width: 20px; height: 20px; }
.ft__tagline { max-width: 16rem; }
.ft__col--links a, .ft__col--contact a, .ft__col--follow a {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  color: #fff;
  font-size: var(--body-sm);
  transition: color .2s ease;
}
.ft__col--links a svg, .ft__col--contact a svg { width: 16px; height: 16px; stroke: currentColor; flex-shrink: 0; }
.ft__col--links a:hover, .ft__col--contact a:hover, .ft__col--follow a:hover,
.ft__col--links a:focus-visible, .ft__col--contact a:focus-visible, .ft__col--follow a:focus-visible { color: var(--accent); }
.ft__col--contact address { color: var(--fg-72); font-size: var(--body-sm); margin-top: .1rem; }
.ft__bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1.5rem 0;
  border-top: 1px solid var(--line);
  font-size: var(--body-sm);
  color: var(--fg-60);
}
.ft__legal {
  display: flex;
  gap: 1.5rem;
}
.ft__legal a:hover, .ft__legal a:focus-visible { color: var(--accent); }
address { font-style: normal; }

/* =========================================================================
   TOUR PAGE
   ========================================================================= */
.tour__intro {
  padding: calc(var(--header-h) + 3rem) var(--gutter) 0;
  max-width: 90rem;
  margin: 0 auto;
}
.breadcrumbs {
  display: flex;
  align-items: center;
  gap: .5rem;
  color: var(--fg-60);
  font-size: var(--body-sm);
  margin-bottom: 1.5rem;
}
.breadcrumbs a { transition: color .2s ease; }
.breadcrumbs a:hover { color: #fff; }
/* Tour title reads at the h2 scale, not the full hero h1 — the hero-sized
   default made every tour page open on a headline as big as the homepage. */
.tour__intro h1 {
  font-size: var(--h2);
  font-weight: 600;
  letter-spacing: -.03em;
  line-height: 1.05;
  margin-top: 1rem;
}
.gal {
  display: grid;
  grid-template-columns: 61fr 19.5fr 19.5fr;
  grid-template-rows: 1fr 1fr;
  gap: 1.5rem;
  margin-top: 3rem;
}
/* Every gallery cell needs its OWN definite aspect-ratio: object-fit:cover
   only crops correctly when the box already has a height. The old
   aspect-ratio:auto on the wide top-right image let a portrait source photo
   set that row's height directly (~700px), inflating the whole gallery and
   reading as a ~300px gap before the lead paragraph. */
.gal img { width: 100%; height: 100%; object-fit: cover; }
/* Aug 21 (Trevor: "why is there so much space here?"): the rows used to be
   sized by the side photos' own aspect boxes — the wide top-right one set
   both 1fr rows at 335px, the squares only filled 256 of theirs, and the
   main photo stopped 80px short. Now ONLY the main photo has an aspect
   ratio; the side cells are positioned boxes that fill whatever row height
   that gives them. The gallery ends where the photos end. */
.gal__main { grid-column: 1; grid-row: 1 / 3; aspect-ratio: 3 / 2; }
.gal__side { position: relative; min-height: 0; overflow: hidden; }
.gal__side img { position: absolute; inset: 0; }
.gal__side:nth-of-type(2) { grid-column: 2 / 4; grid-row: 1; }
.gal__side:nth-of-type(3) { grid-column: 2; grid-row: 2; }
.gal__side:nth-of-type(4) { grid-column: 3; grid-row: 2; }
/* The lead is the first thing in .tour__content (moved there Aug 21 so the
   sticky card starts level with it, right under the gallery); the gap from
   the gallery is .tour__body's margin. max-width keeps the measure readable. */
.tour__lead { max-width: 44rem; }

.tour__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 27.5rem;
  gap: 4rem;
  max-width: 90rem;
  /* 2rem: this is now the gallery-to-lead gap (Trevor, Aug 21: "have the
     sticky card start right under the picture and push the description up
     to fit snug"); the lead sits first in .tour__content on every route. */
  margin: 2rem auto 0;
  padding: 0 var(--gutter) 6rem;
  align-items: start;
}
/* The lead is a <p>, not a section, so the flex gap (5rem) would follow it;
   pull the first section up so lead-to-Options / lead-to-About reads as one
   block: 2.5rem (Capitol Hill's accordion precedent, .private__acc). */
.tour__content > .tour__lead + section { margin-top: -2.5rem; }
.tour__content {
  display: flex;
  flex-direction: column;
  gap: 5rem;
}
.tour__content > section { scroll-margin-top: calc(var(--header-h) + 1.5rem); }
/* Content sub-heads (About / What's included / Itinerary / …) read at the
   h3 scale, like the reference's "About Retreats" — the bare h2 default was
   as big as the tour's own H1. */
.tour__content h2 {
  font-size: var(--h3);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.15;
}
.tour__content .why-item h3 { margin-bottom: .5rem; }
.tour__content .why-item { padding: 1.5rem 0; border-top: 1px solid var(--line); }
.tour__content .why-item:first-child { border-top: none; padding-top: 0; }

.included {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 2rem;
  margin-top: 1.5rem;
}
.included__col {
  display: flex;
  flex-direction: column;
}
.included__col li {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: .75rem 0;
  border-top: 1px solid var(--line);
  color: var(--fg-72);
}
.included__col li:first-child { border-top: none; padding-top: 0; }
.included__col li svg { width: 16px; height: 16px; stroke: var(--accent); flex-shrink: 0; margin-top: .1em; }

.itinerary {
  margin-top: 1.5rem;
  display: flex;
  flex-direction: column;
}
.itinerary__row {
  display: grid;
  grid-template-columns: 2.5rem 12rem 1fr;
  gap: 1.5rem;
  padding: 1.5rem 0;
  border-top: 1px solid var(--line);
}
.itinerary__row:first-child { border-top: none; padding-top: 0; }
.itinerary__num { color: var(--fg-60); font-weight: 500; } /* fg-40 is 3.77:1 on ink-900 — decoration only, never text */
/* var(--fg), not a hardcoded #fff: this row is now reused inside the tour
   page's options accordion panels (.opt-acc__itin) as well as the page's
   own Itinerary section, both on the palette-reactive body ground. The
   hardcode measured 1.10:1 under marble (QC, Aug 21, CLAUDE.md trap 4 —
   "contrast ladders do not transfer between grounds") since marble flips
   the ground light but this stayed literal white; var(--fg) resolves to
   marble's own dark ink automatically, at 13.82:1, with zero visible change
   on the shipped harbor palette (var(--fg) is #fff there too). */
.itinerary__stop { font-weight: 600; color: var(--fg); }
.itinerary__note { color: var(--fg-72); }

/* Tour page options accordion (.opt-acc, Trevor's Aug 21 review): the
   first section of .tour__content, directly under the lead. It was briefly
   a full-width block in .tour__intro; Trevor's second Aug 21 note — the
   three options "don't need to extend the whole page, because it now
   pushes down the sticky booking card" — put it in the left column, so
   .tour__body (and the pinned aside) starts right after the lead. The
   gap from the lead is .tour__body's own margin-top, no margin here.

   The collapse/expand animation is a deliberate, literal reuse of the
   mobile nav dropdown (.mnav / .mnav.is-active below, in the 767px media
   query): identical transform (translateY(-8px) -> 0), identical opacity
   fade, identical visibility-delay-on-close-only trick, identical
   durations/easing (transform .3s ease, opacity .25s ease). The one
   addition is grid-template-rows 0fr -> 1fr for the height itself, which
   .mnav does not need (it is a fixed-position overlay, not in document
   flow) but the home page's private-tours accordion already does, on the
   same .3s ease timing so it reads as one motion rather than two. */
/* h2 sizing now comes from .tour__content h2 (var(--h3)) like every other
   content sub-head; the section carries no margin of its own. */
/* Trevor, Aug 23 review: a one-line hint under the H2 so the collapsed rows
   read as "open me for the full tour" rather than a bare price list. Same
   quiet-copy treatment as .opt-acc__facts / .opt-acc__desc — fg-72, no
   emphasis, it is a hint, not a heading. */
.opt-acc__hint { margin-top: .75rem; color: var(--fg-72); max-width: 42rem; }
.opt-acc__list {
  margin-top: 1rem;
  display: flex;
  flex-direction: column;
}
.opt-acc__item { border-top: 1px solid var(--line); }
.opt-acc__item:last-child { border-bottom: 1px solid var(--line); }
/* margin:0 already comes from the sitewide h1-h5 reset; the real point of
   this rule is font-size — the bare global `h3 { font-size: var(--h3) }`
   rule would otherwise reach .opt-acc__btn via its `font: inherit` and win
   over .opt-acc__sub's own (unset) size, since .opt-acc__name is sized
   explicitly but .opt-acc__sub was not: QC caught it rendering at 31.68px,
   BIGGER than the 25.92px name above it (var(--h4)) — exactly backwards for
   a "name, then a smaller one-line sub" collapsed row. font-size:0 here
   stops the h3 default at the source; every visible descendant sizes
   itself explicitly instead of trusting inheritance through a heading tag. */
.opt-acc__h { margin: 0; font-size: 0; }
.opt-acc__btn {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: .375rem;
  /* Trevor, Aug 23 review: right padding grew from 2.5rem to make room for
     .opt-acc__cta ("See full tour") sitting beside the icon in this same
     gutter; the 480px query below takes it back to icon-only room, where
     .opt-acc__cta is hidden (CLAUDE.md trap 3 territory — narrow tap targets
     get the plain "+" back rather than crowding name/price/sub). */
  padding: 1.5rem 9rem 1.5rem 0;
  position: relative;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.opt-acc__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.opt-acc__name {
  font-size: var(--h4);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.15;
  color: var(--fg);
  transition: color .2s ease;
}
.opt-acc__price {
  font-size: var(--h4);
  font-weight: 600;
  color: var(--fg);
  flex-shrink: 0;
}
.opt-acc__sub {
  font-size: var(--body-lg);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -.01em;
  color: var(--fg-72);
  max-width: 42rem;
}
.opt-acc__icon {
  position: absolute;
  top: 1.5rem;
  right: 0;
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.75rem;
  font-weight: 400;
  line-height: 1;
  color: var(--fg-60);
  transition: transform .3s ease, color .2s ease;
}
/* Trevor, Aug 23 review: "See full tour" sits in the same top-right gutter
   as the "+", just to its left, so the pair reads as one affordance. This is
   meaningful text (not aria-hidden, unlike the icon) — the row's name/price/
   sub are unaffected because .opt-acc__btn's right padding above already
   reserves the room. Matches .label's own size/weight (style.css .label)
   rather than inventing an uppercase treatment the theme doesn't otherwise
   use. Hidden once the row is open (.opt-acc__btn[aria-expanded="true"]
   below) — the rotated "+" alone reads as close. */
.opt-acc__cta {
  position: absolute;
  top: 1.5rem;
  right: 2.75rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  font-size: var(--label);
  font-weight: 500;
  color: var(--fg-60);
  white-space: nowrap;
  transition: color .2s ease;
}
/* Trevor, Aug 24: "Tap to expand" (touch is the norm) at ≤1023px, "Click to
   expand" at ≥1024px -- matches the theme's own touch/mouse cutoff
   (initPins()/ScrollSmoother in motion.js already split the same way).
   Both spans exist in the DOM at every width so assistive tech always reads
   accurate wording; only one is ever visually shown. */
.opt-acc__cta-mouse { display: none; }
@media (min-width: 1024px) {
  .opt-acc__cta-touch { display: none; }
  .opt-acc__cta-mouse { display: inline; }
}
.opt-acc__btn:hover .opt-acc__name, .opt-acc__btn:focus-visible .opt-acc__name,
.opt-acc__btn:hover .opt-acc__icon, .opt-acc__btn:focus-visible .opt-acc__icon,
.opt-acc__btn:hover .opt-acc__cta, .opt-acc__btn:focus-visible .opt-acc__cta {
  color: var(--accent);
}
.opt-acc__btn[aria-expanded="true"] .opt-acc__icon {
  color: var(--accent);
  transform: rotate(45deg);
}
.opt-acc__btn[aria-expanded="true"] .opt-acc__cta { display: none; }
/* Identical property list/timing to .mnav / .mnav.is-active (767px media
   query below): transform .3s ease, opacity .25s ease, visibility toggled
   with a trailing 0s delay on close only so the panel's Book link and text
   stay in the tab order and legible until the slide/fade has finished, and
   drop out immediately (no delay) the moment it opens. grid-template-rows
   supplies the height, on the same .3s ease as the transform so both read
   as one movement. */
.opt-acc__panel-wrap {
  display: grid;
  grid-template-rows: 0fr;
  visibility: hidden;
  opacity: 0;
  transform: translateY(-8px);
  transition: grid-template-rows .3s ease, transform .3s ease, opacity .25s ease, visibility 0s linear .3s;
}
.opt-acc__item:has(.opt-acc__btn[aria-expanded="true"]) .opt-acc__panel-wrap {
  grid-template-rows: 1fr;
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
  transition: grid-template-rows .3s ease, transform .3s ease, opacity .25s ease, visibility 0s;
}
.opt-acc__panel {
  min-height: 0; /* trap: grid items default to min-height:auto and refuse to shrink below content — vertical twin of the min-width:0 trap */
  overflow: hidden;
}
.opt-acc__panel-in { padding: 0 0 2rem; }
.opt-acc__desc { color: var(--fg-72); max-width: 44rem; }
.opt-acc__for { color: var(--fg); font-weight: 500; margin-top: 1rem; }
.opt-acc__includes {
  margin-top: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: .75rem;
}
.opt-acc__includes li { display: flex; align-items: flex-start; gap: .75rem; color: var(--fg-72); }
.opt-acc__mark {
  flex-shrink: 0;
  width: 16px; height: 16px;
  margin-top: .1em;
  display: inline-flex;
}
.opt-acc__mark svg { stroke: var(--accent); }
/* Included rows use the check glyph (same as .included__col); only the
   excluded row keeps the "+" rotated into an "x". Trevor, Aug 21: a "+" on
   these rows looked like the accordion's own clickable "+" one line up. */
.opt-acc__mark--out { transform: rotate(45deg); }
.opt-acc__mark--out svg { stroke: var(--fg-60); }
.opt-acc__facts { margin-top: 1.25rem; color: var(--fg-60); }
/* Days and departure times for the option, printed as two icon rows right
   under the description (Trevor, Aug 21). Same 16px accent icon grammar as
   .included__col. */
.opt-acc__when { display: flex; flex-wrap: wrap; gap: .5rem 2rem; margin: 1rem 0 1.25rem; color: var(--fg-72); }
.opt-acc__when li { display: flex; align-items: center; gap: .5rem; }
.opt-acc__when b { color: var(--fg); font-weight: 600; }
.opt-acc__when-ico { display: inline-flex; width: 16px; height: 16px; }
.opt-acc__when-ico svg { stroke: var(--accent); }
.opt-acc__itin { margin-top: 1.5rem; display: flex; flex-direction: column; }
.opt-acc__itin .itinerary__row:first-child { border-top: none; padding-top: 0; }
/* Trevor, Aug 23 review: full width of the panel and centered, same pattern
   already used for .booking-panel .btn (width:100%; justify-content:center)
   rather than a new full-width button rule — .btn is inline-flex, so
   justify-content is what actually centers the label/icon once width is
   forced to 100%. */
.opt-acc__book { margin-top: 1.5rem; width: 100%; justify-content: center; }
/* No dedicated prefers-reduced-motion override here, matching .mnav (not
   .private__acc, which does add one): .mnav relies solely on the global
   catch-all at the top of this file (transition-duration: .001ms !important
   on *), and this component reuses .mnav's grammar exactly, so it inherits
   the same reduced-motion behaviour for free. */
/* Trevor, Aug 23 follow-up: mobile is 90% of visitors, so hiding the "See
   full tour" text under 480px (the first pass) is out — it has to be
   abundantly obvious there, not just implied by a bare "+". Below 480px the
   CTA drops out of the absolute top-right overlay (there's no room beside
   name/price there) and back into normal document flow instead, as its own
   line under .opt-acc__sub, left-aligned, full row width available to it.
   The "+" stays put, top-right, unchanged. Still hidden once the row is
   open (.opt-acc__btn[aria-expanded="true"] .opt-acc__cta above applies at
   every width, media query or not). */
@media (max-width: 480px) {
  .opt-acc__btn { padding-right: 2.5rem; }
  .opt-acc__cta {
    position: static;
    height: auto;
    align-self: flex-start;
    margin-top: .125rem;
  }
}

/* Trevor, Aug 25 (final pre-prod edit): Know before you go and Meeting point
   (both share this .know component) restyled off the checkmark bullet onto
   the same hairline-row idiom as .itinerary__row / .opt-acc__item — 1px
   var(--line) borders BETWEEN items, first-child drops its own top border and
   the padding that would otherwise double up with the section above, no
   trailing border on the last item (matching .itinerary, not .opt-acc, since
   this list is not immediately followed by anything that needs a closing
   line). Icon is the waypoint circle-dot (cst_icon('waypoint')), the same
   mark the section-label motif already uses, in place of the checkmark that
   used to imply "included." What's included (.included__col) is unchanged. */
.know {
  margin-top: 1.5rem;
  display: flex;
  flex-direction: column;
}
.know li {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: .75rem 0;
  border-top: 1px solid var(--line);
  color: var(--fg-72);
}
.know li:first-child { border-top: none; padding-top: 0; }
.know li svg { width: 16px; height: 16px; color: var(--accent); stroke: currentColor; flex-shrink: 0; margin-top: .1em; }

/* Private tour section (#private, cst_render_private_section in
   inc/render.php). Trevor, Sep 7: "put it below 'know before you go' and give
   it its own quick scroll button between know before you go and reviews."
   Hairline rows like .know and .included__col rather than a boxed card, so
   it reads as a section of the page: one row per private rate (Capitol Hill
   has two, the BIG 3 at its own fee), then the route's Peek PRIVATE button
   and the /private-tours/ link on one line. The h2 takes .tour__content h2's
   scale like every other section; the lead matches .opt-acc__hint. */
.tour-private__lead { margin-top: .75rem; color: var(--fg-72); max-width: 42rem; }
.tour-private__rates {
  display: flex;
  flex-direction: column;
  margin-top: 1.5rem;
  max-width: 42rem;
}
.tour-private__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .25rem .625rem;
  padding: .875rem 0;
  border-top: 1px solid var(--line);
  color: var(--fg-72);
}
.tour-private__price:first-child { border-top: none; padding-top: 0; }
.tour-private__price b { font-size: var(--h4); font-weight: 600; color: var(--fg); letter-spacing: -.02em; }
.tour-private__for { flex-basis: 100%; font-size: var(--body-sm); color: var(--fg-60); }
.tour-private__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
  margin-top: 1.5rem;
}
.tour-private__link {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
  font-weight: 500;
  color: var(--fg);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color .2s ease;
}
.tour-private__link:hover, .tour-private__link:focus-visible { color: var(--accent); }

details {
  border-top: 1px solid var(--line);
  padding: 1.5rem 0;
}
details:first-of-type { border-top: none; padding-top: 0; }
details summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  font-weight: 600;
  font-size: var(--body-lg);
  color: #fff;
}
details summary::-webkit-details-marker { display: none; }
details summary::after {
  content: "+";
  font-size: 1.5rem;
  font-weight: 400;
  color: var(--fg-60);
  flex-shrink: 0;
  transition: transform .2s ease;
}
details[open] summary::after { transform: rotate(45deg); }
details p { margin-top: 1rem; color: var(--fg-72); max-width: 42rem; }

.tour__side {
  display: flex;
  flex-direction: column;
}
.side__price { font-size: var(--h3); }
.side__price b { color: #fff; }
/* EDIT 6: this route's own rating once trusted, else the operator aggregate
   said plainly — cst_tour_rating_line() in inc/render.php. var(--fg) rather
   than a hardcoded #fff, so it is correct on every palette with no override. */
.side__rating {
  display: flex;
  align-items: center;
  gap: .4rem;
  margin-top: .5rem;
  color: var(--fg-72);
  font-size: var(--body-sm);
}
.side__rating b { color: var(--fg); font-weight: 600; }
.side__meta {
  display: flex;
  flex-direction: column;
  margin: 2rem 0;
}
.side__meta > * {
  display: flex;
  align-items: center;
  gap: .5rem;
  border-top: 1px solid var(--line);
  padding: .75rem 0;
  font-size: var(--body-sm);
  color: var(--fg-72);
}
.side__meta svg { width: 16px; height: 16px; stroke: currentColor; flex-shrink: 0; }
/* icon + label grouped left (gap above); value pushed to the far right by
   its own margin-left:auto instead of justify-content:space-between, which
   with 3 children (icon, label, value) spaced the label into the middle of
   the row instead of next to its icon. */
.side__val {
  margin-left: auto;
  color: var(--fg);
  font-weight: 600;
}
.side__nav {
  display: flex;
  flex-direction: column;
  gap: .35rem;
  margin-bottom: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.side__nav a {
  color: var(--fg-60);
  font-size: var(--body-sm);
  padding: .35rem 0;
  transition: color .2s ease;
}
.side__nav a.is-active, .side__nav a:hover { color: var(--fg); } /* QC, Sep 7 (with the Private tour tab): was #fff, which is invisible on the light Long View palette; --fg is #fff on every dark palette, so nothing changes there */
.booking-panel {
  display: flex;
  flex-direction: column;
  gap: .75rem;
}
.booking-panel .btn { width: 100%; justify-content: center; }

/* =========================================================================
   OTHER PAGES — about / private / faq / legal / contact / 404
   ========================================================================= */
.page-intro {
  max-width: 90rem;
  margin: 0 auto;
  padding: calc(var(--header-h) + 5rem) var(--gutter) 5rem;
}
.page-intro .label { margin-bottom: 1.5rem; }
/* Secondary pages use the h2 scale for their title — the hero word is the only
   thing on the site that runs at --h1 (matches the reference's inner pages). */
.page-intro h1 { font-size: var(--h2); letter-spacing: -.03em; line-height: 1.05; max-width: 20ch; }
.page-intro__lead { margin-top: 1.5rem; max-width: 44rem; }

/* /tours/: slim intro (Trevor, Aug 20: "just say Our Tours and then straight
   into what we offer"), title left with the filter rail beside it, straight
   into the grid. Full-width — NOT the centered 90rem column the other
   page-intros use: the grid section below is full-bleed to the gutter, so on
   a wide monitor a centered intro reads as drifted toward the middle (Trevor
   saw exactly this at ~1920px). Same gutter as the grid, so the h1 and the
   cards share a left edge at every width. */
.page-intro--tours {
  max-width: none;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem 3rem;
  flex-wrap: wrap;
  padding-bottom: 2.5rem;
}
.tours-grid { padding-top: 0; }
.tours-grid .featured__grid { margin-top: 0; }

/* Filter chips: pills matching the header CTA's geometry; the active chip
   inverts to the site's white-pill state. (A hairline-rail version was tried
   Aug 20; Trevor preferred the pills.) */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
}
.filter {
  font: inherit;
  font-size: var(--body-sm);
  color: var(--fg-72);
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .625rem 1.375rem;
  cursor: pointer;
  transition: color .2s ease, border-color .2s ease, background-color .2s ease;
}
.filter:hover { color: #fff; border-color: var(--fg-60); }
.filter.is-active { color: var(--ink-900); background: #fff; border-color: #fff; }
html[data-scheme="light"] .filter.is-active { color: #fff; background: var(--fg); border-color: var(--fg); } /* on a light ground the white pill vanished (found on the Long View preview, Sep 7) */

/* Filtered state: cards hide via [hidden]; the between-row rules and the
   centre vertical are placed for the full set, so they come off and a 1px
   top shadow per card redraws the row seams wherever the rows now fall
   (the first row's shadow lands on the grid's own dec--top — same pixel). */
.tour-card[hidden] { display: none; }
.featured__grid.is-filtered > .dec--row { display: none; }
.featured__grid.is-filtered > .tour-card { box-shadow: 0 -1px 0 0 var(--line); }

/* Prose sections (about / private-tours body): image | text, like the
   reference's about block. cst_prose_sections() emits .prose__img first when a
   section has one; sections without an image are a single measured column. */
.prose__img {
  aspect-ratio: 4 / 5;
  max-width: 32rem;
  border-radius: var(--radius-img);
  overflow: hidden;
}
.prose__img img { width: 100%; height: 100%; object-fit: cover; }
.sec:has(> .prose__img) {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  column-gap: 4rem;
  row-gap: 1.25rem;
  align-items: start;
}
.sec:has(> .prose__img) > .prose__img { grid-column: 1; grid-row: 1 / span 20; }
.sec:has(> .prose__img) > :not(.prose__img) { grid-column: 2; }
.sec:not(:has(> .prose__img)) > h2,
.sec:not(:has(> .prose__img)) > p { max-width: 44rem; }
.sec > h2 + p, .sec > p + p { margin-top: 1.25rem; }
.sec:has(> .prose__img) > h2 { font-size: var(--h3); }
@media (max-width: 1023px) {
  .sec:has(> .prose__img) { grid-template-columns: 1fr; }
  .sec:has(> .prose__img) > .prose__img { grid-row: auto; max-width: none; aspect-ratio: 16 / 10; }
  .sec:has(> .prose__img) > :not(.prose__img) { grid-column: 1; }
}

/* =========================================================================
   COMPONENTS — blog (index cards + single post)

   Index reuses the established tours-grid idiom verbatim: the wrapper is
   `.featured__grid` itself (same hairline dec--top/dec--row/dec--bottom
   placement rules already defined above, which key off that class name),
   with `.blog-card` as a sibling to `.tour-card` rather than sharing that
   class — content differs enough (no price, no options, a date and an
   excerpt instead) that reusing `.tour-card` directly would drag along
   price/props rules that don't apply, but the numbers below (grid split,
   gap, padding, image radius/ratio) match `.tour-card` exactly so the two
   read as one family on the page.
   ========================================================================= */
.blog-card {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 2rem;
  padding: 2.5rem;
}
.blog-card__body { display: flex; flex-direction: column; }
.blog-card__date { color: var(--fg-60); font-size: var(--body-sm); margin-bottom: .75rem; }
.blog-card__title { margin-bottom: 1rem; }
.blog-card__excerpt { color: var(--fg-72); margin-bottom: 1.5rem; max-width: 42rem; }
.blog-card__visual {
  border-radius: var(--radius-img);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  align-self: start;
  background: var(--ink-800); /* holds the shape if a post has no featured image */
}
.blog-card__visual img { width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 1023px) {
  .blog-card { grid-template-columns: 1fr; }
  .blog-card__visual { order: -1; aspect-ratio: 3 / 2; }
}

/* Single post: readable measure, native-looking old-content markup. `a`,
   `ul`/`ol` and heading colour all need restating here because the theme
   resets them globally (style.css "a { color:inherit }", "ul,ol {
   list-style:none }") for its own chrome, which would otherwise flatten
   plain prose from the database into unreadable, unmarked walls of text.
   Link colour is white + underline, not --accent: --accent is decoration
   only on this palette (knowledge/marketing/website.md) and fails AA as a
   text/link colour, so the underline (not a second colour) carries the
   affordance, matching the "amber is never text" rule sitewide. */
.post-body {
  max-width: 42rem;
  color: var(--fg-72);
  font-size: var(--body-lg);
  line-height: 1.7;
}
.post-body > * + * { margin-top: 1.5rem; }
.post-body h2, .post-body h3, .post-body h4 { color: var(--fg); margin-top: 3rem; }
.post-body h2:first-child, .post-body h3:first-child, .post-body h4:first-child { margin-top: 0; }
.post-body ul, .post-body ol { padding-left: 1.25rem; display: flex; flex-direction: column; gap: .5rem; }
.post-body ul { list-style: disc; }
.post-body ol { list-style: decimal; }
.post-body li::marker { color: var(--fg-60); }
.post-body strong, .post-body b { color: var(--fg); font-weight: 600; }
.post-body a { color: var(--fg); text-decoration: underline; text-underline-offset: .2em; text-decoration-color: var(--fg-60); }
.post-body a:hover, .post-body a:focus-visible { text-decoration-color: var(--fg); }
.post-body blockquote { border-left: 2px solid var(--line-strong); padding-left: 1.5rem; color: var(--fg); font-style: italic; }
.post-body figure { margin: 0; }
.post-body img { display: block; width: 100%; height: auto; border-radius: var(--radius-img); }
.post-body figcaption { font-size: var(--body-sm); color: var(--fg-60); margin-top: .5rem; }
.post-body table { width: 100%; border-collapse: collapse; }
.post-body th, .post-body td { border: 1px solid var(--line); padding: .5rem .75rem; text-align: left; }
.post-body th { color: var(--fg); }
.post__meta { color: var(--fg-60); font-size: var(--body-sm); margin-bottom: 1rem; }
.post__hero {
  max-width: 90rem;
  margin: 2.5rem auto 0;
  padding: 0 var(--gutter);
}
.post__hero img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-img);
}

/* =========================================================================
   COMPONENTS — guides ("The faces behind Cornerstone", About page, EDIT E).
   Elegant blank avatar (initial letter, hairline ring) rather than a stock
   or AI face; reveal is hover, keyboard focus (:focus-within) and a JS tap
   toggle (site.js) all driving the same .guide-card__panel, whose closed
   state is visibility:hidden — not the `hidden` attribute — so it drops out
   of the accessibility tree without any extra aria bookkeeping.
   ========================================================================= */
.guides {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem;
  margin-top: 2.5rem;
}
.guide-card {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-img);
  overflow: hidden;
  background: var(--ink-800);
  border: 1px solid var(--line);
}
.guide-card__trigger {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  width: 100%;
  height: 100%;
  padding: 1.5rem;
  background: none;
  border: none;
  text-align: center;
}
.guide-card__avatar {
  width: 5.5rem;
  height: 5.5rem;
  border-radius: 50%;
  background: var(--ink-700);
  box-shadow: 0 0 0 1px var(--line-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.guide-card__letter {
  font-size: var(--h4);
  font-weight: 600;
  color: var(--fg-72);
  letter-spacing: -.02em;
}
.guide-card__name {
  font-size: var(--body-lg);
  font-weight: 600;
  color: #fff;
  letter-spacing: -.01em;
}
.guide-card__panel {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: .85rem;
  padding: 1.75rem;
  overflow-y: auto;
  background: var(--ink-900);
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s ease;
  pointer-events: none;
}
/* :focus-within reveals the panel visually for plain Tab navigation (no
   Enter needed) but deliberately leaves pointer-events:none. A `<button>`
   is given focus by the browser as part of mousedown/touchstart, BEFORE
   its own mouseup/click completes — confirmed mid-build: with
   pointer-events:auto here, that native focus-on-press made the panel
   cover the button between mousedown and mouseup, so mouseup landed on
   the panel instead, the click target became their common ancestor, and
   NEITHER the trigger's nor the panel's click handler ever ran (a click
   or a tap could never open the card at all). Keyboard Enter/Space
   activation is dispatched straight to the focused element and never
   hit-tested, so it is entirely unaffected by pointer-events either way. */
.guide-card:focus-within .guide-card__panel {
  opacity: 1;
  visibility: visible;
}
.guide-card.is-open .guide-card__panel {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
/* Real hover only — gated behind (hover:hover), not a bare :hover, since a
   touch tap still satisfies :hover in Chromium's touch emulation and would
   race the same way. Touch relies on .is-open (site.js) instead. */
@media (hover: hover) and (pointer: fine) {
  .guide-card:hover .guide-card__panel {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
}
.guide-card__panel-name { font-size: var(--body-lg); font-weight: 600; color: #fff; }
.guide-card__bio { font-size: var(--body-sm); color: var(--fg-72); line-height: 1.5; }
.guide-card__quote {
  margin: 0;
  padding-top: .75rem;
  border-top: 1px solid var(--line);
  font-size: var(--body-sm);
  color: #fff;
  font-style: normal;
}
.guide-card__quote cite {
  display: block;
  margin-top: .5rem;
  font-style: normal;
  color: var(--fg-60);
}
@media (prefers-reduced-motion: reduce) {
  .guide-card__panel { transition: none; }
}
@media (max-width: 1023px) {
  .guides { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 767px) {
  .guides { grid-template-columns: 1fr; }
}

.legal {
  display: grid;
  grid-template-columns: 16rem 1fr;
  gap: 4rem;
  max-width: 90rem;
  margin: 0 auto;
  padding: 0 var(--gutter) 6rem;
  align-items: start;
}
.legal__nav {
  display: flex;
  flex-direction: column;
  gap: .35rem;
}
.legal__nav a {
  color: var(--fg-60);
  font-size: var(--body-sm);
  padding: .35rem 0;
  transition: color .2s ease;
}
.legal__nav a.is-active, .legal__nav a:hover { color: #fff; }
.legal__text {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  max-width: 44rem;
  color: var(--fg-72);
}
.legal__text h2 { color: #fff; font-size: var(--h4); margin-top: 1rem; }
.legal__text p { color: var(--fg-72); }

.contact {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4rem;
  max-width: 90rem;
  margin: 0 auto;
  padding: 0 var(--gutter) 6rem;
  align-items: start;
}
.contact__aside {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}
.contact__channels {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.contact__channel {
  display: flex;
  gap: 1rem;
  padding: 1.25rem 0;
  border-bottom: 1px solid var(--line);
}
.contact__channel:first-child { padding-top: 0; }
.contact__channel-icon { color: var(--fg-60); margin-top: .2rem; }
.contact__channel-body {
  display: flex;
  flex-direction: column;
  gap: .35rem;
}
.contact__channel-body .label { color: var(--fg-60); }
.contact__channel-link {
  color: #fff;
  font-size: var(--body);
  font-style: normal; /* <address> */
  text-decoration: none;
}
a.contact__channel-link:hover { text-decoration: underline; }
.contact__visual {
  border-radius: var(--radius-img);
  overflow: hidden;
  aspect-ratio: 3 / 2; /* landscape: the channels list above already fills the column */
}
.contact__visual img { width: 100%; height: 100%; object-fit: cover; }
.contact__form {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
}
.inp-group {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  padding-bottom: .75rem;
  border-bottom: 1px solid var(--line);
}
.inp-group__label {
  font-size: var(--body-sm);
  color: var(--fg-60);
}
.inp-group input, .inp-group textarea, .inp-group select {
  background: transparent;
  border: none;
  color: #fff;
  font-size: var(--body);
  font-family: var(--font);
  padding: 0;
  resize: vertical;
}
.inp-group select { color-scheme: dark; }
.inp-group select option { color: #000; } /* native popup chrome, outside page CSS's reach */
.inp-group input::placeholder, .inp-group textarea::placeholder { color: var(--fg-40); }
.inp-group input:focus, .inp-group textarea:focus, .inp-group select:focus { outline: none; }
.inp-group.is-error { border-bottom-color: var(--accent); }
.inp-group.is-error .inp-group__label { color: var(--accent); }
.inp-group__req { color: var(--accent); margin-left: .15rem; }
.inp-group__error {
  display: none;
  font-size: var(--body-sm);
  color: var(--accent);
}
.inp-group.is-error .inp-group__error { display: block; }
.contact__form .is-sent-msg { display: none; color: #fff; }
.contact__form.is-sent .is-sent-msg { display: block; }
.contact__form.is-sent .inp-group, .contact__form.is-sent button[type="submit"] { display: none; }
/* ?sent=0 (inc/forms.php): keep the fields so the guest can retry, add the line. */
.is-failed-msg { display: none; color: var(--accent); }
.is-failed .is-failed-msg { display: block; }
.is-sent-msg:focus, .is-failed-msg:focus { outline: none; }
.is-sending button[type="submit"] { opacity: .6; pointer-events: none; }

/* EDIT C: the private-tours interest form — same .inp-group component, a
   two-column layout since it carries nine fields rather than three. */
.private-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.75rem 2rem;
  margin-top: 3rem;
  max-width: 56rem;
}
.private-form .inp-group--full { grid-column: 1 / -1; }
.private-form button[type="submit"] { grid-column: 1 / -1; justify-self: start; margin-top: .5rem; }
.private-form .is-sent-msg { grid-column: 1 / -1; display: none; color: #fff; }
.private-form .is-failed-msg { grid-column: 1 / -1; }
.private-form.is-sent .is-sent-msg { display: block; }
.private-form.is-sent .inp-group, .private-form.is-sent button[type="submit"] { display: none; }
@media (max-width: 767px) {
  .private-form { grid-template-columns: 1fr; }
}

.err404 {
  position: relative;
  overflow: hidden;
  min-height: 70vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: calc(var(--header-h) + 4rem) var(--gutter) 4rem;
}
.err404__links {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 2rem;
}

.mbar { display: none; } /* ≤1023px only; shown in the first responsive block */

/* =========================================================================
   RESPONSIVE
   ========================================================================= */
@media (max-width: 1023px) {
  .tour__body { grid-template-columns: 1fr; gap: 3rem; }
  /* Mobile and tablet booking bar (.mbar), ≤1023px — the pinned aside only
     exists on desktop (motion.js no-ops pins at ≤1023), so below that this
     is the persistent CTA (QC, Aug 21): fixed to the foot of the screen, the
     header's frosted glass (blur, no tint), price left, one button right. */
  .mbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 90;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .75rem var(--gutter) calc(.75rem + env(safe-area-inset-bottom));
    border-top: 1px solid var(--line);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
  }
  .mbar__price { margin: 0; color: var(--fg-72); }
  .mbar__price b { color: var(--fg); font-size: 1.25rem; font-weight: 600; }
  .mbar__btn { flex: 0 0 auto; }
  body:has(.mbar) .ft { padding-bottom: 5.5rem; } /* the fixed bar must not cover the footer's legal line */
  /* QC, Aug 24: at ≤1023px the open consent banner (bottom:0, same as .mbar)
     fully covered the fixed booking bar. The bar is the mobile booking
     action (Trevor, Aug 23) and must stay visible and clickable while the
     banner is open and undecided, so the banner is raised above it rather
     than the bar being hidden or demoted below it. .mbar measures 73px tall
     (padding + its one line of content) plus the safe-area inset on a
     notched device; z-index stays as authored (.cst-consent's 200 still
     above everything, .mbar's 90 unchanged) since only the vertical
     position needs to move, not the stacking order. */
  body:has(.mbar) .cst-consent { bottom: calc(73px + env(safe-area-inset-bottom)); }
  /* Trevor, Aug 23 review, round 2: the fixed .mbar bar above is the mobile
     booking action; the static "Choose an option" / "Book privately" duo and
     its secure-checkout note, sitting near the top of the page, is redundant
     with it at this width and goes. Desktop (.tour__side, >1023px) is
     unchanged — .booking-panel there is the only booking CTA. */
  .booking-panel { display: none; }
  /* QC, Sep 7: the aside-above-content order used to live in the ≤767px block
     only, so at 768 to 1023px the whole card (price, rating, specs, on-page
     nav) rendered after Reviews, just above the footer. Tablets now get the
     phone order: card first, its on-page nav after the card. The nav's
     horizontal-strip styling stays ≤767px; at tablet it is the vertical list
     it always was, just relocated. */
  .tour__side { order: -1; position: static; }
  .side__nav { order: 1; }
  .legal { grid-template-columns: 1fr; gap: 3rem; }
  .contact { grid-template-columns: 1fr; gap: 3rem; }
  .about__body { grid-template-columns: 1fr; gap: 2.5rem; }
  .reviews { grid-template-columns: 1fr; gap: 2.5rem; }
  .routes__grid, .stats, .how__grid, .map__legend, .ft__grid { grid-template-columns: repeat(2, 1fr); }
  .featured__grid { grid-template-columns: 1fr; }
  .tour-card { grid-template-columns: 1fr 16rem; }
  /* Below 1024px the grids reflow to two (or one) columns, so the animated
     vertical rules — placed for the desktop column count — would cut through
     the cards. They are a desktop flourish: hide them here and let borders on
     the cards themselves draw the dividers, which follow the real grid at any
     count. (Trevor caught this at ~930px, Aug 19.) */
  .routes__grid > .dec--y, .featured__grid > .dec--y, .how__grid > .dec--y, .featured__grid > .dec--row, .ft__grid > .dec--y { display: none; }
  .route-card:nth-of-type(2n) { border-left: 1px solid var(--line); }
  .route-card:nth-of-type(n+3) { border-top: 1px solid var(--line); }
  .how__item:nth-of-type(2n) { border-left: 1px solid var(--line); }
  .how__item:nth-of-type(n+3) { border-top: 1px solid var(--line); }
  .featured__grid > .tour-card:not(:nth-of-type(1)) { border-top: 1px solid var(--line); }
  .private__steps { grid-template-columns: 1fr; gap: 2rem; }
  .sec__head { grid-template-columns: 1fr; gap: 1.5rem; }
}

@media (max-width: 767px) {
  .tour__content .rw__grid { grid-template-columns: 1fr; }
  .tour__content .rw__grid .review-card, .tour__content .rw__grid .review-card:not(:first-child) { padding: 1.5rem 0; border-right: none; border-bottom: 1px solid var(--line); }
  .tour__content .rw__grid .review-card:last-child { border-bottom: none; }
  /* Trevor, Aug 21: "on mobile, all of the header text is too large, same
     with the tour cards" — reference is GetYourGuide's mobile scale. The
     hero's own H1 gets its own override below (it stays the one big brand
     moment); every other h1 drops to ~26px, h2 to ~22px, h3/card titles to
     ~18px, and the two body tokens (leads, and the "lg" size used for
     prices/specs) step down to ~17px/~15px. This --h1 override now mostly
     reaches .err404, the one page still on the bare h1 element — .page-intro
     and .tour__intro size their own h1 off --h2 (see the comments at their
     base rules) and get pulled back into the h1 bucket just below, since a
     tour/page title is still an h1 even though it borrows the h2 token. */
  :root {
    --h1: 1.625rem;
    --h2: 1.375rem;
    --h3: 1.125rem;
    --h4: 1.125rem;
    --body-xxl: 1.0625rem;
    --body-lg: .9375rem;
  }
  /* h1/h2 run at line-height 1/1.05 on desktop, tuned for a handful of huge
     display words — at reading sizes that is too tight once a title wraps
     to two lines (h3/h4 are already 1.15/1.2, already sensible, left alone).
     Also bumps .hero__title (still one short line, so no visible cost). */
  h1, .h1, h2, .h2 { line-height: 1.15; }
  .body-xxl, .body-lg { line-height: 1.45; }
  .page-intro h1, .tour__intro h1 { font-size: 1.625rem; line-height: 1.15; }
  /* .tour__content h2 sizes off --h3 (a real h2 element deliberately kept
     smaller than the tour's own h1 — see its base rule) — keep it in the h2
     bucket rather than following h3 all the way down to card-title size. */
  .tour__content h2 { font-size: 1.375rem; }
  /* .side__price also sizes off --h3; GetYourGuide's price reads a step
     bigger than a card title, so give it its own step instead of following
     h3 down to 18px. */
  .side__price { font-size: 1.25rem; }
  /* The bold price figure inside .tour-card__price should read a step above
     the paragraph it sits in (now --body-lg, 15px) rather than matching it —
     GetYourGuide's price is visually distinct from the meta line around it. */
  .tour-card__title { font-size: 1.1875rem; line-height: 1.25; } /* GYG reference: ~19px card title, 1.25 leading */
  .tour-card__price b { font-size: 1.25rem; } /* GYG reference (IMG_0779): the price is the loudest thing on the card, ~20px */
  /* .opt-acc__sub sets its own line-height (1.35, not the .body-lg utility),
     so the bump above does not reach it — same reasoning applies now that
     it is dropping to 15px. */
  .opt-acc__sub { line-height: 1.45; }

  .sec { padding: 4rem var(--gutter); }
  .tours-grid { padding-top: 0; } /* the generic .sec reset above would re-open the gap under the /tours/ filter rail */
  .private__in { padding-top: 4rem; }
  .private__acc { margin-top: 1.75rem; }
  .private__steps { padding: 2rem var(--gutter); }
  .private__actions { padding: 0 var(--gutter) 2rem; }

  .hd__dec--2 { display: none; }

  .mnav {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1.5rem;
    position: fixed;
    /* Not inset:0 + bottom:0: .mnav's containing block is .hd (its DOM
       parent), and .hd's own backdrop-filter makes IT the containing block
       for fixed descendants instead of the viewport (backdrop-filter joins
       transform/filter/perspective in triggering that per spec). .hd's own
       box is only header-h (72px) tall, so `bottom:0` resolved against
       THAT, not the viewport, and the panel collapsed to its own padding
       (measured: 64px tall instead of covering the screen). An explicit vh
       height sidesteps the wrong containing block entirely. */
    top: var(--header-h);
    left: 0; right: 0;
    height: calc(100vh - var(--header-h));
    z-index: 90;
    background: var(--ink-900);
    padding: 2rem var(--gutter);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: transform .3s ease, opacity .25s ease, visibility 0s linear .3s;
  }
  .mnav.is-active {
    transform: translateY(0);
    opacity: 1;
    visibility: visible;
    transition: transform .3s ease, opacity .25s ease, visibility 0s;
  }
  .mnav a:not(.btn) {
    font-size: var(--h4);
    font-weight: 600;
    color: #fff;
  }
  .mnav .btn { align-self: flex-start; margin-top: 1rem; }

  .hero { aspect-ratio: auto; min-height: 100svh; }
  .hero__text { padding-bottom: 4rem; }
  .hero__title { font-size: 2.25rem; margin-bottom: var(--hero-title-lift-m, 2rem); }
  .hero__kicker { font-size: 1.25rem; }
  /* Lincoln on a phone: same idea — memorial in the upper half, text stack on the water. */
  .hero--hero-lincoln-sunset { --hero-title-lift-m: 1rem; min-height: 100svh; }
  .hero--hero-lincoln-sunset .hero__title, .hero--hero-monument-blossoms .hero__title, .hero--hero-monument-blossoms-dark .hero__title { font-size: 2.25rem; } /* per-image hero rules outrank .hero__title; cover each */
  .hero__kicker, .hero--hero-lincoln-sunset .hero__kicker, .hero--hero-monument-blossoms .hero__kicker, .hero--hero-monument-blossoms-dark .hero__kicker { font-size: 1.125rem; }
  .hero--hero-monument-blossoms-dark .hero__text { padding-bottom: 6vh; }
  .hero--hero-monument-blossoms-dark .hero__back, .hero--hero-monument-blossoms-dark .hero__front { object-position: center 45%; }
  /* Phone: the stack is taller relative to the frame, so the title lands nearer the shore and the
     reflection; a deeper, taller bottom scrim keeps white text above 4:1 there (measured 3.5:1
     against the brightest patches before this). */
  .hero--hero-monument-blossoms-dark .hero__front--fallback { height: 58%; background: linear-gradient(to top, rgba(var(--ink-900-rgb), .85) 0%, rgba(var(--ink-900-rgb), .55) 40%, rgba(var(--ink-900-rgb), 0) 100%); }
  .hero--hero-lincoln-sunset .hero__text { padding-bottom: 5vh; }
  .hero--hero-lincoln-sunset .hero__back, .hero--hero-lincoln-sunset .hero__front { object-position: center 60%; }
  /* Both layers need the SAME object-fit/position so the (eventual)
     transparent-sky front cutout stays pixel-registered with the back
     photo on a portrait crop; keeps the dome in frame on tall viewports. */
  .hero__back, .hero__front { object-position: center 38%; }

  .routes__grid, .stats, .how__grid, .map__legend,
  .about__body, .reviews__grid, .private__steps, .ft__grid {
    grid-template-columns: 1fr;
  }
  .tour-card { grid-template-columns: 1fr; }
  .tour-card__visual { order: -1; aspect-ratio: 3 / 2; } /* GYG reference: landscape card photo, so the title is on the first screen */
  /* Trevor, Aug 21: four stacked photos cost four screens before the copy.
     One photo at a time, swipe for the rest — a CSS scroll-snap carousel,
     no JS, the desktop order kept. Edge-to-edge, 4:3. */
  .gal {
    display: flex;
    gap: .75rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin: 1.25rem calc(-1 * var(--gutter)) 0;
    padding: 0 var(--gutter);
  }
  .gal::-webkit-scrollbar { display: none; }
  .gal__main, .gal__side, .gal__side:nth-of-type(2), .gal__side:nth-of-type(3), .gal__side:nth-of-type(4) {
    grid-column: auto; grid-row: auto;
    flex: 0 0 calc(100vw - 2 * var(--gutter));
    aspect-ratio: 4 / 3;
    scroll-snap-align: start;
    border-radius: .5rem;
    overflow: hidden;
  }
  /* Vita reference flow on mobile (Trevor, Aug 21): photo, then price / specs
     / CTA, then a horizontal strip of section tabs, THEN the copy. The aside
     moves above the content (order, now set in the ≤1023px block so tablets
     share it), unpinned; its nav becomes a one-line swipeable strip here. The
     gallery-to-card gap closes to 1.5rem. */
  .tour__body { gap: 2rem; margin-top: 1.5rem; }
  .side__nav {
    display: flex;
    flex-direction: row;
    gap: 1.5rem;
    font-size: var(--body);
    overflow-x: auto;
    white-space: nowrap;
    scrollbar-width: none;
    margin: 0 calc(-1 * var(--gutter));
    padding: 0 var(--gutter) .75rem;
    border-bottom: 1px solid var(--line);
  }
  .side__nav::-webkit-scrollbar { display: none; }
  .side__nav a { flex: 0 0 auto; }
  .side__nav { border-top: none; padding-top: 0; margin-top: .75rem; margin-bottom: 0; } /* after the card, as the reference; the order itself is set in the ≤1023px block */
  .included { grid-template-columns: 1fr; }
  .tour-private__price b { font-size: 1.25rem; }
  .tour-private__actions { flex-direction: column; align-items: stretch; }
  .tour-private__actions .btn { width: 100%; justify-content: center; }
  .itinerary__row { grid-template-columns: 1fr; gap: .5rem; }
  .opt-acc__row { flex-direction: column; gap: .25rem; }
  .opt-acc__btn { padding-right: 2.5rem; }
  .ft { padding-top: 4rem; }
  .ft__grid { gap: 2.5rem; padding: 2.5rem 0; }

  /* Single column: no vertical rules at all; card borders carry the dividers. */
  .routes__grid > .dec--y, .featured__grid > .dec--y, .how__grid > .dec--y,
  .reviews__grid > .dec--y, .reviews__divider, .ft__grid > .dec--y { display: none; }
  .route-card:nth-of-type(2n), .how__item:nth-of-type(2n) { border-left: none; }
  .route-card:nth-of-type(n+2), .how__item:nth-of-type(n+2) { border-top: 1px solid var(--line); }
  .reviews__grid > .review-card:not(:nth-of-type(1)) { border-top: 1px solid var(--line); }
}

/* =========================================================================
   COMPONENTS — consent banner (measurement package, inc/measurement.php +
   assets/js/consent.js). Structure and behaviour are the production port
   verbatim (same #cst-consent id, .cst-consent-prefs, [data-cst-action]
   contract); this stylesheet is new, adapted to this theme's own tokens
   (--ink-800/900, --fg, --line, --radius-pill, --font) rather than the old
   theme's, since the old stylesheet could not be fetched in this session
   (no outbound network from this build). Fixed to the bottom of the
   viewport, above the header (z-index above .hd's 100), hidden by default
   and shown by consent.js toggling [hidden] and [data-open].
   ========================================================================= */
.cst-consent {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 200;
  display: flex;
  justify-content: center;
  padding: 1rem;
  pointer-events: none;
  transform: translateY(100%);
  opacity: 0;
  transition: transform .3s ease, opacity .3s ease;
}
/* QC, Aug 24: author-origin CSS always beats the UA [hidden] rule at any
   specificity (origin outranks specificity in the cascade), so `.cst-consent
   { display:flex }` above was silently keeping the closed banner in the flex
   layout — visually off-screen (translateY(100%), opacity 0) but still
   `display:flex`, so its buttons stayed in the tab order (QC reached Accept
   at tab stop 43 on every page load). consent.js closes the banner by
   setting the `hidden` PROPERTY (root.hidden = true in consent.js's close()),
   which reflects onto the `hidden` ATTRIBUTE — key the override off that,
   with enough specificity to beat `.cst-consent`'s own `display: flex`. */
.cst-consent[hidden] { display: none; }
.cst-consent[data-open="true"] {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
}
.cst-consent__panel {
  width: 100%;
  max-width: 640px;
  background: var(--ink-800);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .35);
  padding: 1.5rem;
  font-family: var(--font);
}
.cst-consent__text {
  font-size: var(--body-sm);
  color: var(--fg-72);
  margin: 0 0 1rem;
  line-height: 1.5;
}
.cst-consent__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .625rem;
}
.cst-consent-btn-primary,
.cst-consent-btn-ghost,
.cst-consent-btn-text {
  display: inline-flex;
  align-items: center;
  height: 40px;
  padding: 0 1.25rem;
  border-radius: var(--radius-pill);
  font-weight: 500;
  font-size: var(--body-sm);
  border: none;
  cursor: pointer;
  transition: background-color .2s ease, color .2s ease;
}
.cst-consent-btn-primary {
  background: var(--accent);
  color: var(--ink-900);
}
.cst-consent-btn-primary:hover,
.cst-consent-btn-primary:focus-visible {
  background: var(--accent-deep);
}
.cst-consent-btn-ghost {
  background: rgba(255, 255, 255, .08);
  color: var(--fg);
  border: 1px solid var(--line-strong);
}
.cst-consent-btn-ghost:hover {
  background: var(--ink-700);
}
.cst-consent-btn-text {
  background: transparent;
  color: var(--fg-72);
  padding-left: .25rem;
  padding-right: .25rem;
}
.cst-consent-btn-text:hover,
.cst-consent-btn-text:focus-visible {
  color: var(--fg);
}
.cst-consent-prefs {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: .75rem;
}
/* QC, Aug 24: same defect as #cst-consent above — consent.js's open(false)
   sets prefs.hidden = true on first paint (only "Manage cookies" flips it to
   false), but `.cst-consent-prefs { display:flex }` overrode the UA [hidden]
   rule regardless, so the checkboxes + Save preferences panel showed
   immediately instead of waiting for Manage cookies. */
.cst-consent-prefs[hidden] { display: none; }
.cst-consent__row {
  display: flex;
  align-items: flex-start;
  gap: .625rem;
  font-size: var(--body-sm);
  color: var(--fg-72);
  cursor: pointer;
}
.cst-consent__row input[type="checkbox"] {
  margin-top: .2rem;
  flex-shrink: 0;
}
/* The footer reopen control (inc/render.php, .ft__legal). Styled to match
   the other legal links it sits beside, as a <button> rather than an <a>
   since it opens the banner rather than navigating. */
.cst-consent-reopen {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: none;
  transition: color .2s ease;
}
.cst-consent-reopen:hover,
.cst-consent-reopen:focus-visible {
  color: var(--accent);
}
@media (max-width: 599px) {
  .cst-consent { padding: 0; }
  .cst-consent__panel { max-width: none; border-radius: 12px 12px 0 0; }
}

/* =========================================================================
   MOTION STATES (last: FOUC guard)
   ========================================================================= */
html.js:not(.motion-off) [data-m]:not(.m-ready),
html.js:not(.motion-off) [data-anim]:not(.m-ready) {
  visibility: hidden;
}

/* Above-fold entrance that never gates paint (HO-034 G3, Aug 25). The tour
   page's LCP element (the lead gallery image) and everything above it must be
   visible before any script runs: the [data-anim] path above holds elements at
   visibility:hidden until the deferred GSAP bundle executes, which on lab
   slow-4G chained first paint to the whole JS payload (22.3s simulated LCP on
   the old production theme; 5.9s here). .lcp-rise is the script-free
   replacement for the tour intro only: transform-only (opacity stays 1, so
   the browser counts the first frame as the paint), CSS animation so it starts
   at first render with no JS in the loop, same 0/.15/.3/.45/.55/.65/.8s
   stagger the GSAP version used (--rise-d, set inline in render.php). Fill is
   'both' so a delayed element waits at its 28px offset instead of flashing at
   rest first. Transforms never move other boxes, so this contributes no CLS. */
.lcp-rise {
  animation: lcp-rise .7s cubic-bezier(0.215, 0.61, 0.355, 1) both;
  animation-delay: var(--rise-d, 0s);
}
@keyframes lcp-rise {
  from { transform: translateY(28px); }
  to   { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .lcp-rise { animation: none; }
}
html.motion-off .lcp-rise { animation: none; }

/* =========================================================================
   LONG VIEW — header and menu overrides. Kept at the end of the file so they
   out-order the shared light-scheme flips above. The kit (p.5) says never to
   place the mark over busy photography, so under Long View the header is a
   solid bar from first paint rather than glass over the hero.
   ========================================================================= */
/* longview (navy) keeps the site's glass header exactly as harbor has it: transparent at the top, blur on scroll, no tint (Trevor, Sep 7: "no more liquid glass" was the one thing he missed first). The hero's own top scrim carries the logo. Only the light rendering gets a solid bar. */
html[data-palette="longview-light"] { --lv-gold: #8A6D2E; }
html[data-palette="longview-light"] .hd, html[data-palette="longview-light"] .hd.is-active { background: rgb(250, 250, 248); }
html[data-palette="longview-light"] body.has-hero .hd__nav a:not(.btn), html[data-palette="longview-light"] body.has-hero .hd__logo,
html[data-palette="longview-light"] .hd__logo { color: var(--fg); text-shadow: none; }
/* Every light palette: the mobile menu's links were hardcoded white on the
   light ground (found by QC on the Long View preview, Sep 7; identical on
   marble and the rest). Scoped to the scheme, so the dark palettes are untouched. */
html[data-scheme="light"] .mnav a:not(.btn) { color: var(--fg); }
/* Same family of misses, found by Lighthouse on the Long View preview
   (webmaster-5c, Sep 7) and a computed-style sweep of every page: text that
   was hardcoded white for the dark ground. Scheme-wide, dark palettes untouched. */
html[data-scheme="light"] details summary, html[data-scheme="light"] .guide-card__name,
html[data-scheme="light"] .contact__channel-link, html[data-scheme="light"] a.contact__channel-link { color: var(--fg); }
html[data-scheme="light"] .skip { background: var(--fg); color: var(--ink-900); } /* the focused skip link was white on the light ground */

