/* ── system/fonts.css ── */
/* ============================================================================
   ATLAS WILD EXPERIENCES — webfonts
   ----------------------------------------------------------------------------
   The two faces named on the cheat sheet, both SIL Open Font Licence 1.1,
   vendored under ../fonts/ with their licences. No CDN, no cost.

   Derived by ingest_kit.py from the client's supplied zips — subset to latin,
   then instanced down to the axes this site actually uses:

     Playfair Display   294 KB  ->  49 KB   variable wght 400–700
     Afacad Flux        421 KB  ->  31 KB   variable wght 300–700, slant pinned

   80 KB for two variable faces. Do not add a third: the cheat sheet also
   names a "logo font", but supplies no file for it — it exists only as
   outlines inside the lockup artwork. The wordmark is therefore always the
   SVG lockup, never live text, and Playfair carries every heading.

   Paths are relative to this file (system/), so link it as
   <link rel="stylesheet" href="/system/fonts.css"> and keep the tree intact.
   ========================================================================== */

/* Display — headings and pull-quotes. High-contrast serif; give it air and
   never set it below 25px, where the hairlines start to disappear. */
@font-face {
  font-family: "Playfair Display";
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url("/assets/fonts/playfair-display/PlayfairDisplay-latin-var.woff2")
       format("woff2-variations");
}

/* A metrics-matched stand-in for Playfair, and the reason it exists:
   `font-display: swap` paints every heading in the fallback serif first and
   swaps when the real face arrives. Georgia is wider and shorter than Playfair,
   so the swap RE-WRAPPED the hero headline and shoved the page down — Google
   measured 0.098 of the home page's 0.11 layout shift on `.hero-copy`, all of
   it attributed to this font loading (PageSpeed, 2026-09-16). Preloading the
   file helped the 15 small shifts but not this one: on a throttled connection
   the swap still lands after first paint.

   These numbers are measured from the font binary, not guessed. Playfair is
   unitsPerEm 1000, hhea ascent 1082, descent -251, line gap 0, average
   character width 533. `size-adjust` scales Georgia so its average character
   is the same width as Playfair's, and the ascent/descent overrides are the
   real metrics divided by that scale, so a line of fallback text occupies the
   same box as the line that replaces it. The swap then changes the letterforms
   and moves nothing.

   Only for the fallback — never put overrides on the real face.

   AND IT IS NOT ENOUGH ON ITS OWN. `local()` resolves nothing on a machine
   with no Georgia — Linux, most Android devices, and Google's own PageSpeed
   test machines. Tried alone on 2026-09-16 it left the shift at 0.09 and the
   page total went UP to 0.225, because the body font swap was the larger half
   all along. The faces above are `font-display: optional`, which is what
   actually fixes it on every device; this block is the belt to that braces,
   and earns its keep on the desktops that do have Georgia. */
@font-face {
  font-family: "Playfair fallback";
  src: local("Georgia"), local("Times New Roman"), local("Times");
  size-adjust: 98.8%;
  ascent-override: 109.51%;
  descent-override: 25.4%;
  line-gap-override: 0%;
}

/* Body AND data. Afacad Flux ships `tnum` and `zero`, so the same face sets
   running text and the figures in the logbook and the seat ticker — which is
   why this brand needs no monospace. Columns of numbers get
   `font-feature-settings: var(--awe-figures)`. */
@font-face {
  font-family: "Afacad Flux";
  font-style: normal;
  font-weight: 300 700;
  font-display: optional;
  src: url("/assets/fonts/afacad-flux/AfacadFlux-latin-var.woff2")
       format("woff2-variations");
}

/* Preload both in <head> — they are the only two, and both are above the fold:
   <link rel="preload" as="font" type="font/woff2" crossorigin
         href="/assets/fonts/afacad-flux/AfacadFlux-latin-var.woff2">
   <link rel="preload" as="font" type="font/woff2" crossorigin
         href="/assets/fonts/playfair-display/PlayfairDisplay-latin-var.woff2">
                                                                            */

/* ── system/variables.css ── */
/* ============================================================================
   ATLAS WILD EXPERIENCES — token contract
   ----------------------------------------------------------------------------
   BE CURIOUS ✦ BE CONNECTED ✦ BE IN AWE

   The single source of truth for the brand's values. Paste this whole :root
   block into any page's first <style> before writing component CSS; every
   var(--*) used by the components in kit/components.html resolves from here.

   EVERY COLOUR BELOW COMES FROM ONE OF TWO PLACES
     · the eight hexes printed on page 2 of the client's brand cheat sheet
       (vendored at brand-kit/Atlas-Brand-CheatSheet.pdf), or
     · derive_tokens.py, which moves one of those eight along OKLab lightness
       until it meets WCAG AA on the ground it will actually sit on.
   Nothing is eyeballed. Run `python derive_tokens.py --check` to re-verify all
   36 pairings; it is a build gate.

   Tier map (see design_system_management SKILL.md §3):
     A1-identity   8   the brand — no fallback can substitute
     A1-structure 18   type scale, rhythm, container
     A2           26   defaultable, but declared here so nothing resolves empty
     B-slot        4   richer tier, aliased or independently bound
     C-extension  --awe-*  private to this brand

   FOUR GROUNDS, ONE CONTRACT
   The kit is not a palette with an accent — it is a set of flat colour fields
   butted against each other, and its own artwork is composed that way (page 1
   is six blocks of solid colour sharing hard edges). So this system gives you
   five grounds and rebinds the identity tokens on each. No component CSS
   changes between them:

     (default)                cream  #f4e7ce   long-form reading
     data-surface="forest"    forest #2c3c1a   chrome, ticker, footer
     data-surface="night"     black  #000000   galleries, photograph-forward
     data-surface="sand"      sand   #ffdd9d   call-to-action bands
     data-surface="paper"     white  #ffffff   product headers (the store's)

   THE ONE RULE THAT IS EASY TO GET WRONG
   Orange #dd7d36 is the brand's loudest colour and reads 2.43:1 on cream —
   below even the large-text floor. It is a FILL in this system: button
   grounds, rules, the pattern. It is never text on a light ground. Links on
   light take rust #8c3900 (6.36:1); links on forest stay cream and carry an
   orange underline, so the brand colour is in the link without being asked to
   carry the legibility.
   ========================================================================== */

:root {

  /* ── A1-identity ─────────────────────────────────────────────────────────
     Declaration order matters: the workspace hub reads the first six colour
     values as this system's swatch row. */
  --bg: #f4e7ce;                 /* Cream  — the page                        */
  --surface: #ffffff;            /* White  — raised panels                   */
  --fg: #2c3c1a;                 /* Forest — ink, 9.71:1                     */
  --muted: #676b4d;              /* derived — captions, 4.53:1               */
  --border: #cbc3a9;             /* derived — rules and dividers             */
  --accent: #8c3900;             /* Rust   — links and emphasis, 6.36:1      */
  /* "Playfair fallback" sits FIRST among the substitutes on purpose: it is
     Georgia re-proportioned to Playfair's metrics (see fonts.css), so the
     headline occupies the same space before and after the web font loads.
     Without it the swap moved the page. The plain names stay behind it for
     anyone whose browser ignores the overrides. */
  --font-display: "Playfair Display", "Playfair fallback", "Iowan Old Style", Georgia, serif;
  --font-body: "Afacad Flux", "Avenir Next", "Segoe UI", system-ui, sans-serif;

  /* ── B-slot ──────────────────────────────────────────────────────────── */
  --surface-warm: #ffdd9d;       /* Sand — one step warmer than the page     */
  --fg-2: #5b6545;               /* derived — de-emphasised body, 5.06:1     */
  --meta: var(--muted);          /* aliased                                  */
  --border-soft: #dfd6ba;        /* derived — hairline inside a panel        */

  /* ── A2 · accent states ──────────────────────────────────────────────── */
  --accent-on: #ffffff;          /* white on rust = 7.78:1                   */
  --accent-hover: color-mix(in oklab, var(--accent), black 16%);
  --accent-active: color-mix(in oklab, var(--accent), black 28%);

  /* ── A2 · semantic status ─────────────────────────────────────────────
     A second channel only. The palette is entirely warm, so `danger` lands on
     the same rust as `--accent` here; availability is told apart by SHAPE
     first (see .tk-dot) and colour second. Never encode a state in hue alone
     in this brand. */
  --success: #687532;            /* Moss   — seats open                      */
  --warn: #cb6c21;               /* derived orange — filling up              */
  --danger: #8c3900;             /* Rust   — last places / sold out          */

  /* ── A2 · type ───────────────────────────────────────────────────────────
     THE DATA VOICE IS NOT MONOSPACE HERE.
     The kit supplies two faces and no monospace, and it does not need one:
     Afacad Flux ships `tnum` and `zero`, so the same face sets running text
     and every figure that has to line up in a column. `--font-data` is what
     the logbook, the ticker, captions and labels use.

     `--font-mono` still exists because the token schema requires it, and it
     is a real system stack rather than an alias of the data face — if actual
     code ever appears on this site it needs monospace metrics, and quietly
     pointing the slot at a proportional face would be a lie the schema check
     cannot see. It costs nothing: no webfont is loaded for it. */
  --font-data: var(--font-body);
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* ── A2 · space (4px base) ───────────────────────────────────────────── */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;

  /* ── A2 · radius — the lockup is an oval; everything else is cut ─────── */
  --radius-sm: 2px;
  --radius-md: 3px;
  --radius-lg: 4px;
  --radius-pill: 9999px;         /* the availability dot only                */

  /* ── A2 · elevation — rules, not shadows ─────────────────────────────── */
  --elev-flat: none;
  --elev-ring: 0 0 0 1px var(--border);
  --elev-raised: 0 0 0 1px var(--border);
  --focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 62%);

  /* ── A2 · motion ─────────────────────────────────────────────────────── */
  --motion-fast: 160ms;
  --motion-base: 240ms;
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);

  /* ── A1-structure · type scale ───────────────────────────────────────────
     Afacad Flux runs small for its point size, so the body floor is 18px and
     the lede sits at 21px. Playfair is high-contrast and wants air: every
     display size below is paired with --leading-tight, never the body
     leading. */
  --text-xs: 12px;               /* letterspaced labels, captions            */
  --text-sm: 14px;               /* data rows                                */
  --text-base: 18px;             /* body — the floor, never go under         */
  --text-lg: 21px;               /* lede                                     */
  --text-xl: 25px;               /* card titles                              */
  --text-2xl: 32px;              /* sub-headings                             */
  --text-3xl: 44px;              /* section headings                         */
  --text-4xl: 72px;              /* page titles                              */
  --leading-body: 1.6;
  --leading-tight: 1.08;
  --tracking-display: -0.008em;  /* Playfair is already tight; barely pull   */

  /* ── A1-structure · rhythm and container ─────────────────────────────── */
  --section-y-desktop: 96px;
  --section-y-tablet: 72px;
  --section-y-phone: 56px;
  --container-max: 1120px;
  --container-gutter-desktop: 40px;
  --container-gutter-tablet: 32px;
  --container-gutter-phone: 24px;

  /* ── C-extension · the named palette ─────────────────────────────────────
     The eight, verbatim from the cheat sheet. These are the ONLY colours that
     may be named in the brand's voice; anything else is a derived role above. */
  --awe-black: #000000;
  --awe-forest: #2c3c1a;
  --awe-moss: #687532;
  --awe-rust: #8c3900;
  --awe-orange: #dd7d36;
  --awe-sand: #ffdd9d;
  --awe-cream: #f4e7ce;
  --awe-white: #ffffff;

  /* ── C-extension · the voice ─────────────────────────────────────────────
     The lockup sets its tagline in wide letterspaced caps. That spacing is the
     brand's own device, so every label, eyebrow and data row inherits it. */
  --awe-tracking-data: 0.055em;   /* uppercase data — never set caps tighter */
  --awe-tracking-eyebrow: 0.2em;  /* section eyebrows, matching the lockup   */
  --awe-tracking-tagline: 0.34em; /* the tagline itself, as drawn in the kit */
  --awe-measure: 66ch;            /* reading measure cap                     */
  --awe-rule: 1px solid var(--border);
  --awe-caption-gap: 11px;        /* image → caption rule                    */
  --awe-figures: "tnum" 1, "zero" 1;  /* every number that sits in a column  */

  /* ── C-extension · the pattern ───────────────────────────────────────────
     Page 3 of the cheat sheet is a tiling motif — the marks at low contrast
     on forest. It is a real brand material, not decoration, so it gets a
     token rather than being inlined at each use. Bound in base.css. */
  --awe-pattern-ink: color-mix(in oklab, var(--awe-black), transparent 78%);
  --awe-pattern-size: 168px;
}

/* ── Forest ─────────────────────────────────────────────────────────────────
   Header, footer, the seat ticker. The ground the vertical lockup is drawn
   on in the kit, so the sand lockup is the correct mark here.

   `body.woocommerce-order-received` joins this list because the site's own
   custom CSS paints that page's <main> forest green — deliberately — but was
   never given the matching ink, so the order confirmation read dark green on
   dark green and was effectively blank. Found 2026-09-03. Taking the tokens
   from here rather than restating them means the page inherits the same
   contrast ratios that were checked for every other forest surface.        */
[data-surface="forest"],
body.woocommerce-order-received {
  --bg: #2c3c1a;
  --surface: #1f2c12;
  --surface-warm: #687532;
  --fg: #f4e7ce;
  --fg-2: #bbb79b;
  --muted: #a0a283;
  --border: #55603f;
  --border-soft: #414c2c;
  --accent: #ffdd9d;
  --accent-on: #000000;
  --success: #798644;
  --warn: #dd7d36;
  --danger: #c0693c;
  --awe-pattern-ink: color-mix(in oklab, var(--awe-black), transparent 88%);

  background: var(--bg);
  color: var(--fg);
}

/* ── Night ──────────────────────────────────────────────────────────────────
   Galleries and photograph-forward sections. The only ground on which orange
   is legible as text (7.06:1), which is why the monogram sits on black in the
   kit.                                                                      */
[data-surface="night"] {
  --bg: #000000;
  --surface: #16200b;
  --surface-warm: #2c3c1a;
  --fg: #f4e7ce;
  --fg-2: #a39a89;
  --muted: #7b7466;
  --border: #3a3529;
  --border-soft: #241f18;
  --accent: #dd7d36;
  --accent-on: #000000;
  --success: #687532;
  --warn: #dd7d36;
  --danger: #94410d;

  background: var(--bg);
  color: var(--fg);
}

/* ── Sand ───────────────────────────────────────────────────────────────────
   Call-to-action bands. The brightest ground the kit gives, so it is used
   sparingly — once per page, where the page asks for something.             */
[data-surface="sand"] {
  --bg: #ffdd9d;
  --surface: #f4e7ce;
  --surface-warm: #f4e7ce;
  --fg: #2c3c1a;
  --fg-2: #5e6339;
  --muted: #6a6638;
  --border: #d4b978;
  --border-soft: #e6cd8c;
  --accent: #8c3900;
  --accent-on: #ffffff;
  --success: #687532;
  --warn: #c5671a;
  --danger: #8c3900;

  background: var(--bg);
  color: var(--fg);
}

/* ── Paper ──────────────────────────────────────────────────────────────────
   White ground, black ink. The product ground: the live store's own single-
   product template sets the title, the story and the facts strip on white
   rather than the page's cream, and this reproduces it. At 21:1 it is the
   highest-contrast ground the system has, which is what a page somebody
   reads carefully before spending money actually wants. Use it for product
   headers — not as a general alternative to cream, which stays the page.  */
[data-surface="paper"] {
  --bg: #ffffff;
  --surface: #ffffff;
  --surface-warm: #f4e7ce;
  --fg: #000000;                 /* Black  — ink, 21:1                       */
  --fg-2: #1f1f1f;               /* derived — body one step down, 17.4:1     */
  --muted: #6b6b6b;              /* derived — labels, 5.28:1                 */
  --border: #dcdcdc;             /* derived — the facts-strip rule           */
  --border-soft: #ededed;        /* derived — hairline inside a panel        */
  --accent: #8c3900;             /* Rust — 7.78:1, the only accent that      */
  --accent-on: #ffffff;          /*        carries text weight on white      */
  --success: #4a5a24;
  --warn: #8c4a00;
  --danger: #8c3900;

  background: var(--bg);
  color: var(--fg);
}

/* Reduced motion is honoured globally — the brand barely moves anyway. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --motion-fast: 0ms;
    --motion-base: 0ms;
  }
}

/* ── system/base.css ── */
/* ============================================================================
   ATLAS WILD EXPERIENCES — base layer
   ----------------------------------------------------------------------------
   Reset + the element defaults that carry the brand. Load order is fixed:

     1. system/fonts.css       @font-face
     2. system/variables.css   the token contract
     3. system/base.css        this file
     4. your page CSS          components, using var(--*) only

   Rule that keeps the system real: no raw colour literal appears below this
   line. Every colour is a token.

   THE TWO-VOICE RULE
   The kit names three type roles and hands you two files. So the site runs on
   two voices and the split is serif-versus-sans, not serif-versus-mono:

     PLAYFAIR NARRATES   headings, pull-quotes, the numbers in a display
                         figure. Anything being said.
     AFACAD RECORDS      running text, and every fact set as data — dates,
                         parks, seat counts, prices, captions. Anything being
                         logged. Uppercase, letterspaced to match the tagline
                         in the lockup, with tabular figures on.

   The wordmark is never live text. See fonts.css.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Told directly, 2026-08-30: every piece of text on the site, headings and
     running paragraphs alike, centered — no exceptions, including long-form
     body copy. A single inherited default here covers every block kind
     without touching each one; component CSS still wins where a specific
     rule needs to stay put (e.g. .calc-lead-card input, which stays
     left-aligned on purpose — typed data-entry text, not display copy). */
  text-align: center;
}

/* ── Display type ─────────────────────────────────────────────────────────
   Playfair is high-contrast: its hairlines thin out badly under about 25px,
   so it is never used for body, labels or captions. Weight stays at 400–500
   for display sizes — Playfair's bold is a much heavier colour on the page
   than the lockup's own letterforms and looks like a different brand. */
h1, h2, h3, .display {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
  margin: 0;
}

h1 { font-size: clamp(40px, 7vw, var(--text-4xl)); }
h2 { font-size: clamp(28px, 4vw, var(--text-3xl)); }
h3 { font-size: var(--text-xl); line-height: 1.14; }

/* Small headings drop out of the display voice entirely — at this size
   Playfair is unreadable, so they become labels in the data voice. */
h4, h5, h6 {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: var(--awe-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}

p {
  margin: 0 0 var(--space-5);
  max-width: var(--awe-measure);
}
p:last-child { margin-bottom: 0; }

/* ── Links ───────────────────────────────────────────────────────────────
   Underlined by a border rather than text-decoration so the rule sits clear
   of the descenders.

   On light grounds the accent is rust and carries the link itself. On forest
   and night the text stays at full contrast and the ORANGE does the signalling
   from the underline — orange is a fill in this brand, never a text colour on
   anything but black. See the note at the top of variables.css. */
a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in oklab, var(--accent), transparent 58%);
  transition: border-color var(--motion-fast) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard);
}
a:hover { border-bottom-color: var(--accent); }

[data-surface="forest"] :where(p, li, dd) a {
  color: var(--fg);
  border-bottom-color: var(--awe-orange);
  border-bottom-width: 2px;
}
[data-surface="forest"] :where(p, li, dd) a:hover { color: var(--accent); }

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

/* ── Data voice ──────────────────────────────────────────────────────────
   Anything factual. `tnum` aligns figures down a column and `zero` slashes
   the zero, so a seat count of 0 cannot be misread as O — both features are
   in Afacad Flux, which is why this brand needs no monospace. */
.data, code, kbd, samp, time, .price, .seats {
  font-family: var(--font-data);
  font-feature-settings: var(--awe-figures);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  letter-spacing: var(--awe-tracking-data);
}

.eyebrow {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: var(--awe-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--space-5);
}

.lede {
  font-size: var(--text-lg);
  line-height: 1.5;
  font-weight: 400;
  color: var(--fg-2);
  max-width: 60ch;
}

/* ── The tagline ─────────────────────────────────────────────────────────
   BE CURIOUS ✦ BE CONNECTED ✦ BE IN AWE, set the way the lockup draws it.

   Neither webfont contains U+2726, so the separator is a CSS shape rather
   than a character — ingest_kit.py checks this on every run and says so. It
   is marked aria-hidden in the markup; the tagline reads correctly without
   it. */
.tagline {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--awe-tracking-tagline);
  text-transform: uppercase;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
}
.tagline i {
  display: block;
  width: 0.7em;
  height: 0.7em;
  flex: none;
  background: var(--accent);
  clip-path: polygon(50% 0%, 60% 40%, 100% 50%, 60% 60%,
                     50% 100%, 40% 60%, 0% 50%, 40% 40%);
}

/* ── Media ───────────────────────────────────────────────────────────────
   The photograph is the loudest thing on the page. Nothing is cropped round,
   nothing gets a shadow. */
img, video { max-width: 100%; display: block; height: auto; }
figure { margin: 0; }

figcaption {
  font-family: var(--font-data);
  font-feature-settings: var(--awe-figures);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--muted);
  /* No rule between a photograph and its caption. It drew a tan hairline the
     full width of every captioned image on the site, and at the sizes the
     photographs run it reads as a border on the picture rather than as a
     separator under it — the client called it exactly that (2026-09-01).
     Removed here rather than per block, because it was never wanted on any of
     them. The caption is already set apart by its size, its letter-spacing,
     its case and its colour; the gap below does the rest.
     `--awe-caption-gap` was the image→rule distance and is now the image→text
     distance, so it is set once rather than doubled either side of a line
     that is no longer there. */
  margin-top: var(--awe-caption-gap);
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  align-items: baseline;
}
figcaption b { color: var(--fg-2); font-weight: 600; }
figcaption .sep { color: var(--border); }

/* ── The pattern ─────────────────────────────────────────────────────────
   Page 3 of the cheat sheet: the insignia marks tiled at low contrast. It is
   a brand material, so it is available as a class rather than being redrawn
   per use. Applied to a ground, never behind body text.

   The tile is built from the insignia SVGs at build time — see
   copy_assets() in build.py, which writes assets/pattern.svg. */
.patterned {
  background-image: url("/assets/pattern.svg");
  background-size: var(--awe-pattern-size);
  background-repeat: repeat;
}

/* ── Layout primitives ───────────────────────────────────────────────────── */
.container {
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-gutter-phone);
}

.section { padding-block: var(--section-y-phone); }

hr, .rule {
  border: 0;
  height: 1px;
  background: var(--border);
  margin: 0;
}

@media (min-width: 700px) {
  .container { padding-inline: var(--container-gutter-tablet); }
  .section { padding-block: var(--section-y-tablet); }
}

@media (min-width: 1000px) {
  .container { padding-inline: var(--container-gutter-desktop); }
  .section { padding-block: var(--section-y-desktop); }
}

/* ── site.css ── */
/* Atlas Wild Experiences — LAYOUT ONLY.
 *
 * Every colour, size, font and radius comes from the design system at
 * ../design-system/system/variables.css, which the build copies in ahead of
 * this file. There is deliberately not one raw hex below — that rule is what
 * lets the editor's Tokens panel restyle all pages by rewriting one :root, and
 * what stops a hardcoded colour surviving a rebrand.
 *
 * The component tokens this file is allowed to add live in the :root block
 * below and carry the --awe- extension prefix, per the system's token contract.
 */
:root {
  --awe-head-h: 72px;
  --awe-card-min: 260px;
  --awe-print-w: 880px;
}

/* Sitewide: every page centre-aligns its text (told directly, 2026-08-30).
   Loaded after fonts/variables/base but before blocks.css and home.css, so
   this is the default every component inherits unless it opts back out. */
* { text-align: center; }

/* Sections are full-bleed colour fields butted against each other — the way
   the brand cheat sheet itself is composed. So a band paints its own ground
   edge to edge and only the text column is constrained. Two consecutive bands
   on the SAME ground would show a seam of doubled padding, so they collapse. */
.band + .band[data-surface="forest"],
.band + .band[data-surface="night"],
.band + .band[data-surface="sand"] { border-top: 0; }

/* ── shell ─────────────────────────────────────────────────────────────── */
.wrap {
  max-width: var(--container-max);
  margin: 0 auto;
  padding-inline: var(--container-gutter-phone);
}
.band { padding-block: var(--section-y-phone); }

/* A band with an `id` is the target of an in-page jump — the FAQ hub's
   contents list is the first of them. The header is sticky, so without a
   scroll margin the heading you jumped to arrives underneath it and the
   reader lands mid-answer. The phone header was two rows (136px clearance)
   until the menu button replaced its scrolling nav row on 2026-09-15; it is
   one row now. */
/* The page itself now keeps clear of the sticky header (pre-launch audit,
   2026-09-16): Shift+Tab left a focused field entirely underneath it, because
   only these jump targets had a margin. The header is 72px at every width, so
   the padding is set once and the jump targets keep only what they added on
   top of it — the landing positions are unchanged (96px phone, 88px wider). */
html { scroll-padding-top: calc(var(--awe-head-h) + var(--space-4)); }
.band[id] { scroll-margin-top: var(--space-2); }

@media (min-width: 768px) {
  .wrap { padding-inline: var(--container-gutter-tablet); }
  .band { padding-block: var(--section-y-tablet); }
  .band[id] { scroll-margin-top: 0; }
}
@media (min-width: 1040px) {
  .wrap { padding-inline: var(--container-gutter-desktop); }
  .band { padding-block: var(--section-y-desktop); }
}

/* A plain header can ask to be centred with an underline on the h1 — the
   print pages use this to match the live product page's own look (a
   centred title, not the site's usual left-aligned band). Same visual
   idea as .head-block--center, applied to the page's own h1/lede instead
   of a section heading. */
.band.hero.hero-center { text-align: center; }
.band.hero.hero-center h1 {
  display: inline-block;
  position: relative;
  padding-bottom: var(--space-4);
}
.band.hero.hero-center h1::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: 3.5em;
  height: 1px;
  background: currentColor;
}
.band.hero.hero-center .lede {
  margin-inline: auto;
}

/* ── header ────────────────────────────────────────────────────────────── */
/* Same reasoning as `.crumbs`: no hairline under the header either. It is
   the other half of the pair of lines that was dividing every page up. The
   header separates itself from the page by its own ground and by the shadow
   the open nav casts. */
.head {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg);
}
/* The wordmark is artwork, not type — the kit ships no logo font. The lockup
   is 1200x566 with generous internal air, so it is sized by height and the
   header row is what holds the rhythm. */
.logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  border: 0;
  flex: none;
}
/* The `notag` crop, because the tagline baked into the full lockup renders
   about two pixels tall at this size. It is set once, at readable size, in the
   footer instead. */
.logo .lockup-horizontal-notag {
  height: 46px;
  width: auto;
}
@media (min-width: 768px) {
  .logo .lockup-horizontal-notag { height: 54px; }
}
/* On a phone the five nav items wrapped onto three rows and the sticky header
   took 390px — half the viewport, above the fold, on every page. verify.py
   does not catch this: nothing overflows, the header is simply enormous.

   Below 768px the header is two rows: mark and Enquire on the first, the nav
   on the second as one line that scrolls sideways inside itself. The grid
   areas are explicit because the DOM order is logo → nav → button, and left
   to flow the button lands on a third row and stretches to full width. */
.head .wrap {
  min-height: var(--awe-head-h);
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "logo cta" "nav nav";
  align-items: center;
  gap: var(--space-2) var(--space-5);
  padding-block: var(--space-3);
}
.head .logo { grid-area: logo; }
/* The cta area now holds three things, not one: account, cart and Enquire.
   The rule used to be `.head > .wrap > .btn`, which stopped matching the
   moment the button moved inside .headtools — so it is replaced rather than
   left as a dead selector fighting nothing. */
.head > .wrap > .headtools {
  grid-area: cta;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
/* Icon links sit on the header's night ground beside the Enquire button. The
   label is hidden on a phone, where the row is already tight, but stays in
   the accessibility tree via aria-label on the anchor. */
.headtool {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
  border: 0;
  color: inherit;
  line-height: 1;
  flex: none;
}
.headtool:hover { color: var(--accent); }
.headtool svg { display: block; }
.headtool-t {
  font-size: 0.875rem;
  white-space: nowrap;
}
@media (max-width: 900px) {
  .headtool-t { position: absolute; width: 1px; height: 1px; overflow: hidden;
                clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
  .head > .wrap > .headtools { gap: var(--space-3); }
}
/* Text for screen readers and crawlers, invisible on screen. The house
   pattern, which `.headtool-t` and `.menu-btn-t` already use inline above and
   below; this is the reusable version. Not `display: none` — that removes it
   from the accessibility tree too, which is the opposite of the point. */
.u-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The badge is positioned against the icon, so the wrapper is the only thing
   that needs to be a positioning context. */
.cartwrap { position: relative; display: inline-flex; }
.cart-count {
  position: absolute;
  top: -6px;
  inset-inline-start: 11px;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--accent);
  color: var(--accent-on);
  font-size: 11px;
  font-weight: 600;
  line-height: 17px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.head nav {
  grid-area: nav;
  display: flex;
  gap: var(--space-6);
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-bottom: 2px;
}
.head nav::-webkit-scrollbar { display: none; }
.head nav a { white-space: nowrap; }

@media (min-width: 768px) {
  .head .wrap {
    grid-template-columns: auto 1fr auto;
    grid-template-areas: "logo nav cta";
    gap: var(--space-6);
    padding-block: 0;
  }
  .head nav { justify-content: center; overflow: visible; gap: var(--space-8); }
}
.head nav a {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  letter-spacing: var(--awe-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--fg-2);
  text-decoration: none;
  /* Navigation is not prose: the base link rule's underline turns a five-item
     nav into five ruled boxes. The current page gets the only rule here. */
  border-bottom: 2px solid transparent;
  padding-bottom: 2px;
  transition: color var(--motion-fast) var(--ease-standard),
              border-color var(--motion-fast) var(--ease-standard);
}
.head nav a:hover { color: var(--fg); border-bottom-color: var(--border); }
.head nav a[aria-current="page"] {
  color: var(--fg);
  border-bottom-color: var(--accent);
}
.head nav a:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ── header dropdown menus ───────────────────────────────────────────────────
   Modelled on the live site: each nav group with children owns a full-width
   panel that drops out of the black header — titled columns, and a feature
   image for Experiences.

   The closed state is the HTML `hidden` attribute, not a CSS hover trick. A
   panel ships hidden and is opened only by nav.js (hover, click or keyboard),
   so it reads as CLOSED in every context — a stale stylesheet, the editor's
   iframe, or no JavaScript at all — and can never render inline the way a
   hover-only `visibility` toggle can when its media query does not match.
   `.head .navmenu[hidden]` outranks the open rule on specificity, so a panel
   the script has not opened stays display:none. Below 1040px the full-width
   panel would overflow a tablet, so the script does not open it there; the top
   link goes to the hub page, which lists the same children. */
.head .navitem { display: flex; align-items: center; }
.head .navmenu { display: none; }

/* ── the menu button and panel, below 1040px (2026-09-15) ────────────────────
   Replaces the sideways-scrolling row of top-level labels, which hid FAQ off
   the edge of a phone and reached none of the sub-pages. The nav stays one
   element: below the breakpoint it is a panel that drops out of the header
   when the button opens it, with every section's links listed under its
   label; at desktop width the button disappears and the nav is the row it
   always was. The panel is left-aligned on purpose — a list of links scanned
   top to bottom is the one place the site's centring would slow a reader. */
.menu-btn { display: none; }
@media (max-width: 1039.98px) {
  .head .wrap { grid-template-areas: "logo cta"; grid-template-columns: 1fr auto; }
  .menu-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 44px;
    padding: 0 var(--space-2);
    margin-right: calc(-1 * var(--space-2));
    background: none;
    border: 0;
    color: var(--fg);
    font-family: var(--font-data);
    font-size: var(--text-sm);
    letter-spacing: var(--awe-tracking-eyebrow);
    text-transform: uppercase;
    cursor: pointer;
  }
  .menu-btn:focus-visible { outline: 2px solid var(--awe-cream); outline-offset: 2px; }
  .head nav { display: none; }
  .head.menu-open nav {
    display: block;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    max-height: calc(100dvh - 72px);
    overflow-y: auto;
    padding: var(--space-2) var(--container-gutter-phone) var(--space-8);
    background: var(--awe-black);
    border-top: 1px solid color-mix(in oklab, var(--awe-sand), transparent 82%);
    box-shadow: 0 24px 44px color-mix(in oklab, var(--awe-black), transparent 52%);
    text-align: left;
    z-index: 30;
  }
  .head.menu-open nav * { text-align: left; }
  .head.menu-open .navitem {
    display: block;
    padding-block: var(--space-3);
    border-bottom: 1px solid color-mix(in oklab, var(--awe-sand), transparent 86%);
  }
  .head.menu-open nav a {
    display: block;
    padding-block: var(--space-2);
    font-size: var(--text-base);
    border-bottom: 0;
    white-space: normal;
  }
  .head.menu-open .navmenu,
  .head.menu-open .navmenu[hidden] { display: block; }
  .head.menu-open .megawrap { display: grid; gap: var(--space-2); padding-left: var(--space-4); }
  .head.menu-open .megacol-h {
    margin: var(--space-2) 0 0;
    font-family: var(--font-display);
    font-size: var(--text-base);
    color: var(--awe-sand);
  }
  .head.menu-open .megacol a {
    text-transform: none;
    letter-spacing: normal;
    color: var(--awe-cream);
  }
  .head.menu-open .megafeat { display: none; }
}
@media (max-width: 420px) {
  .menu-btn-t { position: absolute; width: 1px; height: 1px; overflow: hidden;
                clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
}

@keyframes navdrop {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}

@media (min-width: 1040px) {
  .head .navitem.has-menu > a { display: inline-flex; align-items: center; }
  .head .navitem.has-menu > a::after {
    content: "";
    margin-left: 7px;
    border-left: 3px solid transparent;
    border-right: 3px solid transparent;
    border-top: 4px solid currentColor;
    opacity: 0.55;
    transition: transform var(--motion-fast) var(--ease-standard),
                opacity var(--motion-fast) var(--ease-standard);
  }
  .head .navitem.open > a::after { transform: rotate(180deg); opacity: 0.9; }

  .head .navmenu {
    display: block;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--awe-black);
    border-top: 1px solid
                color-mix(in oklab, var(--awe-sand), transparent 82%);
    box-shadow: 0 24px 44px
                color-mix(in oklab, var(--awe-black), transparent 52%);
    animation: navdrop var(--motion-fast) var(--ease-standard);
    z-index: 30;
  }
  .head .navmenu[hidden] { display: none; }

  /* The panel's contents sit on the page's centre line (told directly,
     2026-09-02), which is also what the sitewide centring rule at the top of
     this file asks for. It matters most in Experiences: with the feature
     picture gone, three short columns pinned left would leave two-thirds of
     a full-width black panel empty to their right. */
  .megawrap {
    max-width: var(--container-max);
    margin: 0 auto;
    padding: var(--space-8) var(--container-gutter-desktop);
    display: flex;
    gap: var(--space-12);
    align-items: flex-start;
    justify-content: center;
  }
  /* Columns never squeeze: their links are `white-space: nowrap`, so a
     shrunk column does not reflow, it spills. Pinning them means the panel's
     slack comes out of the picture instead — see .megafeat. */
  /* `align-items`, not `text-align`: the links are `width: fit-content` in a
     flex column, so text-align has nothing to centre them inside. */
  .megacol { display: flex; flex-direction: column; gap: var(--space-1);
    flex: none; align-items: center; }
  .megacol-h {
    font-family: var(--font-display);
    font-size: var(--text-base);
    color: var(--awe-sand);
    margin: 0 0 var(--space-3);
  }
  .head .megacol a {
    text-transform: none;
    letter-spacing: normal;
    font-family: var(--font-data);
    font-size: var(--text-sm);
    /* Menu links are primary, not secondary ink: --fg-2 on black reads as a
       disabled item. Cream is the light ink the kit draws on any dark ground. */
    color: var(--awe-cream);
    padding: var(--space-1) 0;
    border-bottom: 0;
    white-space: nowrap;
    width: -moz-fit-content;
    width: fit-content;
  }
  .head .megacol a:hover,
  .head .megacol a:focus-visible { color: var(--awe-orange); }
  /* The picture is the one part of this panel that can lose width without
     losing meaning — it is `object-fit: cover`, so a narrower box is a
     tighter crop rather than a broken layout. It used to be `flex: none` at
     300px, which was fine while Experiences had three columns: at the 1040px
     breakpoint that panel came to 898px in 960px of container. A fourth
     column ("Individual parks", added 2026-09-01) takes it to 1041px, and
     something has to give. This is what gives. Above ~1200px there is room
     for the full 300 and nothing looks any different. */
  .megafeat { margin-left: auto; flex: 0 1 300px; min-width: 190px; }
  .megafeat img {
    width: 100%;
    height: 180px;
    object-fit: cover;
    border-radius: var(--radius-lg);
    display: block;
  }
  .megafeat p {
    font-family: var(--font-display);
    font-style: italic;
    color: var(--awe-cream);
    font-size: var(--text-base);
    line-height: 1.35;
    margin: var(--space-4) 0 0;
  }
}

/* ── hero and headings ─────────────────────────────────────────────────── */
.hero h1 {
  font-size: clamp(var(--text-3xl), 7vw, var(--text-4xl));
  max-width: 16ch;
  margin-block: 0 var(--space-6);
  /* text-align:center alone only centres the words inside the box; a
     max-width box with no auto margins still hugs the left edge of .wrap.
     Centring every piece of text (told directly, 2026-08-30) means the box
     itself has to sit in the middle too. */
  margin-inline: auto;
}
.lede {
  font-size: var(--text-lg);
  color: var(--fg-2);
  max-width: var(--awe-measure);
  margin-block: 0;
  margin-inline: auto;
}
.head-block {
  margin-bottom: var(--space-8);
  text-align: center;
}
.head-block .eyebrow,
.head-block .standfirst { margin-inline: auto; }
/* Centred tier header — Prints page series headings (Collector Series,
   Field Diaries, Postcards): a short rule under the h2 stands in for the
   underline Lala's own store already uses on these same three headings. */
.head-block--center {
  text-align: center;
}
.head-block--center .eyebrow,
.head-block--center .standfirst {
  margin-inline: auto;
}
.head-block--center h2 {
  display: inline-block;
  position: relative;
  padding-bottom: var(--space-4);
}
.head-block--center h2::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: 3.5em;
  height: 1px;
  background: currentColor;
}
.eyebrow {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--space-4);
}

/* ── actions ───────────────────────────────────────────────────────────── */
.acts {
  display: flex;
  justify-content: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-top: var(--space-8);
}
.btn {
  display: inline-block;
  padding: var(--space-4) var(--space-6);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-on);
  font-family: var(--font-data);
  font-size: var(--text-sm);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid var(--accent);
  transition: background var(--motion-fast) var(--ease-standard);
}
.btn:hover { background: var(--accent-hover); }
/* --accent-hover is a color-mix of --accent, and a custom property resolves
   where it is declared: on :root, from the cream page's brown. Every surface
   redefines --accent but inherited that brown hover, so an orange or gold
   button on a dark band turned brown with black ink under the pointer
   (found 2026-09-15). Re-declaring it here recomputes it per surface. */
[data-surface] { --accent-hover: color-mix(in oklab, var(--accent), black 16%); }

/* The keyboard focus ring (launch audit, 2026-09-15). The design-system token
   was a 3px ring of the accent at 38% strength, which all but vanished on the
   black header and on orange buttons. Now: a hairline of the surface's own
   ground, then a solid ring in its ink — cream on dark, black on light — so it
   reads on every ground and around every button colour. Declared on :root AND
   re-declared per surface, for the same reason as --accent-hover above: a
   custom property resolves its var()s where it is declared, so a :root-only
   value would carry the page ground's colours into a night band. */
:root, [data-surface] {
  --focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--fg);
}

.btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* The accent is budgeted at two per screen. A second CTA is a ruled outline. */
.btn-quiet {
  background: transparent;
  color: var(--fg);
  border-color: var(--border);
}
.btn-quiet:hover { background: var(--surface); }

/* ── blocks ────────────────────────────────────────────────────────────── */
.prose p {
  max-width: var(--awe-measure);
  margin-block: 0 var(--space-5);
  margin-inline: auto;
}
/* Print pages: the story reads centred, like the live product page's own
   story text, rather than the site's usual left-aligned column. */
.prose--center { text-align: center; }
.prose--center p { margin-inline: auto; }

/* The centred print photograph. Still not full-bleed — a print with its own
   black mat sits on the page's black ground rather than filling it — but the
   photograph is the thing being sold, so it was scaled up from the live
   product page's cramped 640px (client, 2026-09-02). It stays inside the
   1120px wrap, so the print never reaches the page edges. */
.print-frame { max-width: var(--awe-print-w); margin-inline: auto; }

/* The photographic colour-accuracy disclaimer every live product page
   carries. It CLOSES the page, after the shop buttons (client, 2026-09-02) —
   it is fine print a buyer should be able to find, not something to read
   before the photograph. So: caption size, italic, muted, held to the text
   measure and centred like the rest of the page.

   `text-align` is set on the paragraphs as well as the box. The sitewide
   `* { text-align: center }` is a universal selector with zero specificity,
   so any bare element rule that lands on a `p` outranks it and the note
   silently goes left — which is exactly what was reported. Naming the
   paragraph here settles it rather than relying on that default. */
.print-disclaimer {
  max-width: var(--awe-measure);
  margin-inline: auto;
  text-align: center;
  font-style: italic;
  color: var(--fg-2);
  font-size: var(--text-xs);
  line-height: 1.5;
}
.print-disclaimer p { text-align: center; margin-inline: auto; }

/* Bands are full-bleed colour fields with their own generous padding, which
   is right for a section but wrong for a footnote: a full band's worth of
   black above it would read as one more section rather than as the fine
   print closing the one before. Tuck it under the block it follows. */
.band:has(> .wrap > .print-disclaimer) { padding-block-start: 0; }
.cols {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(var(--awe-card-min), 1fr));
}
.card {
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-6);
  background: var(--surface);
}
.card h3 { font-size: var(--text-xl); margin-block: 0 var(--space-3); }
/* `margin-inline: auto` matters as much as the colour here. base.css gives
   every <p> the reading measure and NO auto margins, so in a card wider than
   the measure the paragraph is a box pinned to the left edge — the heading
   centres, the body does not, and the card looks broken. Invisible in the
   narrow grid cards, obvious in the wide solo-carousel card. Third time this
   trap has bitten on this site: see also `.block-act` and `.quote p`. */
.card p { color: var(--fg-2); margin-block: 0; margin-inline: auto; }
.card ul {
  list-style: none;
  padding: 0;
  margin: var(--space-5) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.card ul li {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--muted);
}
/* A card with a button at its foot (see chrome.py's `card_cta`). `.cols` is a
   grid, so sibling cards are already the same height however much copy each
   one carries — but a button that simply follows the list sits wherever that
   card's text happens to end, and three of them step down the row like a
   staircase. Flex plus `margin-top: auto` puts the card's whole remaining
   height above the button instead, so the row of buttons is level.

   Scoped to `.card--cta` rather than `.card`: every other card on the site is
   a plain block today and there is no reason to change how it lays out. */
.card--cta { display: flex; flex-direction: column; }
/* The foot is a wrapper, not the bare button, for the same reason
   `.icard-foot` is on the home page's destination cards: `margin-top: auto`
   pushes it to the bottom of a card that has slack, and its own `padding-top`
   guarantees the gap in a card that has none. A bare button could only do one
   of those — `margin-top` cannot be both `auto` and a length. */
.card--cta .card-foot { margin-top: auto; padding-top: var(--space-6); }
/* base.css gives every anchor a border-bottom; on a button that reads as a
   stray rule under the label. Same reset .icard-foot .btn carries. */
.card--cta .card-foot .btn { border-bottom-width: 1px; }

/* ── footer ────────────────────────────────────────────────────────────── */
footer {
  padding-block: var(--space-12);
}
/* The roundel and its line sit on the page's centre line (client,
   2026-09-01). Centring is done here rather than in the design system's
   `.tagline` rule, which is shared: this is the footer's arrangement, not a
   change to how the lockup's line is set everywhere. */
.foot-top {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  align-items: center;
  text-align: center;
  padding-bottom: var(--space-12);
  margin-bottom: var(--space-12);
  border-bottom: var(--awe-rule);
}
.foot-top .lockup-vertical { height: 168px; width: auto; }
/* `.tagline` is a flex row, so centring its text needs the axis, not
   `text-align` — which would leave the three phrases packed to the left
   inside a centred box. */
.foot-top .tagline { justify-content: center; }
footer .cols { gap: var(--space-8); }
/* The footer column labels are h2 so the document outline never skips a level
   (see chrome.footer). h2 arrives carrying the display block's family, weight,
   leading and text-wrap, so every one of those is pinned back here — this rule
   is the whole appearance of the label, not a set of tweaks over the default. */
footer .foot-h {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: var(--leading-body);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  text-wrap: wrap;
  color: var(--muted);
  margin-block: 0 var(--space-4);
}
/* Sitemap links, not prose links: the base rule's underline would draw a rule
   the full width of each column and turn the footer into a ruled ledger. */
footer .cols a {
  display: block;
  color: var(--fg-2);
  text-decoration: none;
  border: 0;
  margin-bottom: var(--space-2);
  transition: color var(--motion-fast) var(--ease-standard);
}
footer .cols a:hover { color: var(--accent); }
.legal {
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: var(--awe-rule);
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--muted);
  line-height: var(--leading-body);
  max-width: none;
}
/* Inline, and quiet. These run in a sentence with separators between them. */
.legal a {
  display: inline;
  color: var(--fg-2);
  border-bottom: 1px solid var(--border);
}
.legal a:hover { border-bottom-color: var(--accent); color: var(--accent); }

/* Wide content scrolls inside its own box; the page body never does.
   NOT `table` — every table chrome.py emits is already inside a .table-wrap
   that scrolls (13 of 13 in the build), and adding `display: block` here
   defeated the `width: 100%` in blocks.css: the frame stretched full width
   while the rows hugged the left, which reads as a broken table rather than a
   narrow one. The wrapper owns the scrolling; the table owns its width. */
pre { display: block; overflow-x: auto; max-width: 100%; }

/* ── blocks.css ── */
/* Atlas Wild Experiences — the block renderers. LAYOUT ONLY, same rule as site.css.
 *
 * site.css is the shell: wrap, bands, header, hero, footer. This file is the
 * blocks chrome.py can emit — one section per BLOCKS entry. Split because the
 * two change for different reasons: the shell changes when the site's furniture
 * changes, a block changes when a page needs a new shape.
 *
 * Not one raw colour below. Every value comes from the design system, and
 * build.py fails the build if that stops being true.
 */

/* ── skip link and breadcrumbs ──────────────────────────────────────────── */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: var(--accent-on);
  padding: var(--space-3) var(--space-5);
  font-family: var(--font-data);
  font-size: var(--text-sm);
  z-index: 20;
}
.skip:focus { left: 0; }

/* No rule under the breadcrumbs. It drew a full-width hairline across every
   page directly under the header, which read as the page being chopped into
   sections before the reader had seen anything (client, 2026-09-02, three
   times). The trail is already set apart by its size, letter-spacing and
   colour. The only section break the site keeps is the footer's. */
.crumbs {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  color: var(--muted);
  padding-block: var(--space-4);
}
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--accent); }
.crumbs .sep { margin-inline: var(--space-3); opacity: .5; }
.btn-sm { padding: var(--space-2) var(--space-4); font-size: var(--text-xs); }

/* ── hero with an image ─────────────────────────────────────────────────── */
.hero-media { margin-top: var(--space-8); }
.hero-media figure { margin: 0; }
.hero-media img { width: 100%; height: auto; display: block; }

@media (min-width: 1040px) {
  .hero-img {
    display: grid;
    grid-template-columns: 1.05fr 1fr;
    gap: var(--space-12);
    align-items: center;
  }
  /* The hero text keeps the page's left gutter but stops at the midline, so
     the photograph runs to the right edge without the copy re-centring. */
  .hero-img > .wrap { margin-right: 0; padding-right: 0; }
  .hero-media { margin-top: 0; padding-right: var(--container-gutter-desktop); }
}

/* ── split ──────────────────────────────────────────────────────────────── */
.split { display: grid; gap: var(--space-8); align-items: center; }
.split figure { margin: 0; }
.split img { width: 100%; height: auto; display: block; }
.split .btn { margin-top: var(--space-6); }

@media (min-width: 900px) {
  .split { grid-template-columns: 1fr 1fr; gap: var(--space-12); }
  .split.flip .split-media { order: 2; }
}

/* ── gallery ────────────────────────────────────────────────────────────── */
.gallery {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.gallery figure { margin: 0; }
.gallery img { width: 100%; height: auto; display: block; }
/* A print that is for sale links to the product page. The anchor is
   the grid cell, so the layout does not move; it only has to stop
   behaving like body text. `.gtile` is a block-level <a>, so the global
   link underline (base.css `a { border-bottom }`) draws along the bottom
   of the whole tile — under the caption, not under any run of text — and
   reads as a stray line under every purchasable print. */
.gtile { display: block; text-decoration: none; color: inherit;
  border-bottom: 0; }
.gtile img { transition: opacity .18s ease; }
.gtile:hover img { opacity: .9; }
.gtile:focus-visible { outline: 2px solid var(--accent);
  outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .gtile img { transition: none; }
}
/* `.gallery--framed` used to cancel the base figcaption's tan rule for the
   print-store galleries, whose photographs carry their own printed black mat
   and so stacked two borders. That rule is gone from base.css entirely as of
   2026-09-01, so there is nothing left to cancel. The class stays because
   `photo_frame` still sets it and a framed gallery may want its own
   treatment again. */

/* The store grids name the artwork under it, centred — client, 2026-09-16.
   `text-align` cannot do this: base.css makes every figcaption a flex row so
   that a composed "SPECIES / PLACE / MONTH" caption can wrap as whole parts
   rather than mid-phrase, and a flex container ignores text-align on its
   items. `justify-content` is the property that moves them, and the sitewide
   `* { text-align: center }` is no help here for the same reason.

   Scoped to `--framed`, which is only ever the three shop galleries
   (Collector Series, Field Diaries, Postcards). The park galleries keep their
   captions ranged left, where a four-part caption reads as a data line. */
.gallery--framed figcaption { justify-content: center; text-align: center; }

/* A gallery whose frames are not all the same shape. Every hand-built gallery
   here is uniform, so this never came up; the park galleries are assembled
   from whatever the client dropped into `library/dropbox/<park>/` and mix 3:2
   landscape with 4:5 portrait. In the grid above, a row is as tall as its
   tallest frame — so one portrait leaves a hand's width of empty ground under
   every landscape sitting beside it, four or five times down the page.
   Columns close that up: each frame keeps its own height and the next one
   starts directly beneath it.

   The cost is reading order — down each column rather than across. A set of
   photographs carries that fine; a set of linked cards would not, which is
   why this is asked for per section rather than made the default. */
.gallery--masonry {
  display: block;
  columns: 3 280px;
  column-gap: var(--space-6);
}
.gallery--masonry figure {
  break-inside: avoid;
  margin: 0 0 var(--space-6);
}

/* ── carousel ───────────────────────────────────────────────────────────────
   A manual, swipeable photo strip. The track IS the scroll container and the
   slides snap; the arrows and dots (added by chrome.py's CAROUSEL_JS) only
   drive that native scroll, so it works on touch with no script and never
   pushes the page sideways — it scrolls inside its own box. */
.carousel { position: relative; margin-top: var(--space-6); }
.ctrack {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
  padding-bottom: var(--space-1);
}
.ctrack::-webkit-scrollbar { display: none; }
.cslide {
  flex: 0 0 86%;
  scroll-snap-align: center;
  margin: 0;
}
@media (min-width: 768px) { .cslide { flex-basis: 60%; } }
@media (min-width: 1040px) { .cslide { flex-basis: 46%; } }
.cslide picture { display: block; }
/* Every slide is the same 3:2 box regardless of the frame's own crop, so a
   portrait and a landscape sit at one height instead of a ragged row. The
   photograph fills that box (cover) rather than letterboxing; subjects in this
   set sit central, so centre is the safe anchor. */
.cslide img {
  width: 100%;
  aspect-ratio: 3 / 2;
  height: auto;
  object-fit: cover;
  object-position: center;
  display: block;
  border-radius: var(--radius-md);
  background: var(--awe-black);
}
.cslide figcaption {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--fg-2);
  margin-top: var(--space-3);
}
/* The arrows overlay the photographs, so they cannot borrow the ground's
   colour — a black disc on the night ground is invisible, and any single fill
   fights some frames. A near-solid cream disc with a dark chevron and a soft
   shadow reads as a control on top of a bright tiger and on top of pure black
   alike. It is the one place the carousel earns a real, tappable affordance. */
.cbtn {
  position: absolute;
  top: calc(50% - var(--space-6));
  transform: translateY(-50%);
  z-index: 2;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid color-mix(in oklab, var(--awe-black), transparent 84%);
  background: color-mix(in oklab, var(--awe-cream), transparent 8%);
  color: var(--awe-black);
  font-size: var(--text-2xl);
  line-height: 1;
  display: grid;
  place-items: center;
  cursor: pointer;
  box-shadow: 0 3px 14px color-mix(in oklab, var(--awe-black), transparent 58%);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  transition: background var(--motion-fast) var(--ease-standard),
              transform var(--motion-fast) var(--ease-standard),
              opacity var(--motion-fast) var(--ease-standard);
}
.cbtn:hover {
  background: var(--awe-cream);
  transform: translateY(-50%) scale(1.06);
}
.cbtn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* At the first/last frame there is nowhere further to go, so the spent arrow
   fades out rather than sitting there dead. */
.cbtn[disabled] { opacity: 0; pointer-events: none; }
/* The strips advance on their own now, so the arrows stop being furniture and
   become something you reach for: hidden until the pointer is on the carousel
   (client, 2026-09-01), faded in by the opacity transition `.cbtn` already
   carries.

   Only where a pointer exists. A hover-less device — a phone, but also the
   editor's own webview and many touch laptops, which report `hover: none` —
   has no way to ask for them, and hiding them there is exactly what once made
   the arrows look missing. Those keep the arrows on screen; swipe and the
   dots are the manual control either way. The disabled arrow at the end of a
   strip stays hidden even on hover: there is still nowhere further to go. */
@media (hover: hover) {
  .carousel .cbtn { opacity: 0; }
  .carousel:hover .cbtn,
  .carousel:focus-within .cbtn { opacity: 1; }
  .carousel:hover .cbtn[disabled],
  .carousel:focus-within .cbtn[disabled] { opacity: 0; }
}
.cprev { left: var(--space-3); }
.cnext { right: var(--space-3); }
.cdots {
  display: flex;
  gap: var(--space-2);
  justify-content: center;
  margin-top: var(--space-5);
}
.cdot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  border: 1px solid color-mix(in oklab, var(--awe-sand), transparent 55%);
  background: transparent;
  transition: background var(--motion-fast) var(--ease-standard),
              border-color var(--motion-fast) var(--ease-standard);
}
.cdot[aria-current="true"] {
  background: var(--accent);
  border-color: var(--accent);
}
.cdot:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* Arrows show on every device. Touch also swipes, and a hover-less webview
   (the editor preview, many touch laptops) reports `hover: none` — hiding the
   arrows there is exactly what made them look missing. They are always
   available; on the smallest screens they shrink so they cover less of a
   slide. */
@media (max-width: 560px) {
  .cbtn { width: 40px; height: 40px; font-size: var(--text-xl); }
  .cprev { left: var(--space-2); }
  .cnext { right: var(--space-2); }
}

/* ── quotes ─────────────────────────────────────────────────────────────── */
.quotes {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  /* Each quote is as tall as its own words. The grid default stretches every
     card in a row to the tallest, so a two-line quote beside a six-line one
     sat in a bordered box half full of nothing — the same emptiness that made
     /reviews/ look unfinished (client, 2026-09-16). A row of cards with
     different bottoms is the honest shape of quotations of different
     lengths. */
  align-items: start;
}
.quote {
  margin: 0;
  padding: var(--space-6);
  border: var(--awe-rule);
  border-left: 2px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
/* `margin-inline: auto` is the working half of this, not `text-align`.
   base.css gives every <p> the reading measure and NO auto margins, so in a
   wide card the quotation is a narrow box pinned to the left edge and the
   sitewide `text-align: center` only centres text within that box — the
   review read as left-aligned against a centred name (client, 2026-09-02).
   Same trap as `.block-act`. In the narrow grid cards the measure never
   binds, so this changes nothing there. */
.quote p {
  margin: 0 auto;
  max-width: 60ch;
  color: var(--fg-2);
}
.quote cite {
  display: block;
  margin-top: var(--space-4);
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-style: normal;
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--muted);
}
/* The trip the reviewer actually took, under their name. It is a second line
   rather than a clause inside the name because `cite`'s text IS the Review
   schema's author — see blk_quotes. Dimmer than the name, and not uppercased,
   so a long itinerary title stays readable. */
.quote cite .quote-trip {
  display: block;
  margin-top: calc(var(--space-2) / 2);
  text-transform: none;
  letter-spacing: 0;
  /* --fg-2, not --muted at 75%: that measured 3.8:1 on forest, under the
     4.5:1 small text needs (launch audit, 2026-09-15). */
  color: var(--fg-2);
}

/* Reviews as a swipeable strip (blk_quotes' `carousel`). The slide keeps the
   `.quote` card exactly as the grid draws it — same rule, same accent edge,
   same ground — so this reads as the row of quotes it replaced, moving. The
   flex column centres a two-line review in the height the longest one sets,
   because `.ctrack` stretches every slide to match and a short quote pinned
   to the top of a tall card looks like a mistake. */
.carousel--reviews .cslide {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* The photo carousels sit their arrows above the caption line; a review card
   has no caption, so centre them on the card itself. */
.carousel--reviews .cbtn { top: 50%; }

/* ONE review at a time (/reviews/, `solo`), as a card on a dark ground
   (client, 2026-09-16: "put a black box or some kind of box around the text so
   that they look like cards that are going through a carousel").

   THE HEIGHT IS THE WHOLE TRICK. `.ctrack` is a flex row, so its default
   `align-items: stretch` made every slide as tall as the tallest — and these
   thirteen run from 215 to 952 characters, so the shortest review sat in 480px
   of visibly empty bordered box and read as unfinished ("half haphazard and
   half finished"). `align-items: center` below lets each card be exactly as
   tall as its own words and float in the middle of the band instead. The band
   still keeps the tallest review's height, so nothing jumps as it advances. */
.carousel--reviews.carousel--solo .ctrack { align-items: center; }
/* NOTE: `.quote` and `.cslide` are the SAME element — blk_quotes renders
   `<blockquote class="quote cslide">`. So nothing here may cap its width: a
   `max-width` on this selector overrides the track's `flex: 0 0 100%`, the
   slide stops filling the viewport and the next review peeks in beside it.
   The reading measure goes on the paragraph inside, never on the slide. */
.carousel--reviews.carousel--solo .quote {
  /* Black, not the stock `--surface`: on the forest band that token is a
     slightly darker green and the card read as a faint outline rather than an
     object — the same complaint, and the same fix, as the solo cards on
     /about/ (client, 2026-09-02, "make the cards stand out a little more").
     The accent edge the grid card carries on its left is dropped: centred text
     under a centred name has no left edge for it to mark. */
  background: var(--awe-black);
  border: 1px solid color-mix(in oklab, var(--awe-cream), transparent 84%);
  border-left: 1px solid color-mix(in oklab, var(--awe-cream), transparent 84%);
  border-radius: var(--radius-sm);
  /* --space-12/8, NOT --space-10: that token does not exist in this system
     (the scale is 1,2,3,4,5,6,8,12) and an undefined var voids the whole
     shorthand, so the card renders with no padding at all. See the same trap
     noted at `.band.trip-intro` below. */
  padding: var(--space-12) var(--space-8);
  box-shadow: 0 14px 40px color-mix(in oklab, var(--awe-black), transparent 62%);
}
@media (max-width: 599.98px) {
  .carousel--reviews.carousel--solo .quote { padding: var(--space-8) var(--space-5); }
}
.carousel--reviews.carousel--solo .quote p {
  font-size: var(--text-lg);
  line-height: var(--leading-body);
  color: var(--fg);
  max-width: 62ch;
  margin-inline: auto;
}
/* The opening quotation mark: the one piece of ornament on this band, and it
   earns its place. A black card of centred text on a green ground gives the
   eye nothing to start on, and a review is the one thing on this site where a
   quotation mark is literally what the content is. Generated content, so a
   screen reader never announces a stray double-quote before the review — the
   <blockquote> already says what this is. */
.carousel--reviews.carousel--solo .quote::before {
  content: "\201C";
  display: block;
  font-family: var(--font-display);
  font-size: 68px;
  line-height: 0.62;
  color: var(--accent);
  margin-bottom: var(--space-5);
}
/* The name lifts off the quotation: a short rule, then the accent on the name
   so the attribution reads as a signature rather than a caption. The rule is
   the same gesture the Prints page uses under its series headings. */
.carousel--reviews.carousel--solo .quote cite {
  margin-top: var(--space-6);
  color: var(--accent);
}
.carousel--reviews.carousel--solo .quote cite::before {
  content: "";
  display: block;
  width: 36px;
  height: 1px;
  margin: 0 auto var(--space-5);
  background: color-mix(in oklab, var(--accent), transparent 52%);
}
.carousel--reviews.carousel--solo .quote cite .quote-trip { color: var(--fg-2); }

/* ── steps — numbered because order is genuinely information here ───────── */
.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.step {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-5);
  padding-block: var(--space-6);
  border-top: var(--awe-rule);
}
.step:last-child { border-bottom: var(--awe-rule); }
.step-n {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--awe-tracking-data);
  color: var(--accent);
  padding-top: 5px;
}
/* An unnumbered sequence (blk_steps' `numbered: False`) has nothing to sit in
   the first column, so the row collapses to one. The rules between stages stay
   — they are what makes it read as a sequence once the count is gone. */
.steps--plain .step { grid-template-columns: 1fr; }
.step h3 { margin-block: 0 var(--space-3); font-size: var(--text-xl); }
.step p { margin: 0 auto; color: var(--fg-2); max-width: var(--awe-measure); }

/* ── table ──────────────────────────────────────────────────────────────── */
.table-wrap {
  overflow-x: auto;
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
}
table { border-collapse: collapse; width: 100%; min-width: 560px; }
/* Cells read from the left (client, 2026-09-16). They were centred, which is
   the house habit everywhere else, but a column of sentences of different
   lengths gives the eye no edge to run down — "Green, humid, everything
   overgrown" and "Steady" started in different places on every row. The
   header goes with them so each label sits over its own column. Stated here
   rather than inherited, because site.css centres every element directly
   (see `* { text-align: center }`) and nothing inherits past that. */
th, td {
  text-align: left;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-soft);
  font-variant-numeric: tabular-nums;
}
th {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--muted);
  /* No fill on the header row.
     `--surface` is WHITE on the cream ground, and the table body has no
     background of its own — so a filled header rendered as a detached white
     strip floating above cream rows, the only white in the section. It looked
     like a separate element rather than the top of the table.
     The design system's own tables (build_kit.py) rule the header off instead
     of filling it, which is also the house style: borders, not fields. Doing
     the same here fixes it on all four grounds at once, where a per-surface
     override would have to be written four times. */
  border-bottom: var(--awe-rule);
}
td { font-size: var(--text-sm); color: var(--fg-2); }
td:first-child { color: var(--fg); }
tbody tr:last-child td { border-bottom: none; }
/* The small print under a block — a table's footnote, a gallery's photo
   credit. `.table-note` kept its name because it is in the built pages
   already; `.block-note` is the same rule for everything else that needs it. */
.table-note, .block-note {
  margin-top: var(--space-4);
  margin-inline: auto;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--muted);
  max-width: var(--awe-measure);
}

/* ── faq ────────────────────────────────────────────────────────────────── */
.faqs { border-top: var(--awe-rule); max-width: 76ch; margin-inline: auto; }
.faq { border-bottom: var(--awe-rule); }
.faq summary {
  cursor: pointer;
  padding-block: var(--space-5);
  font-size: var(--text-lg);
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: var(--space-5);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+";
  font-family: var(--font-data);
  color: var(--accent);
  flex: none;
}
.faq[open] summary::after { content: "\2212"; }
.faq summary:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.faq p {
  margin-block: 0 var(--space-5);
  margin-inline: auto;
  color: var(--fg-2);
  max-width: var(--awe-measure);
}

/* ── callout — the thing a brochure would leave out ─────────────────────── */
.callout {
  border: var(--awe-rule);
  border-left: 2px solid var(--warn);
  border-radius: var(--radius-sm);
  padding: var(--space-6);
  background: var(--surface);
  max-width: 76ch;
  margin-inline: auto;
}
.callout h3 { margin-block: 0 var(--space-4); font-size: var(--text-xl); }
.callout p { margin-block: 0 var(--space-4); color: var(--fg-2); }
.callout p:last-child { margin-bottom: 0; }

/* ── facts ──────────────────────────────────────────────────────────────── */
.facts { margin: 0; border-top: var(--awe-rule); }
.fact {
  display: grid;
  gap: var(--space-2) var(--space-6);
  padding-block: var(--space-4);
  border-bottom: var(--awe-rule);
}
.fact dt {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--muted);
}
.fact dd { margin: 0; color: var(--fg); }
/* A label/value list is a table by another name, so it reads from the left
   like one (client, 2026-09-16). Centred, the GST number and the registered
   address each started somewhere different and the column had no edge.
   `.facts--swatch` — the print pages' size bars — is a deliberately centred
   variant that squares up at 560px, so it is held out of this. */
.facts:not(.facts--swatch) .fact dt,
.facts:not(.facts--swatch) .fact dd { text-align: left; }

@media (min-width: 720px) {
  .fact { grid-template-columns: 260px 1fr; align-items: baseline; }
}

/* Print pages: sizes read as bordered bars, not a stacked definition list.
   These are the pages whose products have no add-to-cart ids yet, so the
   bars are the same shape as `.buy-row` above but without the button —
   the two halves of the template have to look like one design. */
.facts--swatch {
  border-top: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: 520px;
  margin-inline: auto;
}
.facts--swatch .fact {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2) var(--space-5);
  border: 1px solid var(--border);      /* see .buy-row on --awe-rule */
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-5);
  text-align: center;
}
.facts--swatch .fact dt { margin: 0; }
.facts--swatch .fact dd {
  font-family: var(--font-display);
  font-size: var(--text-lg);
}
@media (min-width: 560px) {
  .facts--swatch .fact {
    flex-wrap: nowrap;
    justify-content: space-between;
    text-align: left;
  }
}

/* ── trip hero (named departures) ───────────────────────────────────────────
   The live store's own single-product layout: a full-bleed gallery ahead of
   the title, a plain icon-and-rule facts row (no card, no border — the live
   page doesn't box these either), then a dark booking band with its own
   bounded photo beside the price and buttons — all of which go somewhere
   real: the same enquiry path, the itinerary further down this page, and
   the WhatsApp number the whole site already gives out. */
.trip-bleed-band { padding: 0; }
.trip-bleed-figure { margin: 0; }
.trip-bleed-figure img {
  width: 100%;
  height: min(76vh, 760px);
  object-fit: cover;
  display: block;
  border-radius: 0;
}
.trip-bleed-figure figcaption { display: none; }
/* The top gallery runs edge to edge, like the live store's — a taller,
   single-slide box with the arrows inset from the viewport's own edges
   rather than a bounded card's. */
.carousel--bleed { margin-top: 0; }
.carousel--bleed .ctrack { gap: 0; }
.carousel--bleed .cslide {
  flex: 0 0 100%;
  scroll-snap-align: start;
  margin: 0;
}
.carousel--bleed .cslide img {
  aspect-ratio: unset;
  width: 100%;
  height: min(76vh, 760px);
  object-fit: cover;
  border-radius: 0;
}
.carousel--bleed .cslide figcaption { display: none; }
.carousel--bleed .cprev { left: var(--space-6); }
.carousel--bleed .cnext { right: var(--space-6); }
/* The reference marks its frames with short DASHES laid over the bottom of
   the photograph itself, not dots on the band below it — so this variant
   overrides both the shape and the position. */
.carousel--bleed .cdots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 18px;                         /* measured: dash bottom 1799, image ends 1817 */
  margin-top: 0;
  padding-bottom: 0;
  gap: var(--space-2);
  z-index: 2;
}
/* These were 40x6 white dashes, traced off the live store's product gallery.
   Every other strip on this site — the home page's hero, its destination
   cards, the review carousel — marks its position with a round dot, so the
   trip heroes were the one place a reader had to learn a second vocabulary
   for the same idea (client, 2026-09-01). Same shape as the rest now; they
   keep the cream-on-dark treatment because, like the card dots, they sit on
   a photograph rather than on a page ground. */
.carousel--bleed .cdot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid color-mix(in oklab, var(--awe-cream), transparent 45%);
  background: color-mix(in oklab, var(--awe-black), transparent 45%);
}
.carousel--bleed .cdot[aria-current="true"] {
  background: var(--accent);
  border-color: var(--accent);
}
@media (max-width: 560px) {
  .trip-bleed-figure img,
  .carousel--bleed .cslide img { height: 52vh; }
}

/* THE PRODUCT HEADER — a faithful rebuild of the live store's own single-
   product header (measured against the Full Day product page, 2026-08-31).
   Four things make it read as that page rather than as this site's usual
   band, and all four were wrong before:

     1. it sits on WHITE with BLACK ink, not the page's cream and forest —
        hence the `paper` ground added to the design system for it;
     2. it is LEFT-ALIGNED. This block is the one deliberate exception to the
        sitewide centring, because the reference it copies is left-aligned
        and centring it is what made every earlier attempt look wrong;
     3. the columns are NOT equal — the title takes about a third and the
        story about two thirds, so the title breaks over two short lines
        instead of being squeezed into a narrow half;
     4. it runs WIDER than the site's 1120px reading container; on the
        reference the facts strip very nearly touches the viewport edges. */
/* EVERY NUMBER BELOW IS MEASURED, NOT ESTIMATED. The live product page was
   rendered at a 1728px viewport and the pixels read off it directly; the
   figures in the comments are those readings. Re-measure the same way before
   changing any of them — earlier passes at this block were eyeballed, and
   each one was wrong in a different way.

   NOTE: `--space-10` does not exist in this system (the scale is 1,2,3,4,5,6,
   8,12). An earlier version of this block used it, so `padding-block` and the
   facts strip's `margin-top` both computed to zero — which is exactly why the
   white band came out far too short. Raw px below are deliberate: these are
   measurements off a reference, not scale steps. */
.band.trip-intro { padding-block: 30px; }        /* measured 1818→1848, 2067→2097 */
/* The header runs nearly edge to edge: measured inset 65px each side at a
   1728px viewport (content 1598px), not the site's 1120px reading column. */
.trip-wrap {
  max-width: none;
  padding-inline: clamp(24px, 3.76vw, 96px);
}

.trip-split {
  display: grid;
  gap: var(--space-6);
  text-align: left;
}
.trip-title {
  margin: 0;
  text-align: left;
  max-width: none;
  font-family: var(--font-display);
  /* glyph band measured 30px cap-to-descender → ~33px at 1728px wide */
  font-size: clamp(26px, 1.91vw, 33px);
  line-height: 1.2;
  font-weight: 400;
  color: var(--fg);
}
.trip-desc { text-align: left; }
.trip-desc p {
  margin: 0;
  text-align: left;
  max-width: none;
  color: var(--fg);
  font-size: var(--text-base);          /* 18px — measured                    */
  line-height: 1.7;                     /* measured 30.7px between line tops  */
}
.trip-divider { display: none; }
@media (min-width: 900px) {
  /* Columns measured 543px : 2px : 1052px → 1fr : 1.937fr. The description
     column carries its own 33px inset off the rule; the title column starts
     flush with the container edge, so the grid gap itself is zero. */
  .trip-split {
    grid-template-columns: minmax(0, 1fr) 2px minmax(0, 1.937fr);
    align-items: center;
    gap: 0;
  }
  /* Both columns hold the same 33px off the rule. Without the title's side a
     long heading ("A Tryst With Tigers — Regular Safaris") ends flush against
     the divider with no gap at all, which reads as a mistake. The padding
     sits inside the cell, so the rule itself stays at the measured x. */
  .trip-title { padding-right: 33px; }
  .trip-desc { padding-left: 33px; }
  /* Sampled #2c3c1a — forest, the one piece of brand colour in the header,
     and 2px wide, not the 1px hairline the rest of the site rules with. */
  .trip-divider {
    display: block;
    width: 2px;
    align-self: stretch;
    background: var(--awe-forest);
  }
}

/* The facts strip: four EQUAL columns, each left-aligned — measured item
   origins 74/461/851/1241 are evenly pitched, so this is a grid, not a
   `space-between` row (which would push the last item to the right edge, and
   on the reference it stops well short of it). Border sampled pure black, not
   the ground's grey hairline. Box measured 43px tall overall. */
.trip-facts {
  margin: 54px 0 0;                     /* measured 1970→2024                 */
  padding: 10px 8px;                    /* 1+10+21+10+1 = the 43px measured   */
  border: 1px solid var(--fg);
  border-radius: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: center;
  gap: var(--space-4) 0;
}
.tfact {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  text-align: left;
  min-width: 0;
}
.tfact .ticon { flex: none; color: var(--fg); }
.tfact span {
  font-family: var(--font-body);
  font-size: var(--text-base);          /* 18px — measured 18px glyph band    */
  line-height: 1.2;
  color: var(--fg);
  text-align: left;
}
/* Below the grid's comfortable width the four columns become two, then one —
   the reference has no phone layout to copy, so this is the site's own. */
@media (max-width: 1100px) {
  .trip-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); padding: var(--space-4); }
}
@media (max-width: 620px) {
  .trip-facts { grid-template-columns: minmax(0, 1fr); }
}

.trip-buy-split { align-items: start; }
/* The buy band repeats the title and story beside its own bounded photo —
   real WooCommerce behaviour (page header, then product summary), so this
   is a second heading, not a copy of the h1: same words, smaller and
   semantically an h2. */
.trip-buy-h {
  font-size: var(--text-2xl);
  margin: 0 0 var(--space-4);
}
.trip-buy-desc {
  max-width: 56ch;
  margin: 0 0 var(--space-6);
  color: var(--fg-2);
}
.trip-details { margin-bottom: var(--space-6); }
.tdetail {
  border-bottom: var(--awe-rule);
}
.tdetail:first-of-type { border-top: var(--awe-rule); }
.tdetail summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-4);
  cursor: pointer;
  list-style: none;
  font-family: var(--font-data);
  font-size: var(--text-sm);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
}
.tdetail summary::-webkit-details-marker { display: none; }
.tdetail .tchev {
  flex: none;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--motion-fast) var(--ease-standard);
}
.tdetail[open] .tchev { transform: rotate(-135deg); }
.tdetail ul {
  margin: 0 0 var(--space-4);
  padding-left: 1.1em;
  font-size: var(--text-base);
  color: var(--fg-2);
}
/* Inclusions and exclusions read from the left (client, 2026-09-16). Centred,
   the bullet stayed pinned to the list's left edge while its text floated to
   the middle of the row, so the two came apart — and a long inclusion wrapped
   to a second centred line with no bullet under it. On the li itself, not the
   ul: site.css centres every element directly, so alignment never inherits. */
.tdetail li { text-align: left; }
.tdetail li + li { margin-top: var(--space-2); }

.trip-buy .trip-price {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  margin: 0 0 var(--space-5);
}
/* A fully-rounded modifier for "View itinerary" — the reference page's own
   secondary button is a pill, distinct in shape (not colour, which this
   site has only one accent for) from the small-radius primary button. */
.btn-pill { border-radius: 999px; }
.btn-whatsapp {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-6);
  padding: var(--space-3) var(--space-6);
  border-radius: 999px;
  background: var(--fg);
  color: var(--bg);
  font-family: var(--font-data);
  font-size: var(--text-sm);
  letter-spacing: var(--awe-tracking-data);
  text-decoration: none;
  border: none;
}
.btn-whatsapp:hover { opacity: .85; }

/* One slide fills the whole bounded box rather than peeking the next frame —
   this carousel is a product photo, not a "from the field" strip, so there
   is nothing to tease. */
.carousel--single .ctrack { gap: 0; }
.carousel--single .cslide { flex-basis: 100%; }
.carousel--single .cslide figcaption { display: none; }
.trip-box-figure figcaption { display: none; }

/* ── buy: one bar per size, each with its own Add to cart ───────────────────
   Sizes stack as thin, wide bars rather than the squarish side-by-side cards
   they started as (client, 2026-09-02): a long rectangle reads as a line in a
   price list, which is what a reader comparing two sizes is actually doing,
   and it stops the pair from competing with the photograph above them.

   Each bar runs size · price · button across its width, so the price a reader
   is looking at is always the one the button beside it buys. Below 560px the
   bar recentres into a stack — three items strung across a phone would leave
   the button too narrow to hit comfortably. */
.buy {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
  max-width: 520px;
  margin-inline: auto;
}
/* `1px solid var(--border)`, NOT `var(--awe-rule)`. A custom property's own
   var() references resolve where the property is DECLARED, so `--awe-rule`
   is computed once on :root and carries the cream light-mode --border down
   into every surface — which drew these boxes as bright cream rectangles on
   black. Naming --border here lets it resolve against the night surface the
   print pages actually sit on. */
.buy-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2) var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-5);
}
@media (min-width: 560px) {
  .buy-row {
    flex-wrap: nowrap;
    justify-content: space-between;
    text-align: left;
  }
  /* The price takes the slack so the button stays pinned to the right edge
     of every bar and the two buttons line up down the page. */
  .buy-row .buy-price { margin-inline-end: auto; }
}
.buy-size {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--muted);
}
.buy-price {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  color: var(--fg);
}

/* How many of this size are left. Set as data, not prose, and kept quiet:
   it is information a buyer wants at the moment of choosing, not a scarcity
   badge shouting at them. Turns to the accent only when the edition is
   actually gone, which is the one state that changes what they can do. */
.buy-left,
.facts--swatch .buy-left {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.buy-left--out { color: var(--accent); }
.buy .buy-add { flex: none; }
@media (max-width: 559px) {
  .buy .buy-add { margin-top: var(--space-2); }
}
.buy-note {
  margin: var(--space-5) auto 0;
  max-width: var(--awe-measure);
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--muted);
}

/* ── link list ──────────────────────────────────────────────────────────── */
.linklist {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  border-top: var(--awe-rule);
  max-width: 76ch;
}
.linklist li {
  border-bottom: var(--awe-rule);
  padding-block: var(--space-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: baseline;
  justify-content: space-between;
}
.linklist a {
  color: var(--fg);
  text-decoration: none;
  font-size: var(--text-lg);
}
.linklist a:hover { color: var(--accent); }
.linklist span {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--muted);
}

/* ── cta band ───────────────────────────────────────────────────────────── */
/* On the default cream page a call to action needs to be lifted off the ground,
   so it gets a panel. On a band that has already changed ground — sand, forest
   — the colour field IS the emphasis, and a panel inside it is a second box
   saying the same thing. */
.cta {
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-8);
  background: var(--surface);
}
[data-surface] .cta {
  border: 0;
  padding: 0;
  background: none;
}
.cta .head-block { margin-bottom: 0; }
.cta .acts { margin-top: var(--space-6); }
/* Print pages: the buy button sits centred under a centred title, matching
   the live product page's own centred Add to Cart. */
.cta--center .acts { justify-content: center; }
.standfirst {
  color: var(--fg-2);
  max-width: var(--awe-measure);
  margin-block: var(--space-4) 0;
  margin-inline: auto;
}

/* ── footer additions ───────────────────────────────────────────────────── */
/* `.foot-top` and `.foot-brand` used to live here as well as in site.css, and
   because blocks.css loads second this copy silently won. The footer lockup is
   defined once now, in site.css. */
.band.tight { padding-block: var(--space-8); }

/* ── bespoke calculator ───────────────────────────────────────────────────
   A one-question-per-screen wizard. No new colours or radii — every value
   here is a token already used elsewhere in this file, just applied to a
   form for the first time (this is the site's first form of any kind). */
.calc {
  max-width: var(--awe-measure);
  margin-left: auto;
  margin-right: auto;
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-8);
  background: var(--surface);
  text-align: center;
}
/* The wizard toggles `hidden` on buttons and panels whose classes set display
   (.btn is inline-block), and a class beats the attribute. Back used to dodge
   this by being a bare .btn-quiet, which rendered as an unstyled browser
   button; it carries .btn now, so the rule has to exist (2026-09-15). */
.calc [hidden] { display: none; }
/* Every <p> carries the reading-measure max-width and no inline margin, so
   inside this centred card a hint or group label sat left of centre — its
   text centred in a box that was not (client screenshot, 2026-09-15). */
.calc p { margin-inline: auto; }
/* Next waits for an answer; it has to look like it is waiting, or a tap on
   it simply does nothing. */
.calc .btn:disabled { opacity: .4; cursor: not-allowed; background: var(--accent); }
/* Date pickers and number spinners draw their icons for a light page unless
   told otherwise, which left them near-invisible on the dark grounds. */
[data-surface="night"] .calc,
[data-surface="forest"] .calc { color-scheme: dark; }
@media (max-width: 560px) {
  .calc { padding: var(--space-6) var(--space-5); }
}
.calc-progress {
  height: 4px;
  border-radius: var(--radius-sm);
  background: var(--border);
  margin-bottom: var(--space-6);
  overflow: hidden;
}
.calc-progress::before {
  content: "";
  display: block;
  height: 100%;
  width: var(--calc-pct, 0%);
  background: var(--accent);
  transition: width var(--motion-fast) var(--ease-standard);
}
.calc-trail {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin-bottom: var(--space-6);
}
/* Same [hidden]-vs-class-display specificity tie as .calc-lead below. */
.calc-trail[hidden] { display: none; }
.calc-chip {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--accent-on);
  background: var(--accent);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}
.calc-step h3 {
  font-size: var(--text-xl);
  margin-block: 0 var(--space-5);
}
.calc-row {
  display: flex;
  gap: var(--space-5);
  flex-wrap: wrap;
  justify-content: center;
}
.calc-step label {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--fg-2);
  max-width: 200px;
  margin-left: auto;
  margin-right: auto;
}
.calc-step input[type="number"],
.calc-step input[type="date"] {
  font: inherit;
  font-size: var(--text-xl);
  color: var(--fg);
  text-align: center;
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  background: none;
  width: 100%;
}
.calc-step input[type="number"]:focus-visible,
.calc-step input[type="date"]:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.calc-hint {
  color: var(--muted);
  font-size: var(--text-sm);
  margin-block: var(--space-3) 0;
}
.calc-hint--warn { color: var(--danger); }
.calc-writein { margin-top: var(--space-4); }
.calc-writein .btn { margin-top: var(--space-4); }
.calc-grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  justify-content: center;
}
.calc-opt {
  font: inherit;
  font-family: var(--font-data);
  font-size: var(--text-sm);
  text-align: center;
  color: var(--fg);
  background: none;
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-standard);
}
.calc-opt:hover { border-color: var(--accent); }
.calc-opt:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.calc-opt.is-selected {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-on);
}
.calc-opt--card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.calc-opt--card strong { font-size: var(--text-sm); }
.calc-opt--card span {
  font-family: var(--font-data);
  font-weight: normal;
  color: var(--fg-2);
}
.calc-opt--card.is-selected span { color: var(--accent-on); }
/* Next is centred (client, 2026-09-02); Back stays on the left where a back
   button belongs. A three-column grid rather than flex, and both buttons
   placed EXPLICITLY, because Back is `hidden` on the first step: with auto
   placement its absence would slide Next into the first column, so the
   button would move as you walked through the wizard. */
.calc-nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-8);
}
.calc-nav [data-calc-back],
.calc-nav [data-q-back] { grid-column: 1; grid-row: 1; justify-self: start; }
.calc-nav [data-calc-next],
.calc-nav [data-q-next] { grid-column: 2; grid-row: 1; }
/* The lead modal reuses .calc-nav for Cancel / Continue. Those two are a
   pair, not a step forward, so they keep the original split. */
.calc-lead-card .calc-nav {
  display: flex;
  justify-content: space-between;
}
.calc-result h3 { margin-bottom: var(--space-3); }
.calc-range {
  font-family: var(--font-data);
  font-size: var(--text-xl);
  color: var(--accent);
  margin-block: 0;
}
.calc-range-label {
  color: var(--muted);
  font-size: var(--text-sm);
  margin-block: var(--space-1) var(--space-4);
}
/* base.css caps every `p` at the reading measure and gives it NO auto
   margins. With the sitewide `text-align: center`, a SHORT line therefore
   centres itself inside a box pinned to the left edge — so "for your whole
   group" and "per person" sat about ninety pixels left of the figures they
   belong under, while the long figures themselves looked centred because
   they nearly fill the measure (client, 2026-09-05). The pullquote and the
   print disclaimer further down this file document the same trap; the fix
   is the same one: give the box its auto margins back. */
.calc-result > h3,
.calc-result > p,
.calc-usd > p { margin-inline: auto; }
.calc-range--pp {
  font-size: var(--text-lg);
  color: var(--fg-2);
}
.calc-usd {
  padding-top: var(--space-4);
  border-top: var(--awe-rule);
}
.calc-summary-label { margin-top: var(--space-6); }
/* A single centered column, not a two-column dt/dd grid: two columns of
   unequal width (auto / 1fr) would each center their own text independently
   and never line up on one shared axis — the opposite of "center align all
   the text." Each dt/dd pair stacks instead, dt sitting close to its own dd
   and further from the next pair. */
.calc-summary {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0;
  padding-top: var(--space-4);
  border-top: var(--awe-rule);
}
.calc-summary dt {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--muted);
  margin-top: var(--space-4);
}
.calc-summary dt:first-child { margin-top: 0; }
.calc-summary dd {
  margin: var(--space-1) 0 0;
  color: var(--fg);
  font-family: var(--font-data);
  font-size: var(--text-sm);
}
.calc-result .btn { margin-top: var(--space-6); }
.calc-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-4);
  align-items: center;
  margin-top: var(--space-6);
}
.calc-actions .btn,
.calc-actions .btn-quiet { margin-top: 0; }

/* ── lead-capture gate (Download PDF / Enquire) ───────────────────────────── */
.calc-lead {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: color-mix(in oklab, var(--awe-black), transparent 35%);
}
/* [hidden]'s specificity ties with the class rule above, and this
   stylesheet loads after the browser's own — without this, .calc-lead's
   display:flex wins even while [hidden] is set, so the modal would show up
   immediately on page load instead of only when Download/Enquire is clicked. */
.calc-lead[hidden] { display: none; }
.calc-lead-card {
  width: 100%;
  max-width: 420px;
  background: var(--surface);
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-6);
  box-shadow: 0 24px 44px color-mix(in oklab, var(--awe-black), transparent 52%);
}
.calc-lead-card h3 { margin-block: 0 var(--space-2); font-size: var(--text-xl); }
.calc-lead-card label {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--fg-2);
  margin-top: var(--space-4);
}
.calc-lead-card input {
  font: inherit;
  font-size: var(--text-base);
  color: var(--fg);
  /* Name/phone/email stay left-aligned even though .calc centers everything
     else — typed text (especially an email address) reads badly centered
     while it's actively being filled in. */
  text-align: left;
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  background: none;
  width: 100%;
}
.calc-lead-card input:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.calc-lead-card .calc-nav { margin-top: var(--space-6); }

/* ── printable trip recap ─────────────────────────────────────────────────
   Invisible on screen; shown only inside @media print, and only while
   body.is-printing is set (CALCULATOR_JS toggles it right before/after
   window.print()). visibility (not display) hides every other element so
   the printout works regardless of how deep .calc-print sits in the page. */
.calc-print { display: none; }

@media print {
  /* ONE SHEET, BY CONSTRUCTION.
     Everything here is sized in mm and pt rather than the site's screen
     tokens. The tokens are tuned for a viewport and produced a recap that ran
     to two pages with the type still looking oversized on paper; a print sheet
     has a known width and a known height, so it is laid out against those.

     CALCULATOR_JS moves .calc-print up to be a direct child of <body> before
     it calls window.print(). That move is what lets the rule below use
     `display` rather than `visibility` — a `visibility: hidden` element keeps
     its box, so the calculator page's full height stayed in the flow and the
     printer paginated across it. That was the six blank pages. */
  @page { size: A4; margin: 0; }

  body.is-printing > * { display: none !important; }

  /* The panel is 255mm so it clears US Letter too, which leaves slack at the
     foot of an A4 sheet. The page's own cream ground showed through it as a
     pale strip under the footer, so the body carries the forest as well and
     the sheet is one colour whatever the paper. */
  body.is-printing {
    margin: 0;
    background: var(--awe-forest);
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  body.is-printing > .calc-print {
    display: flex !important;
    flex-direction: column;
    position: static;
    box-sizing: border-box;
    width: 100%;
    /* `min-height`, not a fixed 297mm. A fixed A4 height clipped its own
       footer the moment the guest's print dialog was set to US Letter, which
       is 18mm shorter. 255mm is inside Letter's printable area as well as
       A4's, so the sheet fills the page on both and is cut off on neither. */
    min-height: 255mm;
    padding: 12mm 14mm;
    background: var(--awe-forest);
    color: var(--awe-cream);
    text-align: center;
    font-size: 10pt;
    line-height: 1.35;
    /* Browsers drop background colours and images when printing unless the
       page asks for them back. Without this the sheet came out as black text
       on bare white. */
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* THE CENTRING BUG. base.css gives EVERY <p> the reading measure
     (`max-width: var(--awe-measure)`) and no auto margins, so each paragraph
     was a narrow box pinned to the left edge — `text-align: center` then only
     centred the text inside that narrow box, which is why the labels under
     the prices sat left while the summary (a flex column) sat centred. Same
     fix `.block-act` carries for the same reason. */
  body.is-printing > .calc-print p,
  body.is-printing > .calc-print h2,
  body.is-printing > .calc-print dl {
    max-width: none;
    margin-inline: auto;
  }

  body.is-printing > .calc-print * { color: var(--awe-cream); }

  body.is-printing > .calc-print .print-logo {
    height: 13mm;
    margin: 0 auto 5mm;
    flex: none;
  }

  body.is-printing > .calc-print .print-photo {
    width: 100%;
    height: 68mm;
    max-height: 68mm;
    object-fit: cover;
    border-radius: 2mm;
    margin: 0 0 6mm;
    flex: none;
  }

  body.is-printing > .calc-print .print-eyebrow {
    font-family: var(--font-data);
    font-size: 8pt;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    opacity: 0.7;
    margin: 0 0 1mm;
  }

  body.is-printing > .calc-print h2 {
    font-size: 20pt;
    line-height: 1.15;
    margin: 0 0 1mm;
  }

  body.is-printing > .calc-print .print-kicker {
    font-size: 9pt;
    opacity: 0.7;
    margin: 0 0 5mm;
  }

  /* Prices. The group figure leads; per-person is the quieter second line. */
  body.is-printing > .calc-print .calc-range {
    font-family: var(--font-data);
    font-size: 17pt;
    margin: 0;
  }
  body.is-printing > .calc-print .calc-range--pp { font-size: 13pt; }
  body.is-printing > .calc-print .calc-range-label {
    font-size: 8pt;
    opacity: 0.7;
    margin: 0 0 3mm;
  }
  body.is-printing > .calc-print .calc-usd {
    padding-top: 3mm;
    margin-top: 1mm;
    border-top: 0.3mm solid color-mix(in oklab, var(--awe-cream), transparent 75%);
  }

  /* The eight-row recap. On screen it is a centred flex column, which on
     paper cost sixteen lines and was most of the second page. Here it is a
     two-column grid: label right, value left, one row each. */
  body.is-printing > .calc-print .calc-summary {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 6mm;
    row-gap: 1.6mm;
    align-items: baseline;
    width: 100%;
    /* This used to be `margin: auto 0 0`, which pushed the summary AND the
       footer to the bottom of the sheet and left one large void under the
       prices. Only the footer is pinned now (below); the recap follows the
       prices where it belongs and the slack collects above the footer, where
       whitespace reads as intentional. */
    margin: 7mm 0 0;
    padding-top: 4mm;
    border-top: 0.3mm solid color-mix(in oklab, var(--awe-cream), transparent 75%);
    text-align: left;
  }
  body.is-printing > .calc-print .calc-summary dt {
    font-family: var(--font-data);
    font-size: 7.5pt;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    opacity: 0.7;
    text-align: right;
    margin: 0;
  }
  body.is-printing > .calc-print .calc-summary dd {
    font-size: 9pt;
    margin: 0;
  }

  body.is-printing > .calc-print .print-footer {
    flex: none;
    font-size: 8pt;
    opacity: 0.7;
    /* Takes the slack, so the sheet is bottomed out rather than trailing off. */
    margin: auto 0 0;
    padding-bottom: 0;
    padding-top: 3mm;
    border-top: 0.3mm solid color-mix(in oklab, var(--awe-cream), transparent 75%);
  }

  /* Nothing inside a single-sheet layout should try to break anyway, but the
     photograph in particular must never straddle an edge. */
  body.is-printing > .calc-print .print-photo,
  body.is-printing > .calc-print .calc-summary,
  body.is-printing > .calc-print .print-footer {
    break-inside: avoid;
    page-break-inside: avoid;
  }
}
.print-logo {
  display: block;
  height: 64px;
  width: auto;
  margin: 0 auto var(--space-6);
}
.print-photo {
  width: 100%;
  max-height: 320px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-6);
}
.print-footer {
  color: var(--muted);
  font-size: var(--text-sm);
  margin-top: var(--space-6);
  border-top: var(--awe-rule);
  padding-top: var(--space-4);
}

/* A prose section's button (blk_prose's `cta`). The card blocks pin theirs to
   the bottom of a stretched card with `margin-top: auto`; a paragraph has no
   bottom to pin to, so this is plain space after the text — enough that the
   button reads as the section's conclusion and not as another line of it. The
   `border-bottom` reset is the same one .icard-foot .btn carries: the site's
   prose links are underlined, and without it a stray rule sits under the
   label. */
.prose-foot { margin-top: var(--space-6); }
.prose-foot .btn { border-bottom-width: 1px; }

/* A `figure` or `carousel` may carry a cta — see chrome._cta. It is used by
   the centred bands that introduce a person, so it centres with its heading
   rather than sitting hard left under the picture. */
/* `max-width: none` and auto margins are the working half of this rule, not
   `text-align`. base.css gives EVERY <p> the reading measure
   (`max-width: var(--awe-measure)`) and no auto margins, so this paragraph is
   a narrow box pinned to the wrap's left edge — centring text inside it only
   centres the button within that box, which is why the button still sat left
   of the page centre with `text-align: center` already applied sitewide
   (client, 2026-09-02, twice). Same fix `.band.hero.hero-center .lede` uses. */
.block-act {
  margin-top: var(--space-6);
  margin-inline: auto;
  max-width: none;
  text-align: center;
}

/* SHOWCASE — one big frame that advances on its own, the hero's treatment
   brought inside a section (client, 2026-09-02: "same template as the hero
   image, one big image, auto-scroll"). It is `--bleed`'s scale without
   `--bleed`'s edge-to-edge assumptions: this one sits in the page container,
   so it keeps the rounded corner every other in-container image has.

   No peek of the next slide and no caption, same reasoning as `--single`:
   a band introducing a person is one portrait at a time, not a strip.
   Autoplay, hover/focus pause, off-screen pause and prefers-reduced-motion
   all come from CAROUSEL_JS, which drives every [data-carousel] on the page. */
.carousel--showcase .ctrack { gap: 0; }
.carousel--showcase .cslide { flex: 0 0 100%; scroll-snap-align: start; }
.carousel--showcase .cslide img {
  aspect-ratio: unset;
  width: 100%;
  height: min(70vh, 680px);
  object-fit: cover;
}
.carousel--showcase .cslide figcaption { display: none; }
/* Dots ride on the photograph rather than the band below it — there is only
   one frame showing, so the band would otherwise be a row of dots under a
   picture with nothing to relate them to. */
.carousel--showcase .cdots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 18px;
  margin-top: 0;
  padding-bottom: 0;
  z-index: 2;
}
.carousel--showcase .cdot {
  border: 1px solid color-mix(in oklab, var(--awe-cream), transparent 45%);
  background: color-mix(in oklab, var(--awe-black), transparent 45%);
}
.carousel--showcase .cdot[aria-current="true"] {
  background: var(--accent);
  border-color: var(--accent);
}
@media (max-width: 560px) {
  .carousel--showcase .cslide img { height: 52vh; }
}

/* PRICE BOXES — one per currency, with the basis under each (client,
   2026-09-02, replacing the single "INR … / USD … per person" line on pages
   that opt in with `price_boxes`).

   They sit in `.trip-buy`, which is a night surface, so the border is the
   cream ground held back rather than a grey: a true grey on near-black reads
   as dirt. `flex-wrap` and the `min-width` are what stop the pair from
   crushing on a phone — below about 380px they stack, which is the right
   answer for two boxes that are each a complete statement. */
.priceboxes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin: 0 0 var(--space-5);
}
.pricebox {
  flex: 1 1 auto;
  min-width: 11rem;
  padding: var(--space-4) var(--space-5);
  border: 1px solid color-mix(in oklab, var(--awe-cream), transparent 78%);
  border-radius: var(--radius-2, 4px);
}
.pricebox-amt {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  line-height: 1.15;
}
.pricebox-per {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-data);
  font-size: var(--text-sm);
  letter-spacing: var(--awe-tracking-data);
  color: var(--fg-2);
}

/* The buy button, directly under the price boxes (client, 2026-09-03). It
   adds the seat and lands on the cart in one hop.

   Full width, because it is the one thing on this page that takes money and
   the two buttons under it ("Ask about a date", the itinerary) are the
   alternatives to it, not its peers. It sits on the night surface with the
   price boxes, so it takes the accent rather than the cream outline those
   two wear. */
.btn-buy {
  display: block;
  width: 100%;
  margin: 0 0 var(--space-4);
  text-align: center;
}

/* The deposit sentence between the prices and the button (client,
   2026-09-15: say plainly that a 50% deposit books the trip). Left-aligned
   and at reading size because it is a sentence someone should actually read
   before paying, not a caption. */
.trip-deposit {
  margin: 0 0 var(--space-5);
  max-width: none;
  text-align: left;
  font-size: var(--text-base);
  line-height: 1.55;
  color: var(--fg-2);
}
.trip-deposit strong { color: var(--fg); font-weight: 600; }
.trip-deposit a { color: var(--fg); white-space: nowrap; }

/* ── bespoke write-in enquiry ──────────────────────────────────────────────
   The form for the trip the calculator cannot price (client, 2026-09-02):
   somewhere it does not cover, several parks at once, or a guest who would
   rather write to a person.

   It borrows the calculator's own controls rather than defining new ones —
   `.calc-hint` for the small print, the same `--awe-rule` borders and
   `--focus-ring` — because the two sit one band apart and a second form with
   its own manners would read as a second website.

   Two rules here are answers to seeing it built (client, 2026-09-02):

   LEFT-ALIGNED. The band centres its heading block, and the form inherited
   that — so every label sat centred over a full-width box, and the eye had
   no single edge to run down. Nine questions need a left margin to read as
   a list rather than as nine separate announcements.

   WHITE FIELDS. The inputs were transparent on cream with a hairline border,
   which is elegant and, on a page that is almost entirely cream, invisible:
   nothing said "type here". `--surface` is the kit's own raised-panel white
   on this ground, so the fields lift off the page without introducing a
   colour the design system does not already have. */
.wi { max-width: 62rem; margin-inline: auto; text-align: left; }
.wi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}
/* The destination dropdown is eight rows tall; the text field beside it is
   one. Top-aligned so the two labels sit on the same line. */
.wi-row--dest { align-items: start; }
.wi-field { display: block; }
.wi-label {
  display: block;
  font-family: var(--font-data);
  font-size: var(--text-sm);
  letter-spacing: var(--awe-tracking-data);
  color: var(--fg-2);
  margin-bottom: var(--space-2);
}
.wi input[type="text"], .wi input[type="tel"], .wi input[type="email"],
.wi input[type="number"], .wi textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: var(--fg);
  background: var(--surface);
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
}
.wi textarea { resize: vertical; }
.wi input::placeholder, .wi textarea::placeholder { color: var(--muted); }
.wi input:focus-visible, .wi textarea:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
/* THE DESTINATION DROPDOWN — closed until clicked (client, 2026-09-02).
   Built from a button and a panel rather than a `<select>`, because a native
   dropdown holds one answer and this needs several. Everything visible is
   therefore borrowed back from the text fields above it — the same white
   ground, the same rule, the same radius — so it reads as the same control
   even though it is not the same element. */
.wi-dd { position: relative; }
.wi-dd-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--text-base);
  text-align: left;
  color: var(--muted);
  background: var(--surface);
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
}
/* Chosen destinations are content, not a prompt, so the label stops being
   placeholder-grey the moment there is something in it. */
.wi-dd.has-value .wi-dd-btn { color: var(--fg); }
.wi-dd-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.wi-dd-caret {
  width: 0.5em;
  height: 0.5em;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-0.15em) rotate(45deg);
  transition: transform var(--motion-fast) var(--ease-standard);
  flex: none;
}
.wi-dd.is-open .wi-dd-caret { transform: translateY(0.1em) rotate(-135deg); }
/* Absolute, so opening the menu does not push the rest of the form down the
   page — which is the whole difference between a dropdown and a list. */
.wi-dd-panel {
  position: absolute;
  z-index: 5;
  inset-inline: 0;
  top: calc(100% + 4px);
  max-height: 17rem;
  overflow-y: auto;
  background: var(--surface);
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  box-shadow: 0 8px 24px color-mix(in oklab, var(--awe-black), transparent 88%);
}
.wi-dd-panel .wi-check { padding-block: var(--space-2); }
.wi-dd-group {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--accent);
  margin: var(--space-3) 0 var(--space-2);
}
.wi-dd-group:first-child { margin-top: 0; }
.wi-radios {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  padding-block: var(--space-2);
}
.wi-check {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-base);
  cursor: pointer;
}
.wi-check input { accent-color: var(--accent); }
/* The two actions stack, centred (client, 2026-09-02). The form itself is
   left-aligned — nine questions need an edge to run down — but its close is
   a different job: one thing to do, and one quieter alternative under it.
   `.btn-whatsapp` carries its own `margin-top`, so the gap here is the
   column gap only. */
.wi-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: var(--space-6);
}
.wi-actions .btn { border-bottom-width: 1px; }
.wi-actions .btn-whatsapp { margin-top: var(--space-4); }
.wi-done { max-width: var(--awe-measure); }
/* THE SHORT ENQUIRY (trip and contact pages, 2026-09-14). The write-in form's
   own fields, two rows instead of six. `.wi-hp` is the bot trap: off-screen
   rather than display:none, which some bots skip. */
.wi--short { max-width: 52rem; }
.wi--short .wi-done { margin-inline: auto; text-align: center; }
.wi-hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }

/* ── bespoke write-in, as a stepped card (client, 2026-09-15) ─────────────
   The calculator's own card and classes (.calc, .calc-step, .calc-opt,
   .calc-nav). These rules add only what a write-in needs and a price does
   not: text boxes, a grid that holds sixteen places, and Send in Next's
   seat on the last step. No new colours, radii or type sizes. */
.calc--writein .calc-step h3:focus { outline: none; }
.calc--writein .wz-lead { margin-block: calc(-1 * var(--space-3)) var(--space-5); }
.calc--writein .wz-group {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--accent);
  margin-block: var(--space-6) var(--space-3);
}
.calc--writein .wz-lead + .wz-group { margin-top: 0; }
/* Two across even on a phone: sixteen places one per row is a scroll. */
.calc--writein .wz-grid-dest { grid-template-columns: repeat(auto-fill, minmax(min(7.5rem, 40%), 1fr)); }
.calc--writein .wz-grid-dest .calc-opt { padding: var(--space-3); }
.calc--writein .wz-grid-one { grid-template-columns: 1fr; margin-top: var(--space-3); }
.calc--writein .wz-grid-two { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.calc--writein .wz-gap { margin-top: var(--space-6); }
.calc--writein .calc-step label.wz-wide { max-width: 26rem; }
.calc--writein .calc-grid + label.wz-wide { margin-top: var(--space-6); }
.calc--writein .wz-contact { display: grid; gap: var(--space-4); }
.calc--writein .calc-step input[type="text"],
.calc--writein .calc-step input[type="tel"],
.calc--writein .calc-step input[type="email"],
.calc--writein .calc-step textarea {
  font: inherit;
  font-size: var(--text-base);
  color: var(--fg);
  text-align: center;
  border: var(--awe-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  background: none;
  width: 100%;
}
.calc--writein .calc-step textarea { text-align: left; resize: vertical; }
.calc--writein .calc-step input::placeholder,
.calc--writein .calc-step textarea::placeholder { color: var(--muted); }
.calc--writein .calc-step input:focus-visible,
.calc--writein .calc-step textarea:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.calc--writein .calc-nav [data-wz-back] { grid-column: 1; grid-row: 1; justify-self: start; }
.calc--writein .calc-nav [data-wz-next],
.calc--writein .calc-nav [data-writein-submit] { grid-column: 2; grid-row: 1; }
.calc--writein .wi-done { margin-inline: auto; }
.calc--writein + .wi-actions { margin-top: var(--space-6); }
.wi-done-h {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  margin-block: 0 var(--space-3);
}

/* SOLO — a `cards` block laid out as a strip, one card on screen at a time,
   auto-advancing (client, 2026-09-02, for "why travel with us"). Same markup
   contract and the same CAROUSEL_JS as every other strip; only the sizing and
   the control colours change.

   The card is held to a reading measure and centred rather than stretched to
   the full container: one short paragraph across 1200px is a worse read than
   the six-up grid it replaced, which would defeat the point. */
.carousel--solo .ctrack { gap: 0; }
.carousel--solo .cslide { flex: 0 0 100%; scroll-snap-align: start; }
.carousel--solo .cslide .card {
  /* Wider and taller than the grid card it replaced (client, 2026-09-02):
     one card alone on a band needs to hold the eye the way six together did.
     Still capped, because a single short paragraph run across the full
     container is a worse read than the grid. */
  max-width: 920px;
  margin-inline: auto;
  height: 100%;
  /* Was `var(--space-10)`, which does not exist in this system — an undefined
     var voids the whole shorthand, so below 768px (where the override under
     this rule takes over) these cards were rendering with NO padding at all,
     text hard against the card edge. Found 2026-09-16 while boxing the review
     cards; same trap as the one noted at `.band.trip-intro`. */
  padding: var(--space-8) var(--space-8);
}
@media (min-width: 768px) {
  .carousel--solo .cslide .card { padding: var(--space-12) var(--space-12); }
}
.carousel--solo .cslide .card h3 { font-size: var(--text-2xl); }
.carousel--solo .cslide .card > p { font-size: var(--text-lg); }
/* THE CARD ITSELF, on a dark band. The stock `.card` takes `--surface`,
   which on forest is #1f2c12 — a slightly darker green inside a green band,
   so the card read as a faint outline rather than an object (client,
   2026-09-02: "make the cards stand out a little more").

   The fix is a change of MATERIAL, not a nudge in contrast. This site already
   puts cream cards on light grounds ("the people", directly below); doing the
   same on forest reads as the same component inverted, which is a brand
   decision rather than a new one — and it makes six reasons feel like six
   things you could pick up. A darker fill with a brighter edge was the
   alternative and is a half-measure: it makes the outline crisper without
   making the card an object.

   Scoped to `--solo` on a dark surface only, so the same block on a cream
   band keeps the standard card.
   Rust on cream is the kit's own pairing (6.36:1); cream cards carry the
   forest text colour rather than inheriting the band's. */
[data-surface="forest"] .carousel--solo .cslide .card,
[data-surface="night"] .carousel--solo .cslide .card {
  background: var(--awe-cream);
  border-color: transparent;
  box-shadow: 0 14px 40px color-mix(in oklab, var(--awe-black), transparent 68%);
  color: var(--awe-forest);
}
[data-surface="forest"] .carousel--solo .cslide .card h3 a,
[data-surface="night"] .carousel--solo .cslide .card h3 a {
  color: var(--awe-rust);
}
[data-surface="forest"] .carousel--solo .cslide .card > p,
[data-surface="night"] .carousel--solo .cslide .card > p {
  color: color-mix(in oklab, var(--awe-forest), transparent 12%);
}

/* Every other strip's controls sit on a PHOTOGRAPH, so they are a fixed
   cream disc — the one colour that reads on any frame. This strip's slides
   are cards on the page's own ground, and that ground is whatever surface the
   band names: "why travel with us" is on forest today and could be cream
   tomorrow. So the controls take the surface's own tokens and invert them —
   the disc is the foreground colour, the chevron the background — which reads
   on forest, night, sand and cream alike without naming any of them.
   Hardcoding a dark disc here looked right on a cream mock and was invisible
   on the forest band this block actually sits on. */
.carousel--solo .cbtn {
  background: var(--fg);
  border-color: transparent;
  color: var(--bg);
}
.carousel--solo .cbtn:hover {
  background: var(--fg);
  opacity: .88;
}
/* Same reasoning: the default dot is a sand ring, which all but disappears on
   forest. Derive it from the surface instead. */
.carousel--solo .cdot {
  border-color: color-mix(in oklab, var(--fg), transparent 45%);
}

/* Give the arrows their own lane instead of letting them sit on the card
   (client, 2026-09-02). Inset the TRACK rather than nudging the buttons
   outwards: the content lane is 1040px (--container-max 1120 less two 40px
   gutters) and a 920px card leaves 60px a side, which is narrower than one
   48px button — so there is no negative offset that clears the card without
   pushing the button off the page at container width. Padding the carousel
   makes the room instead, and the arrows sit in it at left/right 0.
   The card then fills the narrowed track, ~24px clear of each button.
   Below this width the lane would cost more than the overlap does, so the
   default inset overlay stands. */
@media (min-width: 900px) {
  .carousel--solo { padding-inline: 72px; }
  .carousel--solo .cprev { left: 0; }
  .carousel--solo .cnext { right: 0; }
}

/* The logo hero — the mark on the brand green, no photograph, no headline.
   The <h1> is the lockup itself (see chrome.page_html), so it carries none of
   the heading type: reset the inherited size and spacing, or the h1's own
   margin leaves the mark floating off-centre in the band. */
.hero-logo { text-align: center; }
.hero-logo-mark {
  margin: 0;
  font-size: 0;
  line-height: 0;
}
.hero-logo-mark .lockup {
  display: inline-block;
  width: auto;
  max-width: min(620px, 82vw);
  height: auto;
}
.hero-logo .lede {
  margin: var(--space-8) auto 0;
  max-width: 46ch;
}

/* ── shipping outside India ─────────────────────────────────────────────────
   Sits under the buy buttons on every product page. The shop ships within
   India only; everywhere else is quoted by hand, and this is the invitation
   to ask. Bordered rather than filled, so it reads as a note beside the
   purchase rather than a second competing call to action. */
.intl {
  margin: var(--space-6) auto 0;
  max-width: var(--awe-measure);
  padding: var(--space-5);
  border: 1px solid var(--border);
}
.intl p { margin: 0; }
.intl p + p { margin-top: var(--space-3); }
.intl-h {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.intl-do {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
/* .btn-whatsapp carries its own margin-top for standalone use; inside this
   flow the gap above already does that work. */
.intl-do .btn-whatsapp { margin-top: 0; }

/* ── home.css ── */
/* Atlas Wild Experiences — the landing-page blocks. LAYOUT ONLY, same rule as the rest.
 *
 * These five components exist only on the home page and the journeys page, and
 * they are what stops the site reading as a template: a full-bleed hero, the
 * live seat ticker, the logbook, display figures with their sources, and a
 * pull-quote at display size.
 *
 * Not one raw colour below. build.py fails the build if that stops being true.
 */

/* ── full-bleed hero ────────────────────────────────────────────────────── */
.hero-full {
  position: relative;
  min-height: 78vh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  background: var(--awe-black);
}
/* The photograph fills the section; everything else sits on top of it. Every
   hero is a stack of slides (one, or several that crossfade); the active one is
   opaque and on top, the rest wait at zero opacity. A single-image hero is just
   the one-slide case, so this one rule covers both. */
.hero-full > picture,
.hero-full > picture img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.hero-slides { position: absolute; inset: 0; }
.hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1200ms var(--ease-standard);
}
.hero-slide.is-active { opacity: 1; }
.hero-slide picture,
.hero-slide picture img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.hero-dots {
  position: absolute;
  z-index: 3;
  left: var(--container-gutter-phone);
  bottom: var(--space-4);
  display: flex;
  gap: var(--space-2);
}
.hero-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  border: 1px solid color-mix(in oklab, var(--awe-cream), transparent 45%);
  background: color-mix(in oklab, var(--awe-black), transparent 45%);
  transition: background var(--motion-fast) var(--ease-standard),
              border-color var(--motion-fast) var(--ease-standard);
}
.hero-dot.is-active { background: var(--accent); border-color: var(--accent); }
.hero-dot:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* On a crossfade hero the dots carry the wayfinding, so the scroll cue would
   just crowd the same corner — drop it there. */
.hero-carousel .hero-cue { display: none; }
@media (prefers-reduced-motion: reduce) { .hero-slide { transition: none; } }
/* Two scrims, not one. A single flat overlay greys the whole photograph out;
   a vertical gradient plus a left-weighted one darkens only where type goes. */
.hero-scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top,
      var(--awe-black) 0%,
      color-mix(in oklab, var(--awe-black), transparent 22%) 34%,
      transparent 72%),
    linear-gradient(to right,
      color-mix(in oklab, var(--awe-black), transparent 30%) 0%,
      transparent 62%);
}
.hero-copy {
  position: relative;
  width: 100%;
  padding-block: var(--space-12) var(--space-12);
}
.hero-full h1 {
  font-size: clamp(var(--text-3xl), 6.4vw, 84px);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display);
  max-width: 15ch;
  margin-inline: auto;
  margin-block: 0 var(--space-6);
  /* The type sits on a photograph, so it carries its own shadow rather than
     relying on the scrim being dark enough at every crop. */
  text-shadow: 0 2px 40px var(--awe-black);
}
.hero-full .lede {
  font-size: var(--text-lg);
  max-width: 52ch;
  margin-inline: auto;
  color: var(--fg);
  text-shadow: 0 1px 24px var(--awe-black);
}
/* The copy is a hover reveal (client request, 2026-09-01). Landing on any page
   should be the photograph and nothing else; move the pointer onto the hero and
   the headline, lede and buttons fade up, taking their scrim with them — a flat
   dark wash over a picture with no type on it is just a dimmed picture.

   Removed on 2026-09-15 by the launch audit (a laptop's first screen said
   nothing about what the company sells) and PUT BACK on 2026-09-16 at the
   client's request: "I liked it when the text would only show up when the
   mouse hovers over the hero images on every page." That was the agent's call,
   not the client's — do not take it out again without asking.

   Three guards. It is inside `hover: hover` so a phone, which has no pointer to
   move, keeps the copy visible as before. `:focus-within` opens it too, so a
   keyboard tab into the buttons doesn't put focus on something invisible. And
   it is opacity, never `display` — the h1 stays in the DOM, at full size, for
   search engines and screen readers. The slideshow hero is a grid (see
   .hero-carousel below) and .hero-copy keeps its row while hidden, so the dots
   and caption do not jump when the copy fades. */
@media (hover: hover) and (pointer: fine) {
  .hero-full .hero-copy,
  .hero-full .hero-scrim {
    opacity: 0;
    transition: opacity calc(var(--motion-base) * 2) var(--ease-standard);
  }
  .hero-full .hero-copy {
    pointer-events: none;
    transform: translateY(var(--space-3));
    transition: opacity calc(var(--motion-base) * 2) var(--ease-standard),
                transform calc(var(--motion-base) * 2) var(--ease-standard);
  }
  .hero-full:hover .hero-scrim,
  .hero-full:focus-within .hero-scrim { opacity: 1; }
  .hero-full:hover .hero-copy,
  .hero-full:focus-within .hero-copy {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }
}

.hero-cap {
  position: absolute;
  right: var(--container-gutter-phone);
  bottom: var(--space-4);
  margin: 0;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--muted);
  /* The scrim now fades out with the copy, so this line can find itself on a
     bright corner of a photograph with nothing behind it. Its own shadow, the
     same trick the h1 uses. */
  text-shadow: 0 1px 12px var(--awe-black);
}
.hero-cue {
  position: absolute;
  left: 50%;
  bottom: var(--space-4);
  width: 1px;
  height: 34px;
  background: linear-gradient(var(--accent), transparent);
}

@media (min-width: 768px) {
  .hero-full { min-height: 88vh; }
  .hero-cap { right: var(--container-gutter-tablet); }
  .hero-dots { left: var(--container-gutter-tablet); }
}
@media (min-width: 1040px) {
  .hero-cap { right: var(--container-gutter-desktop); }
  .hero-dots { left: var(--container-gutter-desktop); }
}
/* The hero's motion is the crossfade between slides (see .hero-slide), so the
   old load-time "settle" zoom is gone: applied to the active slide it re-fired
   on every crossfade and fought the fade, and it briefly held the first frame
   at zero opacity. A single, slow dissolve is the quieter choice. */

/* ── the live seat ticker ───────────────────────────────────────────────── */
/* A ticker runs edge to edge or it is just a list in a box. This escapes the
   page's text column without needing a second class in the markup. */
.wrap:has(> .ticker) {
  max-width: none;
  padding-inline: 0;
}
.ticker {
  display: flex;
  align-items: stretch;
  border-block: var(--awe-rule);
  background: var(--surface);
}
.ticker-label {
  flex: none;
  display: flex;
  align-items: center;
  padding-inline: var(--space-5);
  border-right: var(--awe-rule);
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--muted);
  background: var(--bg);
}
/* display:flex, and it is load-bearing. The script appends a CLONE of the
   track as a sibling so the marquee loops without a seam; in a block
   formatting context the clone lands underneath and the ticker renders as two
   stacked copies of the same list. */
.ticker-view { overflow: hidden; flex: 1 1 auto; display: flex; }
.ticker-track {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  padding-block: var(--space-4);
  padding-inline: var(--space-5) 0;
  white-space: nowrap;
  flex: none;
}
.tk {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-data);
  font-feature-settings: var(--awe-figures);
  font-size: var(--text-sm);
  letter-spacing: var(--awe-tracking-data);
  color: var(--fg-2);
  text-decoration: none;
  border: 0;                      /* not prose — the base link rule would rule
                                     a line under every departure */
}
.tk:hover { color: var(--fg); }
/* Availability, encoded in SHAPE first and colour second.
 *
 * The brand palette is entirely warm and has no red: on some grounds `danger`
 * derives to the same rust as the accent, and `warn` is a step of lightness
 * away from it (see the note in derive_tokens.py). Four states cannot be told
 * apart by hue here — and would not be distinguishable to a red-green
 * colour-blind reader in any palette. So the dot fills, then gains a ring,
 * then hollows out, and each row still prints its own seat count in words.
 */
.tk-dot {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: var(--radius-pill);
  background: var(--muted);
  box-shadow: none;
}
.tk-dot[data-status="open"]    { background: var(--success); }
.tk-dot[data-status="filling"] { background: var(--warn); }
/* Last places: filled AND ringed — two channels, not one. */
.tk-dot[data-status="last"] {
  background: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger), transparent 62%);
}
/* Sold out: hollow, and the whole row drops back. */
.tk-dot[data-status="closed"] {
  background: transparent;
  border: 1.5px solid var(--muted);
}
.tk:has(.tk-dot[data-status="closed"]) { color: var(--muted); }

@media (prefers-reduced-motion: no-preference) {
  /* Only once the data has arrived — a marquee scrolling a "loading" row is
     motion that means nothing. */
  .ticker.is-live .ticker-track {
    animation: tick 46s linear infinite;
  }
  .ticker.is-live:hover .ticker-track,
  .ticker.is-live:focus-within .ticker-track { animation-play-state: paused; }
  @keyframes tick {
    from { transform: translateX(0); }
    to   { transform: translateX(-100%); }
  }
}
@media (prefers-reduced-motion: reduce) {
  .ticker-view { overflow-x: auto; }
  .ticker-track { animation: none; }
}

/* ── display figures ────────────────────────────────────────────────────── */
.figs {
  display: grid;
  gap: var(--space-8);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.fig { border-top: 2px solid var(--accent); padding-top: var(--space-5); }
.fig-n {
  font-family: var(--font-display);
  font-size: clamp(var(--text-3xl), 5vw, 68px);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display);
  font-variant-numeric: tabular-nums;
}
.fig-l { margin: var(--space-3) auto 0; color: var(--fg-2); max-width: 30ch; }
.fig-s {
  margin-top: var(--space-3);
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  color: var(--muted);
}
/* Only the first figure gets the accent rule — four accent lines is four
   emphases, which is none. */
.fig:not(:first-child) { border-top-color: var(--border); }

/* ── pull-quote ─────────────────────────────────────────────────────────── */
.pull { margin: 0; }
.pull p {
  font-family: var(--font-display);
  font-size: clamp(var(--text-2xl), 4.4vw, 56px);
  line-height: 1.14;
  letter-spacing: var(--tracking-display);
  margin: 0;
  /* Measured in `em`, so the cap scales WITH the type. `ch` was tried first
     and gave a column three words wide. `ch` is the width of the ZERO, and
     both display faces this brand has used have a narrow one, so 26ch at 56px
     is far less than 26 characters of real text. Verified at 1280px: three
     lines. */
  max-width: 15em;
  margin-inline: auto;
}
.pull cite {
  display: block;
  margin-top: var(--space-6);
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-style: normal;
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--muted);
}

/* A pull-quote that swipes. One quote fills the strip — a peeking neighbour
   works for photographs, where the next frame is an invitation, but half a
   sentence of someone else's testimony just reads as a layout error. The
   arrows sit clear of the text at the band's edges, and the dots go below the
   attribution rather than over it. */
.carousel--quotes { margin-top: 0; }
.carousel--quotes .ctrack { gap: 0; }
.carousel--quotes .cslide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  padding-inline: var(--space-8);
  align-self: center;
}
.carousel--quotes .cbtn { top: 50%; }
.carousel--quotes .cprev { left: 0; }
.carousel--quotes .cnext { right: 0; }
.carousel--quotes .cdots { margin-top: var(--space-8); }
@media (min-width: 768px) {
  .carousel--quotes .cslide { padding-inline: var(--space-12); }
}

/* ── image cards ────────────────────────────────────────────────────────── */
.icards {
  display: grid;
  gap: 2px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.icard {
  display: block;
  text-decoration: none;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.icard-media { aspect-ratio: 4 / 3; overflow: hidden; }
.icard-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--motion-base) var(--ease-standard);
}
.icard:hover .icard-media img { transform: scale(1.03); }
.icard:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.icard-body { padding: var(--space-5); }
/* 25px was set when a card title was "Bandhavgarh". Naming the permit AND
   the host takes them to sixty characters — "A Tryst With Tigers — Full Day
   Safaris, With Our Naturalists" — which at that size ran to four lines and
   pushed the body copy out of the card (client, 2026-09-05). One step down
   the scale, to the lede size, and `text-wrap: balance` so the lines break
   into even lengths instead of leaving one word stranded. Nothing else about
   the card changes; the short titles simply read a touch quieter. */
.icard-body h3 {
  margin-block: 0 var(--space-2);
  font-size: var(--text-lg);
  text-wrap: balance;
}
.icard-body p { margin: 0; color: var(--fg-2); font-size: var(--text-base); }
/* The two "→ …" lines under each trip type. The arrow is the marker, drawn
   here rather than typed into the copy, so the list stays a list: one glyph in
   the accent, hanging outside the text so the lines align on their first
   letter when one of them wraps. */
.icard-points {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.icard-points li {
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--fg-2);
}
/* The arrow rides immediately in front of its line rather than hanging at the
   card's left edge. The card's copy is centred, and a hanging marker on
   centred text leaves the arrow marooned on one side with a hand's width of
   space before the words it belongs to. */
.icard-points li::before {
  content: "\2192";
  margin-right: var(--space-2);
  color: var(--accent);
}

.icard-meta {
  display: block;
  margin-top: var(--space-4);
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--accent);
}
@media (prefers-reduced-motion: reduce) {
  .icard-media img { transition: none; }
  .icard:hover .icard-media img { transform: none; }
}

/* A card whose frame is a carousel (an item with an `images` list). The widget
   is the site's own `.carousel`, resized to live inside the card's 4:3 window
   rather than in the full-width strip it was built for: one slide fills the
   frame, nothing peeks, and the caption is dropped — a 260px-wide card has no
   room to narrate, and the card's own h3 already names what is being shown.

   The hover zoom is off here on purpose. It is a nice affordance on a still,
   but scaling the image inside a scroll-snap track makes the frame drift
   under the pointer while the reader is trying to hit an arrow. */
/* A card with a button at the foot is a column, not a block: the text takes
   whatever height it needs and the button is pushed to the bottom, so the
   three buttons in a row line up even though the three descriptions are
   different lengths. Without this they step down the page like a staircase. */
.icard--split {
  display: flex;
  flex-direction: column;
}
.icard--split .icard-body { flex: 1 1 auto; }
.icard-foot {
  padding: 0 var(--space-5) var(--space-5);
  text-align: center;
}
.icard-foot .btn { border-bottom-width: 1px; }

.icard--carousel .icard-media { position: relative; }
/* On a carousel card the text block is the link, so it has to undo what
   base.css gives every anchor — accent colour and an underline rule, which on
   a card reads as an orange heading with a stray dash under the last line. The
   single-image card never needed this: its own 1px card border overrides the
   anchor's border-bottom, and `.icard` already resets the colour. */
a.icard-body {
  display: block;
  color: var(--fg);
  border-bottom: 0;
}
a.icard-body:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* The image no longer zooms on hover here, so the title carries the "this is
   clickable" cue instead. */
a.icard-body:hover h3 {
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
}
.carousel--card { margin-top: 0; height: 100%; }
.carousel--card .ctrack { gap: 0; height: 100%; padding-bottom: 0; }
.carousel--card .cslide { flex: 0 0 100%; }
.carousel--card .cslide img {
  aspect-ratio: 4 / 3;
  height: 100%;
  border-radius: 0;
}
.carousel--card .cslide figcaption { display: none; }
.icard--carousel:hover .icard-media img { transform: none; }
/* The strip's arrows are sized for a 46%-wide slide; in a card they would eat
   the photograph. Smaller discs, tucked to the edges, and centred on the frame
   itself — the full-size rule offsets them upward to clear a caption that does
   not exist here. */
.carousel--card .cbtn {
  top: 50%;
  width: 32px;
  height: 32px;
  font-size: var(--text-base);
}
.carousel--card .cprev { left: var(--space-2); }
.carousel--card .cnext { right: var(--space-2); }
.carousel--card .cdots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--space-3);
  margin: 0;
  z-index: 2;
}
/* Dots sit on the photograph, not on the card's cream ground, so they carry
   the same cream-on-dark treatment the bleed carousel uses. */
.carousel--card .cdot {
  border-color: color-mix(in oklab, var(--awe-cream), transparent 40%);
  background: color-mix(in oklab, var(--awe-black), transparent 45%);
}
.carousel--card .cdot[aria-current="true"] {
  background: var(--accent);
  border-color: var(--accent);
}

/* ── the logbook ────────────────────────────────────────────────────────── */
.logbook table { min-width: 640px; }
.logbook .lb-d {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  color: var(--muted);
  white-space: nowrap;
}
.logbook .lb-p {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--awe-tracking-data);
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}
.logbook .lb-s { color: var(--fg); font-size: var(--text-base); }
.logbook .lb-n { color: var(--fg-2); }

/* ── carousel controls: target size and pause (launch audit, 2026-09-15) ────
   In home.css because it loads last on every page: the same rules in
   site.css lost to the `gap` that blocks.css and this file set earlier. */
/* Carousel dots stay small to look at but are spaced so each has a 24px
   target around it (WCAG 2.5.8), and a transparent halo widens the actual tap
   area on a phone. Launch audit, 2026-09-15: 767 dots were under-sized. */
.cdots, .hero-dots, .carousel--bleed .cdots { gap: 16px; align-items: center; }
.cdot, .hero-dot { position: relative; }
.cdot::after, .hero-dot::after {
  content: "";
  position: absolute;
  inset: -8px;
  border-radius: 50%;
}

/* Pause and play for anything that moves by itself (WCAG 2.2.2). Added by the
   carousel scripts only where autoplay actually runs, so a reader who asked
   for reduced motion never sees a control for motion that is not there. */
.cpause {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin-left: 4px;
  padding: 0;
  border: 1px solid color-mix(in oklab, var(--awe-cream), transparent 45%);
  border-radius: 50%;
  background: color-mix(in oklab, var(--awe-black), transparent 45%);
  color: var(--awe-cream);
  cursor: pointer;
  align-self: center;
}
.cpause svg { display: block; }
.cpause:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ── long reading, left-aligned (launch audit, 2026-09-15) ───────────────────
   The site centres its text (told directly, 2026-08-30), and headings, ledes,
   cards and short captions still do. Paragraphs of several lines do not: a
   ragged left edge makes the eye hunt for the start of every line, which is
   slow for everyone and hard for dyslexic readers. The column itself stays
   centred on the page; only the lines inside it start from the left. Print
   stories (.prose--center) keep their centred setting. */
.prose:not(.prose--center) p,
.prose:not(.prose--center) li,
.faq p,
.faq li,
.step p,
.callout p { text-align: left; }

/* The reviews wall (launch audit, 2026-09-15) is GONE — /reviews/ is a
   carousel again, as it was at the client's request on 2026-09-02 and as they
   asked for again on 2026-09-16. The `columns: 3 20rem` override that lived
   here laid thirteen reviews of wildly different lengths into three ragged
   columns; with the carousel back it had nothing to style on /reviews/, and on
   the two pages that DO still grid their quotes (/destinations/kanha/ with 2,
   /about/lala/ with 5) it was breaking a tidy row into uneven columns. They
   are back on `.quotes`' own grid in blocks.css, which is right for a handful
   of quotes — the equal-height problem that prompted this only bites at
   thirteen. */

/* ── analytics consent banner (2026-09-15) ───────────────────────────────────
   Rendered only once a GA4 measurement ID is set in chrome.py. It sits at the
   foot of the screen, never over the header, and leaves the page usable
   behind it: a choice, not a wall. */
.consent {
  position: fixed;
  left: var(--space-4);
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: 40;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-3) var(--space-5);
  max-width: 46rem;
  margin-inline: auto;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 12px 32px color-mix(in oklab, var(--awe-black), transparent 50%);
}
.consent[hidden] { display: none; }
.consent p { margin: 0; max-width: 34rem; font-size: var(--text-sm); color: var(--fg); }
.consent a { color: var(--fg); }
.consent-acts { display: flex; gap: var(--space-3); }
/* On a wider screen it is a corner card, so it never covers the hero's own
   buttons in the middle of the first screen. */
@media (min-width: 768px) {
  .consent {
    left: auto;
    right: var(--space-5);
    bottom: var(--space-5);
    max-width: 23rem;
    margin: 0;
    justify-content: flex-start;
  }
  .consent p, .consent p * { text-align: left; }
}

/* ── print pages: fine print, shipping box, story (client, 2026-09-16) ──────
   The colour disclaimer sits directly under the Add to cart buttons, set
   well below the size of the note beside it: it is fine print about what the
   button buys, not a paragraph to read. */
.buy-disclaimer {
  max-width: var(--awe-measure);
  margin: var(--space-3) auto 0;
  font-size: 10px;
  line-height: 1.5;
  font-style: italic;
  color: var(--fg-2);
  text-align: center;
}
.buy-disclaimer + .buy-note { margin-top: var(--space-3); }
/* "Shipping outside India": the text and both buttons centred, like the rest
   of the page. The buttons used to sit at the left under centred text. */
.intl,
.intl p { text-align: center; }
/* A short paragraph's measure cap would otherwise park the label left. */
.intl p { margin-inline: auto; }
.intl-do { justify-content: center; }
/* ── the product page's vertical rhythm ─────────────────────────────────────
   A print page is ONE object — title, photograph, story, sizes, then the offer
   to do something else — not five independent sections. But every one of those
   sections is a `.band`, every band is `night`, and every band carries a full
   96px top AND bottom. So each junction opened 192px of identical black with
   nothing in it, and the page read as five pages that had each ended. The
   client's words: "way too much space between sections" (2026-09-17).

   Measured on /prints/ethereal/ at 1440px, ink to ink, before and after:

       title  -> photograph      240px  ->   64px   belong together
       photograph -> story        16px  ->   16px   unchanged, already right
       story  -> sizes           100px  ->  100px   unchanged, client approved
       sizes  -> custom-size CTA 208px  ->   96px   a change of register

   The shape of that is the point: near-nothing inside the product's own
   presentation, and one consistent ~100px step at the two places the page
   changes what it is doing. Every gap is taken out of BOTH bands that meet,
   because leaving either at full padding still reads as a band of empty black.

   Scope: `.print-frame` and `.buy` are each on exactly the 28 product pages
   and nowhere else, so none of this can reach another page. Deliberately NOT
   keyed on `.band + .band` — most junctions on this site are between different
   surfaces, where the full padding is exactly right. */

/* Title -> photograph. The h1 names the photograph directly below it. */
.band.hero:has(+ .band .print-frame) { padding-block-end: 0; }
.band.hero:has(+ .band .print-frame) + .band { padding-block-start: var(--space-4); }

/* The story belongs to the photograph, so it sits right under it rather than
   a full band of black further down. Two bands meet here, so the gap is
   taken out of both. */
.band:has(.print-frame):has(+ .band > .wrap > .prose--center) { padding-block-end: var(--space-4); }
.band:has(.print-frame) + .band:has(> .wrap > .prose--center) { padding-block-start: 0; }

/* Whatever precedes the sizes and Add to cart. TWO shapes reach the buy block
   and both were wrong by the same amount: the 9 prints with a story run
   photograph -> story -> buy, while the other 19 have no story section at all
   and run photograph -> buy. Keying the second rule on the buy band ITSELF
   rather than on what sits above it covers both, and keeps covering them when
   a story is finally written for one of the 19.

   The buy block wants more air above it than the story wanted below the
   photograph — prose to purchase control is a change of register, not a
   continuation — hence 48+32 rather than the near-nothing above. */
.band:has(+ .band > .wrap > .buy) { padding-block-end: var(--space-12); }
.band:has(> .wrap > .buy) { padding-block-start: var(--space-8); }

/* And out the far side of it, to "custom sizes are quoted individually". The
   same 192px void sat here, below the shipping note, which made the page look
   finished one section before it was. Matched to the story -> sizes step above
   so the page has ONE step size, not a different guess at every junction.

   Keyed on the buy band rather than on `.cta`, because what follows the sizes
   is a CTA on all 28 pages today but need not stay one. */
.band:has(> .wrap > .buy) { padding-block-end: var(--space-12); }
.band:has(> .wrap > .buy) + .band { padding-block-start: var(--space-8); }

/* ── pre-launch audit, 2026-09-16 ───────────────────────────────────────────
   Slideshow dots: the default ring was sand at 55% transparency, which is
   invisible on the cream and sand bands (the home page's review strip showed
   one orange dot and nothing else). Take the ring from the surface's own ink
   instead. `:where()` keeps this at one class, so the dot styles made for
   photographs (.carousel--card, --bleed, --showcase, --solo) still win. */
.cdot:where(:not([aria-current="true"])) {
  border-color: color-mix(in oklab, var(--fg), transparent 45%);
}

/* On a phone the booking band sat directly under the header it repeats: the
   same photograph, title and description twice in a row. Below the tablet
   width the band keeps only what is new (inclusions, price, deposit and
   buttons). The heading stays for screen readers, which navigate by it. */
@media (max-width: 767.98px) {
  .trip-buy .split-media,
  .trip-buy .trip-buy-desc { display: none; }
  .trip-buy .trip-buy-h {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
    margin: 0;
  }
}

/* ── pre-launch audit, 2026-09-16: accessibility fixes ──────────────────────
   The phone menu button shows a cross while the panel is open. Below 420px its
   "Close" word is visually hidden and the panel fills the screen, so a
   hamburger that stayed a hamburger was the only, unlabelled, way out. */
.menu-btn .menu-x { display: none; }
.head.menu-open .menu-btn .menu-lines { display: none; }
.head.menu-open .menu-btn .menu-x { display: inline; }

/* Text on a card's `--surface`, not the band's ground. The night and forest
   tokens are tuned against their own grounds, and inside the calculator and
   the write-in (which sit on the darker `--surface`) the hints, labels and
   warnings measured 3.6:1, 2.4:1 and 3.7:1. Derived from the kit's own inks,
   so they read as the same muted and warning voices, now at 4.8–5.2:1. */
[data-surface="night"] .calc {
  --muted: color-mix(in oklab, var(--fg-2), var(--surface) 12%);
  --danger: color-mix(in oklab, var(--awe-orange), var(--awe-rust) 18%);
}
[data-surface="forest"] .calc {
  --danger: color-mix(in oklab, var(--awe-orange), var(--awe-cream) 5%);
}

/* Next and Back move focus to the new question, and a sent form moves it to
   its thank-you. Those are landmarks for the reader, not controls, so they
   take focus without a ring (the write-in already did this). */
.calc .calc-step h3:focus,
.wi-done:focus { outline: none; }

/* Hero photo caption: --muted on black measured 4.4–4.5:1 where a slide's
   bright corner shows through; the secondary ink is 7.5:1. */
.hero-cap { color: var(--fg-2); }

/* The slideshow hero's dots, pause button and caption were drawn absolutely on
   one bottom edge: on a phone they sat on top of each other on eight of the
   eleven slideshow pages, and from 768 to 1040px a two-line caption ran under
   the call-to-action. They are now one row in the flow under the headline
   block (dots left, caption right), so nothing can land on anything else. The
   copy's bottom padding gives up the row's height, which leaves a one-line
   caption's page exactly where it was. HERO_CAROUSEL_JS holds the caption at
   the height of its longest one, so the headline does not move between slides. */
.hero-carousel {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: 1fr auto;
  align-items: end;
}
.hero-carousel > .hero-copy {
  grid-column: 1 / -1;
  grid-row: 1;
  padding-block-end: var(--space-2);
}
.hero-carousel > .hero-dots {
  position: relative;
  left: auto;
  bottom: auto;
  grid-column: 1;
  grid-row: 2;
  padding: 0 0 var(--space-4) var(--container-gutter-phone);
}
.hero-carousel > .hero-cap {
  position: relative;
  right: auto;
  bottom: auto;
  grid-column: 2;
  grid-row: 2;
  max-width: none;
  margin: 0;
  padding: 0 var(--container-gutter-phone) var(--space-4) var(--space-6);
  text-align: right;
}
@media (min-width: 768px) {
  .hero-carousel > .hero-dots { padding-left: var(--container-gutter-tablet); }
  .hero-carousel > .hero-cap { padding-right: var(--container-gutter-tablet); }
}
@media (min-width: 1040px) {
  .hero-carousel > .hero-dots { padding-left: var(--container-gutter-desktop); }
  .hero-carousel > .hero-cap { padding-right: var(--container-gutter-desktop); }
}
/* A phone has no room for both on one line: the caption goes above the dots,
   and a long run of dots wraps rather than running off the edge. */
@media (max-width: 767.98px) {
  .hero-carousel {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 1fr auto auto;
  }
  .hero-carousel > .hero-copy { padding-block-end: var(--space-3); }
  .hero-carousel > .hero-cap {
    grid-column: 1;
    grid-row: 2;
    margin-bottom: var(--space-3);
    padding: 0 var(--container-gutter-phone);
  }
  .hero-carousel > .hero-dots {
    grid-column: 1;
    grid-row: 3;
    flex-wrap: wrap;
    row-gap: var(--space-4);
    padding: 0 var(--container-gutter-phone) var(--space-4);
  }
  /* A single-photo hero keeps its scroll cue, a hairline at the centre of the
     bottom edge, except where a caption is there to cross it. */
  .hero-full:not(.hero-carousel):has(.hero-cap) .hero-cue { display: none; }
}

/* Text strips — one card, one quote, or a row of reviews: the arrows sat on
   the cards and covered words. Below the width where the one-card strip gets
   its own lane, they move down into the dots row, one at each end. The review
   row gets the same lane above it (client-approved for the one-card strip,
   2026-09-02), since its arrows sat on the neighbouring review's text. */
@media (max-width: 899.98px) {
  :is(.carousel--solo, .carousel--quotes, .carousel--reviews):has(.cdots) .cbtn {
    top: auto;
    bottom: 0;
    transform: none;
  }
  :is(.carousel--solo, .carousel--quotes, .carousel--reviews):has(.cdots) .cbtn:hover { transform: scale(1.06); }
  :is(.carousel--solo, .carousel--quotes, .carousel--reviews):has(.cdots) .cprev { left: 0; }
  :is(.carousel--solo, .carousel--quotes, .carousel--reviews):has(.cdots) .cnext { right: 0; }
  :is(.carousel--solo, .carousel--quotes, .carousel--reviews) .cdots {
    min-height: 48px;
    padding-inline: 60px;
    /* Between the arrows a long run of dots wraps rather than shrinking below
       its 24px tap spacing (eleven reviews did, at 390px). */
    flex-wrap: wrap;
    row-gap: var(--space-4);
  }
  :is(.carousel--solo, .carousel--quotes, .carousel--reviews) .cdots > * { flex-shrink: 0; }
}
@media (max-width: 560px) {
  :is(.carousel--solo, .carousel--quotes, .carousel--reviews) .cdots { min-height: 40px; padding-inline: 52px; }
}
@media (min-width: 900px) {
  .carousel--reviews { padding-inline: 72px; }
  .carousel--reviews .cprev { left: 0; }
  .carousel--reviews .cnext { right: 0; }
}

/* Carousel arrows glide to the next frame; a reader who asked for reduced
   motion gets the jump instead. CAROUSEL_JS asks for the same. */
@media (prefers-reduced-motion: reduce) {
  .ctrack { scroll-behavior: auto; }
}
