/* ==========================================================================
   Red Dunes — components.  Each block below becomes one WordPress pattern.
   ========================================================================== */

/* ---- Buttons ----------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--rd-sp-2);
  min-height: var(--rd-tap); padding: 0 var(--rd-sp-5);
  border: 1px solid transparent; border-radius: var(--rd-radius-pill);
  font-size: var(--rd-step-0); font-weight: 650; letter-spacing: .005em;
  text-decoration: none; max-width: 100%;
  /* wrap rather than force the page wider than the screen */
  white-space: normal; text-align: center; overflow-wrap: anywhere;
  transition: background var(--rd-fast) var(--rd-ease),
              transform var(--rd-fast) var(--rd-ease),
              box-shadow var(--rd-base) var(--rd-ease),
              border-color var(--rd-fast) var(--rd-ease);
}
.btn:active { transform: translateY(0); }

.btn--primary { background: var(--rd-rust); color: #fff; box-shadow: var(--rd-shadow-md); }
.btn--primary:hover { background: var(--rd-rust-700); color: #fff; }

.btn--wa { background: var(--rd-wa); color: #06281A; }
.btn--wa:hover { background: #1FBE5B; color: #06281A; }

.btn--ghost { background: transparent; border-color: var(--rd-line); color: var(--rd-espresso); }
.btn--ghost:hover { border-color: var(--rd-espresso); color: var(--rd-espresso); background: var(--rd-sand); }

.btn--on-dark {
  background: rgba(255,255,255,.1); color: #fff;
  border-color: rgba(255,255,255,.4);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.btn--on-dark:hover { background: rgba(255,255,255,.2); color: #fff; border-color: #fff; }

/* Translucent WhatsApp green, for the hero only. The glass treatment needs the
   dark photo behind it, so this variant does not belong on a light section —
   use .btn--wa there. White label, because the green is only ~25% opaque over a
   dark scrim and a dark label would disappear into it. */
.btn--wa-glass {
  background: rgba(37,211,102,.26); color: #fff;
  border-color: rgba(37,211,102,.62);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.btn--wa-glass:hover {
  background: rgba(37,211,102,.42); color: #fff;
  border-color: rgba(37,211,102,.9);
}

.btn--sm { min-height: 46px; padding: 0 var(--rd-sp-4); font-size: var(--rd-step--1); }
.btn--block { width: 100%; }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--rd-sp-3); }

.icon { width: 1.15em; height: 1.15em; flex: none; fill: none;
        stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.icon--solid { fill: currentColor; stroke: none; }

/* ---- Badges & chips ---------------------------------------------------- */
.badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px var(--rd-sp-3);
  border-radius: var(--rd-radius-pill);
  background: var(--rd-rust-050); color: var(--rd-rust-700);
  font-size: var(--rd-step--1); font-weight: 650;
  letter-spacing: .01em;
}
.badge--neutral { background: var(--rd-sand-200); color: var(--rd-espresso); }
.badge--on-media {
  position: absolute; top: var(--rd-sp-3); left: var(--rd-sp-3);
  background: rgba(255,255,255,.92); color: var(--rd-espresso);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  box-shadow: var(--rd-shadow-sm);
}
.glass-pill {
  display: inline-flex; align-items: center; gap: var(--rd-sp-2);
  padding: var(--rd-sp-2) var(--rd-sp-4);
  border-radius: var(--rd-radius-pill);
  background: rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.3);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  color: #fff; font-size: var(--rd-step--1); font-weight: 600;
  max-width: 100%; flex-wrap: wrap; overflow-wrap: anywhere;
}
.stars { color: var(--rd-star); letter-spacing: .06em; }

/* unit chip — the fix for the competitor's uncomparable prices */
.chip {
  display: inline-block; padding: 3px var(--rd-sp-2);
  border: 1px solid var(--rd-line); border-radius: 6px;
  background: var(--rd-white);
  font-size: 12.5px; font-weight: 600; color: var(--rd-slate-600);
  letter-spacing: .01em; text-transform: lowercase;
}

/* ---- Header ------------------------------------------------------------ */
.topbar {
  background: var(--rd-espresso); color: #EADFD7;
  font-size: var(--rd-step--1);
}
.topbar .wrap { display: flex; flex-wrap: wrap; gap: var(--rd-sp-2) var(--rd-sp-5);
                align-items: center; min-height: 40px; }
/* WCAG 2.2 target size: 24px minimum in the compact bars too. */
.topbar a, .breadcrumb a, .site-footer a, .footer-bottom a {
  display: inline-flex; align-items: center; min-height: 24px;
}
.topbar a { color: #fff; text-decoration: none; font-weight: 600; overflow-wrap: anywhere; }
.topbar a:hover { text-decoration: underline; }
.topbar .spacer { margin-left: auto; }

.site-header {
  position: sticky; top: 0; z-index: 100;
  background: rgba(254,253,253,.9);
  -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3);
  border-bottom: 1px solid var(--rd-line-soft);
}

/* ---- Floating header over a photo hero ---------------------------------
   The look is lifted from extremeadventuresdubai: the hero runs up behind a
   frosted bar that floats clear of the page edges. Theirs scrolls away; ours
   stays sticky and returns to the plain solid bar once the hero is behind it,
   so the nav and the WhatsApp CTA never leave. Only a page with a .hero gets
   it, and only at >=64em, where the nav is one line and 96px is its real
   height — below that the bar can wrap and the hero offset would be wrong. */
@media (min-width: 64em) {
  body:has(.hero) .site-header {
    background: transparent; border-bottom-color: transparent;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    transition: background var(--rd-base) var(--rd-ease),
                border-color var(--rd-base) var(--rd-ease);
  }
  body:has(.hero) .site-header .header-bar {
    margin-block: var(--rd-sp-3); padding-inline: var(--rd-sp-5);
    border-radius: 20px;
    background: rgba(254,253,253,.76);
    border: 1px solid rgba(255,255,255,.5);
    -webkit-backdrop-filter: blur(18px) saturate(1.5); backdrop-filter: blur(18px) saturate(1.5);
    box-shadow: 0 12px 34px rgba(30,18,14,.22);
    transition: margin var(--rd-base) var(--rd-ease), padding var(--rd-base) var(--rd-ease),
                border-radius var(--rd-base) var(--rd-ease), background var(--rd-base) var(--rd-ease),
                box-shadow var(--rd-base) var(--rd-ease), border-color var(--rd-base) var(--rd-ease);
  }
  /* How far the hero is pulled up so it runs behind the floating bar.
     THE OLD VALUE WAS 96px AND IT WAS 3px SHORT. The comment here used to read
     "72px bar + its 12px margins", which counted the bar's `min-height` and its
     margins but NOT the three borders in the stack: the floating bar carries
     `border: 1px` (so it measures 74px, not 72) and `.site-header` carries a 1px
     bottom border. 72 + 1 + 1 + 12 + 12 + 1 = 99.
     The 3px sliver this left was invisible while the hero was the first thing on
     the page - it was 3px of cream page background at the very top of a cream page.
     The 11 Nov utility strip put a rust edge directly above it and it became a
     visible gap between the two bars. Measured on live, not derived: with 99px the
     hero's top lands exactly on the strip's bottom edge.
     ONE constant, used by both rules, so they cannot drift apart again. */
  body:has(.hero) { --rd-float-header: 99px; }
  body:has(.hero) .hero { margin-top: calc(var(--rd-float-header) * -1); }
  body:has(.hero) .hero__inner { padding-top: calc(var(--rd-float-header) + clamp(40px, 9vw, 88px)); }

  body:has(.hero) .site-header[data-stuck="true"] {
    background: rgba(254,253,253,.9); border-bottom-color: var(--rd-line-soft);
    -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3);
  }
  body:has(.hero) .site-header[data-stuck="true"] .header-bar {
    margin-block: 0; padding-inline: 0; border-radius: 0;
    background: transparent; border-color: transparent; box-shadow: none;
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .site-header, .site-header .header-bar { transition: none; }
}
.site-header .header-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--rd-sp-3) var(--rd-sp-4);
  min-height: 72px;
}
/* The mobile drawer is a flex item of `.header-bar`, so opening it makes the bar a
   TWO-ROW flex container - and `min-height` belongs to the CONTAINER, not to a row.
   Closed, the 72px padded out the single row and the 44px logo sat at top 14; open,
   the taller panel satisfied the container on its own and row one collapsed to its
   natural 48px, moving the logo and the burger UP BY 12px every time the menu
   opened. Measured, not guessed: logo top 14 -> 2.
   Pinning the logo to the bar's own min-height keeps row one at 72px whatever else
   joins the bar. The logo is `display: flex; align-items: center`, so the image
   stays centred and the closed state is unchanged. */
.site-header .header-bar > .logo { min-height: 72px; }
.logo { display: flex; align-items: center; gap: 10px; min-height: 44px; text-decoration: none;
        margin-right: auto; min-width: 0; }
.logo__mark { width: 38px; height: 38px; flex: none; }
/* Real brand lockup (Logo-2.png from the live site, trimmed + resized 2314x787 -> 690x220).
   height wins over base.css `img { height: auto }` on specificity, so width stays auto. */
.logo__img { height: 44px; width: auto; flex: none; }
@media (max-width: 29.99em) { .logo__img { height: 38px; } }
.logo__img--footer { height: 58px; margin-bottom: 12px; }
.logo__text { display: grid; line-height: 1.05; }
.logo__name {
  font-size: var(--rd-step-0); font-weight: 800; color: var(--rd-espresso);
  letter-spacing: .1em; text-transform: uppercase;
}
.logo__sub { font-size: 12px; font-weight: 600; color: var(--rd-slate-400); letter-spacing: .14em; text-transform: uppercase; }

.nav { display: none; }
.nav { min-width: 0; }
.nav > ul { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end;
            gap: var(--rd-sp-1) var(--rd-sp-2); list-style: none; margin: 0; padding: 0; }
.nav > ul > li > a {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 var(--rd-sp-3); border-radius: var(--rd-radius-pill);
  color: var(--rd-slate); font-size: var(--rd-step--1); font-weight: 600;
  text-decoration: none;
}
.nav > ul > li > a:hover { background: var(--rd-sand); color: var(--rd-espresso); }
.nav > ul > li > a[aria-current="page"] { color: var(--rd-rust-700); background: var(--rd-rust-050); }
.header-cta { display: none; }

/* Icon-only since 4 Nov 2026 (was the word "Menu"). Square and 48px so the tap
   target survives losing the label; the accessible name moved to aria-label,
   which ui.js swaps to "Close menu" when the drawer is open. */
.nav-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; padding: 0;
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: var(--rd-radius-pill);
  color: var(--rd-espresso);
}
.nav-toggle .icon { width: 24px; height: 24px; stroke-width: 2; }
/* SCOPED with `.nav-toggle` on purpose - a bare `.nav-toggle__close` is (0,1,0) and
   TIES with `.icon`, which `blocks.css` sets to `display: inline-block` and which
   loads LAST, so the tie went to blocks.css and the burger and the cross painted on
   top of each other on every phone. Scoping makes these (0,2,0) and the outcome
   independent of file order. Fifth time this trap has bitten here: a component rule
   that can be overridden by a bare utility in a later file is not safe. */
.nav-toggle .nav-toggle__close { display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle__open { display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle__close { display: inline-block; }
.nav-toggle[aria-expanded="true"] { background: var(--rd-sand); }
.nav-panel {
  display: none; border-top: 1px solid var(--rd-line-soft);
  background: var(--rd-page); padding-block: var(--rd-sp-4);
}
.nav-panel[data-open="true"] {
  /* Stays in normal flow — see ui.js: .site-header has a backdrop-filter, which
     makes it the containing block for any fixed child, so a fixed sheet would be
     trapped inside the header. ui.js caps max-height to the space actually left
     below it; these let the capped box scroll instead of clipping, and stop the
     scroll chaining to the locked page behind. */
  display: block;
  overflow-y: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* The drawer runs to the bottom of the screen, so its last item would sit
     behind the fixed action bar (64px) — scroll it clear, as body does. */
  padding-bottom: calc(var(--rd-actionbar-h, 76px) + var(--rd-sp-4) + env(safe-area-inset-bottom, 0px));
}
/* The top bar is 4 stacked rows on a phone and ate ~230px of the drawer's
   height — on a landscape phone that left 74px of menu. It is decoration while
   the menu is open, and the menu carries its own phone and WhatsApp links. */
body.nav-locked .topbar { display: none; }
/* Scroll lock while the drawer is open. position:fixed, not overflow:hidden —
   iOS Safari ignores overflow:hidden on body. ui.js restores scrollY. */
body.nav-locked { position: fixed; left: 0; right: 0; width: 100%; overflow-y: scroll; }
.nav-panel ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.nav-panel a {
  display: flex; align-items: center; min-height: var(--rd-tap);
  padding: 0 var(--rd-sp-3); border-radius: var(--rd-radius-sm);
  color: var(--rd-espresso); font-weight: 600; text-decoration: none;
}
.nav-panel a:hover { background: var(--rd-sand); }

/* em, not px: this queries the browser's own font size, so a reader running a
   larger default text size gets the drawer instead of a nav that overruns. */
@media (min-width: 64em) {
  .nav { display: block; }
  .header-cta { display: inline-flex; }
  .nav-toggle { display: none; }
  .nav-panel { display: none !important; }

  /* Nav sits on the RIGHT, beside the WhatsApp button (11 Nov 2026, user's call).
     This REVERSES the 9 Nov centring - do not "restore" it. The whole mechanism is
     `.logo { margin-right: auto }`: the logo takes its natural width and the auto
     margin eats every pixel of slack, so the nav and the CTA are pushed hard right
     and sit together as one group separated by the bar's own column gap.
     That same `margin-right: auto` is what puts the burger on the right edge BELOW
     this breakpoint, so here it is simply left alone rather than cancelled - which is
     why this block is now shorter than the centred version it replaces.
     Scoped `.site-header .logo` (0,2,0) because the bare `.logo` rule is (0,1,0) -
     the specificity trap this handbook lists four times. `flex: 0 1 auto` + the
     inherited `min-width: 0` let the nav SHRINK rather than wrap the bar onto a
     second row when the viewport is tight. */
  .site-header .logo { flex: 0 0 auto; }
  .site-header .nav { flex: 0 1 auto; }
  .site-header .nav > ul { justify-content: flex-end; }
  .site-header .header-cta { flex: 0 0 auto; }
}

/* ---- Hero -------------------------------------------------------------- */
.hero { position: relative; isolation: isolate; background: var(--rd-night); }
.hero__media { position: absolute; inset: 0; z-index: -2; }
.hero__media img { width: 100%; height: 100%; object-fit: cover; }
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(100deg, rgba(30,18,14,.9) 0%, rgba(30,18,14,.72) 38%,
                    rgba(30,18,14,.34) 68%, rgba(30,18,14,.1) 100%),
    linear-gradient(to top, rgba(30,18,14,.6) 0%, transparent 42%);
}
.hero__inner {
  display: grid; grid-template-columns: minmax(0, 1fr);
  align-content: end; gap: var(--rd-sp-5);
  min-height: min(78svh, 680px);
  padding-block: clamp(40px, 9vw, 88px);
}
.hero__copy { max-width: min(42rem, 100%); }
/* the rating pill had no margin of its own, so it sat on the cap-height of the h1 */
.hero .glass-pill { margin-bottom: var(--rd-sp-4); }
.hero h1 { color: #fff; }
/* The right of the hero was empty photo. These four tiles carry the same trips the
   live site's hero carousel does, without its collage artwork. Desktop only: below
   64em the copy already fills the width and the tours section follows immediately. */
/* Hero enquiry card (4 Nov 2026) --------------------------------------------
   Replaced the four trip tiles. On desktop it owns the hero's right column; on
   a phone it stacks under the copy, i.e. BELOW the fold, which is where
   extremeadventuresdubai.com puts theirs too — a form occupying a phone's first
   screen buries the headline that earns the enquiry.
   The .hero-tile rules below are kept: markup is in ../attic/hero-tiles.html if
   the tiles are ever wanted back. */
.hero-form {
  margin-top: var(--rd-sp-6);
  background: var(--rd-page); color: var(--rd-slate);
  border-radius: var(--rd-radius-card);
  padding: var(--rd-sp-4);
  box-shadow: 0 18px 50px rgba(30,18,14,.4);
  display: grid; gap: var(--rd-sp-3);
}
.hero-form .field { display: grid; gap: 4px; }
.hero-form .form-note { font-size: 12.5px; margin-top: -2px; }
/* The card is bottom-aligned with the hero copy, so any height ABOVE the copy's
   own height hangs over the rating pill and stretches the whole hero — it was
   652px against the copy's 442px, putting the hero 247px past a 1024x768 screen.
   Everything here exists to keep the card inside that budget. 46px inputs stay
   over the 44px minimum; the 50px --rd-tap is kept for the submit button. */
.hero-form h2 { font-size: var(--rd-step-2); color: var(--rd-espresso); }
.hero-form__sub { font-size: var(--rd-step--1); color: var(--rd-slate-600); margin-top: -4px; }
@media (min-width: 64em) {
  .hero-form { gap: var(--rd-sp-2); padding: var(--rd-sp-4) var(--rd-sp-4) var(--rd-sp-3); }
  /* Same checkbox exclusion as the base `.field` rule above - a bare `input`
     selector here would put the 46px min-height straight back on it. */
  .hero-form .field input:not([type="checkbox"]),
  .hero-form .field select { min-height: 46px; }
  .hero-form .field > label { font-size: 13px; }
  /* Hidden on DESKTOP only. There the card is bottom-aligned to the hero copy,
     so every extra line pushes its top up over the rating pill and stretches
     the hero; the hero headline already says what the page is. On a phone the
     card stands on its own below the copy and needs the heading, and there is
     vertical room for it. The form keeps aria-label, so its accessible name
     survives either way. */
  .hero-form h2, .hero-form__sub, .hero-form .field__hint { display: none; }
}
/* Honeypot. display:none, NOT an off-screen -9999px box: the off-screen version
   registers as real page overflow and as a 1px clipped element in
   check-responsive.mjs. A display:none input is still submitted (only `disabled`
   stops that), which is exactly what a honeypot needs. */
.hp { display: none; }

.hero__tiles { display: none; }
.hero-tile {
  position: relative; display: block; overflow: hidden;
  aspect-ratio: 4 / 3; border-radius: var(--rd-radius-card);
  border: 1px solid rgba(255,255,255,.22); text-decoration: none;
  box-shadow: 0 10px 30px rgba(30,18,14,.35);
}
.hero-tile img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--rd-base) var(--rd-ease); }
.hero-tile span {
  position: absolute; inset: auto 0 0 0; padding: 28px var(--rd-sp-4) var(--rd-sp-3);
  background: linear-gradient(to top, rgba(30,18,14,.92), rgba(30,18,14,.55) 55%, transparent);
  color: #fff; font-size: var(--rd-step--1); font-weight: 650;
}
.hero-tile:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
@media (hover: hover) {
  .hero-tile:hover img { transform: scale(1.06); }
  .hero-tile:hover { border-color: rgba(255,255,255,.55); }
}
@media (min-width: 64em) {
  .hero__inner { grid-template-columns: minmax(0, 1fr) clamp(300px, 27vw, 392px);
                 column-gap: var(--rd-sp-7); align-items: end; }
  .hero__copy  { grid-column: 1; }
  .hero__tiles { display: grid; grid-column: 2; grid-template-columns: repeat(2, 1fr);
                 gap: var(--rd-sp-3); align-self: end; }
  /* stretch, not end: the card then matches the copy column's exact height, so
     its top edge lines up with the rating pill and its bottom with the price
     line. space-between puts the slack into the gaps between fields rather than
     leaving a block of empty card at one end. */
  .hero-form { grid-column: 2; align-self: stretch; align-content: space-between;
               margin-top: 0; max-width: 26rem; justify-self: end; }
  .trust { grid-column: 1 / -1; }
}
.hero__lead {
  margin-top: var(--rd-sp-4);
  color: #F0E4DC; font-size: var(--rd-step-1); max-width: min(34rem, 100%);
}
.hero .btn-row { margin-top: var(--rd-sp-5); }
.hero__price {
  margin-top: var(--rd-sp-4);
  color: #EADFD7; font-size: var(--rd-step--1);
}
.hero__price strong { color: #fff; font-size: var(--rd-step-0); }
.hero__price .ph, .hero__lead .ph { border-bottom-color: rgba(255,255,255,.5); }

/* trust row — 3 verifiable items, not a logo pile */
.trust {
  display: grid; gap: var(--rd-sp-3);
  grid-template-columns: 1fr;
  padding-top: var(--rd-sp-5);
  border-top: 1px solid rgba(255,255,255,.18);
}
.trust li {
  display: flex; align-items: center; gap: var(--rd-sp-2);
  color: #F0E4DC; font-size: var(--rd-step--1); font-weight: 600;
}
.trust .icon { color: var(--rd-wa); }
@media (min-width: 40em) { .trust { grid-template-columns: repeat(3, auto); justify-content: start; gap: var(--rd-sp-6); } }

/* ---- Family cards (3 product families up front) ------------------------ */
.families { display: grid; gap: var(--rd-sp-4); grid-template-columns: 1fr; }
/* auto-fit, not repeat(3): the third card (vehicle hire) was removed 10 Nov 2026 and a
   hardcoded 3 left two cards sitting in two thirds of the row with a hole beside them.
   auto-fit lets the row hold whatever cards the pattern has - same reasoning as the
   count-free headings. */
@media (min-width: 47.5em) { .families { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); } }
.family {
  position: relative; overflow: hidden;
  border-radius: var(--rd-radius-card);
  min-height: 220px; display: grid; align-content: end;
  padding: var(--rd-sp-5); color: #fff; text-decoration: none;
  isolation: isolate;
}
.family img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -2;
               transition: transform 400ms var(--rd-ease); }
.family::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to top, rgba(30,18,14,.88) 0%, rgba(30,18,14,.45) 50%, rgba(30,18,14,.12) 100%);
}
.family h3 { color: #fff; }
.family p { color: #EADFD7; font-size: var(--rd-step--1); margin-top: 4px; }
.family__from { margin-top: var(--rd-sp-3); font-size: var(--rd-step--1); font-weight: 650; color: #fff; }
.family__arrow { display: inline-block; margin-left: 6px; transition: transform var(--rd-base) var(--rd-ease); }

/* ---- Tour card --------------------------------------------------------- */
.tour-grid { display: grid; gap: var(--rd-sp-5); grid-template-columns: 1fr; }
@media (min-width: 40em)  { .tour-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64em) { .tour-grid { grid-template-columns: repeat(3, 1fr); } }

.card {
  display: flex; flex-direction: column;
  background: var(--rd-white);
  border: 1px solid var(--rd-line-soft);
  border-radius: var(--rd-radius-card);
  overflow: hidden;
  box-shadow: var(--rd-shadow-sm);
  transition: box-shadow var(--rd-base) var(--rd-ease), transform var(--rd-base) var(--rd-ease),
              border-color var(--rd-base) var(--rd-ease);
}
/* flex-item minimum: without min-height:0 the image's own intrinsic ratio becomes the
   box's min-content height and quietly overrides aspect-ratio, so a row of cards ends up
   with media boxes of three different heights. */
.card__media { position: relative; aspect-ratio: 4 / 3; overflow: hidden; background: var(--rd-sand-200);
               flex: 0 0 auto; min-height: 0; }
.card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform 400ms var(--rd-ease); }
.card__body { display: flex; flex-direction: column; flex: 1; padding: var(--rd-sp-4) var(--rd-sp-4) var(--rd-sp-5); gap: var(--rd-sp-3); }
.card__title {
  font-size: var(--rd-step-2);
  /* two lines reserved: titles of different length must not shunt the body text
     up and down across a row of cards. */
  min-height: calc(2em * 1.25);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.card__title a { color: inherit; text-decoration: none; }
.card__title a:hover { color: var(--rd-rust); }
.card__meta {
  display: flex; flex-wrap: wrap; gap: var(--rd-sp-2) var(--rd-sp-4);
  color: var(--rd-slate-600); font-size: var(--rd-step--1);
}
.card__meta span { display: inline-flex; align-items: center; gap: 6px; }
.card__meta .icon { color: var(--rd-slate-400); }
.card__text {
  color: var(--rd-slate-600); font-size: var(--rd-step--1);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}

/* fixed price slot: identical position and shape on every card */
.price-slot {
  margin-top: auto; padding-top: var(--rd-sp-4);
  border-top: 1px dashed var(--rd-line);
  display: grid; gap: var(--rd-sp-3);
  grid-template-columns: 1fr auto; align-items: end;
}
.price { display: grid; gap: 2px; }
/* Client request (3 Nov 2026): a WhatsApp "ask for a discount" button on every
   excursion. It sits under the price row and spans the card, so it reads as a
   second action rather than competing with Details. */
.card__wa { display: flex; width: 100%; margin-top: var(--rd-sp-3); }
.card__wa .icon { flex: none; }
.featured .card__wa { max-width: 320px; }

.price__label { font-size: 12.5px; font-weight: 650; letter-spacing: .07em; text-transform: uppercase; color: var(--rd-slate-400); }
.price__value {
  display: flex; align-items: baseline; gap: var(--rd-sp-2); flex-wrap: wrap;
  font-size: var(--rd-step-2); font-weight: 750; color: var(--rd-espresso);
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
}
.price__pp { font-size: 12.5px; color: var(--rd-slate-600); font-weight: 500; }

/* ---- Featured tour (editorial hierarchy, not another equal card) ------- */
.featured {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--rd-sp-5);
  background: var(--rd-white); border: 1px solid var(--rd-line-soft);
  border-radius: var(--rd-radius-card); overflow: hidden;
  box-shadow: var(--rd-shadow-md);
}
@media (min-width: 56.25em) { .featured { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 0; } }
.featured__media { position: relative; aspect-ratio: 4 / 3; }
@media (min-width: 56.25em) { .featured__media { aspect-ratio: auto; height: 100%; min-height: 420px; } }
.featured__media img { width: 100%; height: 100%; object-fit: cover; }
.featured__body { padding: clamp(20px, 3vw, 40px); display: grid; align-content: center; gap: var(--rd-sp-4); }
.featured h3 { font-size: var(--rd-step-3); }
.highlights { display: grid; gap: var(--rd-sp-2); list-style: none; padding: 0; margin: 0; }
.highlights li { display: flex; gap: var(--rd-sp-3); font-size: var(--rd-step-0); }
.highlights .icon { color: var(--rd-rust); margin-top: 4px; }

/* ---- Steps ------------------------------------------------------------- */
.steps { display: grid; gap: var(--rd-sp-5); grid-template-columns: 1fr; counter-reset: step; }
@media (min-width: 40em)  { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64em) { .steps { grid-template-columns: repeat(4, 1fr); } }
.step { counter-increment: step; }
.step::before {
  content: counter(step);
  display: grid; place-items: center;
  width: 44px; height: 44px; margin-bottom: var(--rd-sp-3);
  border-radius: var(--rd-radius-pill);
  background: var(--rd-rust-050); color: var(--rd-rust-700);
  font-size: var(--rd-step-1); font-weight: 750;
}
.step h3 { font-size: var(--rd-step-1); margin-bottom: 6px; }
.step p { color: var(--rd-slate-600); font-size: var(--rd-step--1); }

/* ---- Reviews ----------------------------------------------------------- */
.rating-summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--rd-sp-4) var(--rd-sp-6);
  padding: var(--rd-sp-5); margin-bottom: var(--rd-sp-6);
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: var(--rd-radius-card);
}
.rating-summary__score { display: grid; gap: 2px; }
.rating-summary__score b { font-size: var(--rd-step-4); line-height: 1; color: var(--rd-espresso); font-variant-numeric: tabular-nums; }
.review-grid { display: grid; gap: var(--rd-sp-4); grid-template-columns: 1fr; }
@media (min-width: 47.5em) { .review-grid { grid-template-columns: repeat(3, 1fr); } }
.review {
  display: grid; gap: var(--rd-sp-3); align-content: start;
  padding: var(--rd-sp-5);
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: var(--rd-radius-card);
}
.review__head { display: flex; align-items: center; gap: var(--rd-sp-3); }
.review__avatar {
  display: grid; place-items: center; flex: none;
  width: 42px; height: 42px; border-radius: 50%;
  background: var(--rd-sand-200); color: var(--rd-espresso);
  font-weight: 700; font-size: var(--rd-step--1);
}
.review__who { display: grid; }
.review__who b { font-size: var(--rd-step-0); color: var(--rd-espresso); }
.review__who span { font-size: 12.5px; color: var(--rd-slate-400); }
.review p { font-size: var(--rd-step--1); color: var(--rd-slate); }
.review__reply {
  padding: var(--rd-sp-3) var(--rd-sp-4); border-radius: var(--rd-radius-sm);
  background: var(--rd-sand); font-size: 13.5px; color: var(--rd-slate-600);
  border-left: 3px solid var(--rd-rust);
}

/* ---- FAQ — <details>, works with JS off, all answers crawlable --------- */
.faq { border-top: 1px solid var(--rd-line); }
.faq details { border-bottom: 1px solid var(--rd-line); }
.faq summary {
  display: flex; align-items: center; justify-content: space-between; gap: var(--rd-sp-4);
  min-height: var(--rd-tap); padding: var(--rd-sp-4) 0;
  cursor: pointer; list-style: none;
  font-size: var(--rd-step-1); font-weight: 650; color: var(--rd-espresso);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ""; flex: none; width: 13px; height: 13px;
  background: currentColor; color: var(--rd-rust);
  clip-path: polygon(44% 0, 56% 0, 56% 44%, 100% 44%, 100% 56%, 56% 56%, 56% 100%, 44% 100%, 44% 56%, 0 56%, 0 44%, 44% 44%);
  transition: transform var(--rd-base) var(--rd-ease);
}
.faq details[open] summary::after { transform: rotate(45deg); }
.faq summary:hover { color: var(--rd-rust); }
.faq__answer { padding-bottom: var(--rd-sp-5); max-width: 68ch; color: var(--rd-slate-600); }
.faq__answer > * + * { margin-top: var(--rd-sp-3); }

/* ---- Enquiry form (placed AFTER the product, not before it) ------------ */
.enquiry {
  display: grid; gap: var(--rd-sp-6);
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: var(--rd-radius-card); padding: clamp(20px, 3vw, 40px);
  box-shadow: var(--rd-shadow-md);
}
@media (min-width: 56.25em) { .enquiry { grid-template-columns: 1fr 1.15fr; gap: var(--rd-sp-8); align-items: start; } }
.field { display: grid; gap: 6px; min-width: 0; }
.field > label { font-size: var(--rd-step--1); font-weight: 650; color: var(--rd-espresso); }
/* Checkboxes and radios are excluded, and the reason is subtle: `min-height` beats
   `height`, always - so `.field--consent input[type=checkbox] { height: 18px }`
   further down could never win against `min-height: var(--rd-tap)` here, and every
   consent box on the site rendered 18px wide by 50px tall, sitting low against its
   own one-line label. It takes the padding, border and background off them too. */
.field input:not([type="checkbox"]):not([type="radio"]),
.field select, .field textarea {
  min-height: var(--rd-tap); padding: 0 var(--rd-sp-4);
  width: 100%; min-width: 0;   /* date/number inputs otherwise refuse to shrink */
  background: var(--rd-page);
  border: 1px solid var(--rd-line); border-radius: var(--rd-radius-sm);
  transition: border-color var(--rd-fast) var(--rd-ease), box-shadow var(--rd-fast) var(--rd-ease);
}
.field textarea { min-height: 96px; padding: var(--rd-sp-3) var(--rd-sp-4); resize: vertical; line-height: 1.5; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--rd-rust);
  box-shadow: 0 0 0 3px var(--rd-rust-050);
}
.field__hint { font-size: 12.5px; color: var(--rd-slate-400); }
.form-grid { display: grid; gap: var(--rd-sp-4); min-width: 0; }
.enquiry > * { min-width: 0; }
@media (min-width: 35em) { .form-grid--2 { grid-template-columns: 1fr 1fr; } }
.form-note { font-size: 12.5px; color: var(--rd-slate-400); }

/* ---- Quick facts (single-tour page) ------------------------------------ */
/* minmax(0,1fr) throughout: a bare `1fr` leaves the track at min-width:auto and the
   column will not shrink below its longest word, which pushes the page sideways at a
   large browser font. Same trap as .strip and .tour-layout. */
.facts { display: grid; gap: var(--rd-sp-3); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 47.5em) { .facts { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.fact {
  display: grid; gap: 4px; align-content: start; padding: var(--rd-sp-4);
  background: var(--rd-sand); border-radius: var(--rd-radius-card);
}
.fact > * { min-width: 0; }
.fact b { text-wrap: balance; }
.fact__label { display: flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 650;
               letter-spacing: .06em; text-transform: uppercase; color: var(--rd-slate-400); }
.fact__label .icon { color: var(--rd-rust); }
.fact b { font-size: var(--rd-step-0); color: var(--rd-espresso); }

/* ---- Inclusions / exclusions ------------------------------------------- */
.incl { display: grid; gap: var(--rd-sp-5); }
@media (min-width: 47.5em) { .incl { grid-template-columns: 1fr 1fr; gap: var(--rd-sp-7); } }
.incl h3 { font-size: var(--rd-step-1); margin-bottom: var(--rd-sp-3); }
.incl ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--rd-sp-3); }
/* HANGING INDENT, not flex. A flex row works only while everything after the icon
   is ONE text run: an item that mixes a text node with a <span> - a `ph` marker, a
   price, a note - turns each into its own flex item, and on a narrow column they
   shrink into two ragged columns instead of one sentence. A block with a negative
   first-line indent takes any number of inline children and still lines wrapped
   text up under the first word. */
.incl li {
  display: block; font-size: var(--rd-step-0);
  padding-left: calc(1.15em + var(--rd-sp-3));
  text-indent: calc((1.15em + var(--rd-sp-3)) * -1);
}
.incl li > .icon { margin-right: var(--rd-sp-3); }
.incl--in .icon { color: var(--rd-wa-700); }
.incl--out { color: var(--rd-slate-600); }
.incl--out .icon { color: var(--rd-slate-400); }

/* A ticked list OUTSIDE the two-column `.incl` grid - same row treatment, no
   markers, and the icon on its own column so a wrapped line clears it. Without a
   class, a core/list with an icon in each item keeps its bullets AND puts the icon
   on a line of its own, because `base.css` resets svg to `display: block`. */
.ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--rd-sp-3); }
.ticks li {
  display: block; font-size: var(--rd-step-0);
  padding-left: calc(1.15em + var(--rd-sp-3));
  text-indent: calc((1.15em + var(--rd-sp-3)) * -1);
}
.ticks li > .icon { color: var(--rd-wa-700); margin-right: var(--rd-sp-3); }

/* ---- Single-tour layout + booking rail -------------------------------- */
/* The single-column track MUST be declared. With no grid-template-columns at all the
   implicit track is `auto`, which sizes to content and can exceed the container - the
   >=64em rules below already say minmax(0,1fr), the base case just never did. At 390px
   with a 32px browser font the track computed to 422px inside a 358px box and the tour
   page scrolled sideways. Only showed up once a tour had a full photo gallery
   (jet ski + yacht, 12 Nov 2026); the 1-2 image tours never pushed it wide enough. */
.tour-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--rd-sp-7); align-items: start; }
@media (min-width: 64em) { .tour-layout { grid-template-columns: minmax(0,1fr) 352px; gap: var(--rd-sp-8); } }
.tour-main > section + section {
  margin-top: var(--rd-sp-7); padding-top: var(--rd-sp-7);
  border-top: 1px solid var(--rd-line-soft);
}
.tour-main h2 { font-size: var(--rd-step-3); margin-bottom: var(--rd-sp-5); }

.booking { display: none; }
@media (min-width: 64em) {
  .booking {
    display: grid; gap: var(--rd-sp-4);
    position: sticky; top: 96px;
    padding: var(--rd-sp-5);
    background: var(--rd-white); border: 1px solid var(--rd-line);
    border-radius: var(--rd-radius-card); box-shadow: var(--rd-shadow-md);
  }
}
.booking__price { display: grid; gap: 2px; padding-bottom: var(--rd-sp-4); border-bottom: 1px dashed var(--rd-line); }
.booking__price .price__value { font-size: var(--rd-step-3); }
.booking__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--rd-sp-2); font-size: var(--rd-step--1); color: var(--rd-slate-600); }
.booking__list li { display: flex; gap: var(--rd-sp-2); }
.booking__list .icon { color: var(--rd-wa-700); flex: none; margin-top: 3px; }

.breadcrumb { font-size: var(--rd-step--1); color: var(--rd-slate-400); padding-block: var(--rd-sp-4); }
.breadcrumb ol { list-style: none; display: flex; flex-wrap: wrap; gap: var(--rd-sp-2); margin: 0; padding: 0; }
.breadcrumb li + li::before { content: "/"; margin-right: var(--rd-sp-2); color: var(--rd-line); }
.breadcrumb a { color: var(--rd-slate-600); text-decoration: none; }
.breadcrumb a:hover { color: var(--rd-rust); text-decoration: underline; }

/* The 3-up tour gallery. LIVE. Its photos cross-fade - see the rotation rules added
   at the end of this file. `.gallery-strip` on the homepage is a DIFFERENT component. */
/* minmax(0, 1fr), NOT a bare 1fr: a bare `1fr` leaves the track at min-width:auto, so
   the column refuses to shrink below its content's min-content width and the whole
   page scrolls sideways. Surfaced at 390px with a 32px browser font the day the first
   tours with a full photo set went up (jet ski + yacht, 12 Nov 2026). */
.gallery { display: grid; gap: var(--rd-sp-2); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 47.5em) { .gallery { grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr; } }
.gallery figure { position: relative; margin: 0; overflow: hidden; border-radius: var(--rd-radius-card); background: var(--rd-sand-200); }
.gallery figure:first-child { aspect-ratio: 16 / 10; }
@media (min-width: 47.5em) { .gallery figure:first-child { grid-row: span 2; aspect-ratio: auto; } }
.gallery figure:not(:first-child) { aspect-ratio: 16 / 10; }
.gallery img { width: 100%; height: 100%; object-fit: cover; }
.gallery__more {
  position: absolute; right: var(--rd-sp-3); bottom: var(--rd-sp-3);
  background: rgba(30,18,14,.72); color: #fff;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  padding: 8px var(--rd-sp-3); border-radius: var(--rd-radius-pill);
  font-size: 12.5px; font-weight: 650; text-decoration: none;
}

/* ---- Closing CTA ------------------------------------------------------- */
.cta-band {
  position: relative; isolation: isolate; overflow: hidden;
  border-radius: var(--rd-radius-card);
  padding: clamp(32px, 5vw, 64px);
  background: var(--rd-espresso); color: #fff;
}
.cta-band::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: radial-gradient(120% 90% at 85% 15%, rgba(213,51,7,.55) 0%, transparent 60%);
}
.cta-band h2 { color: #fff; max-width: 26ch; }
.cta-band p { color: #EADFD7; margin-top: var(--rd-sp-3); max-width: 46ch; }
/* message on one side, the actions gathered on the other: a stacked panel with
   two pills under a headline is the default every tour site arrives at, and our
   nearest competitor is already sitting on it. */
.cta-band { display: grid; gap: var(--rd-sp-6); }
@media (min-width: 56.25em) {
  .cta-band { grid-template-columns: minmax(0, 1.2fr) minmax(260px, auto); align-items: center;
              gap: var(--rd-sp-8); }
}
.cta-band__actions { display: grid; gap: var(--rd-sp-3); align-content: center; min-width: 0; }
.cta-band > *, .cta-band__actions > * { min-width: 0; }
/* .btn is nowrap by design; at 320px "Message us on WhatsApp" is wider than the
   padded band, so inside this panel the label is allowed to wrap instead */
.cta-band__actions .btn {
  width: 100%; white-space: normal; text-align: center;
  padding-inline: var(--rd-sp-4); line-height: 1.25;
}
.cta-band__call { color: #EADFD7; font-size: var(--rd-step--1); text-align: center;
                  text-wrap: balance; overflow-wrap: break-word; }
.cta-band__call a { color: #fff; text-decoration-thickness: 1px; }
.cta-band__call a:hover { color: #fff; }

.contact-card__h { font-size: var(--rd-step-2); margin-bottom: var(--rd-sp-3); }

/* ---- Footer ------------------------------------------------------------ */
.site-footer { background: var(--rd-espresso); color: var(--rd-on-dark); padding-block: var(--rd-sp-8) var(--rd-sp-5); font-size: var(--rd-step--1); }
.footer-grid { display: grid; gap: var(--rd-sp-6); grid-template-columns: 1fr; }
@media (min-width: 47.5em)  { .footer-grid { grid-template-columns: 1.4fr 1fr 1fr; } }
/* `repeat(3, …) 1.2fr` hardcoded FIVE tracks - brand, three tour-type columns and
   contact - which was right when it was written. reddunes_render_footer_nav() skips a
   column whose type has no PUBLISHED tours, so with vehicle hire withdrawn and 24 tours
   still drafts only three children render, and two tracks (326px) sat empty on the
   right. Same shape as the "a query loop going empty does not remove its section"
   gotcha, one level up: the GRID was the thing counting. auto-fit collapses the tracks
   nothing fills, and grows back by itself when a tour is republished. */
@media (min-width: 64em) { .footer-grid { grid-template-columns: 1.5fr repeat(auto-fit, minmax(150px, .85fr)); } }
.site-footer h3 { color: #fff; font-size: var(--rd-step-0); margin-bottom: var(--rd-sp-3); letter-spacing: .04em; text-transform: uppercase; }
.site-footer a { color: var(--rd-on-dark); text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--rd-sp-2); }
.site-footer .logo__name, .site-footer .logo__sub { color: #fff; }
.footer-bottom {
  display: flex; flex-wrap: wrap; gap: var(--rd-sp-3) var(--rd-sp-5);
  margin-top: var(--rd-sp-7); padding-top: var(--rd-sp-4);
  border-top: 1px solid rgba(255,255,255,.14);
  color: var(--rd-on-dark-dim); font-size: 12.5px;
}
.footer-bottom .spacer { margin-left: auto; }
.lang-row { display: flex; gap: var(--rd-sp-3); flex-wrap: wrap; }
.lang-row span { color: var(--rd-on-dark-soft); }

/* ---- Sticky mobile action bar ----------------------------------------- */
.action-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 120;
  /* min-width:0 or the flex container's min-content width wins over right:0
     and the bar grows wider than the screen. */
  min-width: 0;
  display: flex; flex-wrap: wrap;
  gap: var(--rd-sp-2);
  padding: var(--rd-sp-2) var(--rd-sp-3) calc(var(--rd-sp-2) + env(safe-area-inset-bottom, 0px));
  background: rgba(254,253,253,.94);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  border-top: 1px solid var(--rd-line);
  box-shadow: 0 -8px 24px -16px rgba(62,49,48,.4);
}
.action-bar .btn { flex: 1 1 7rem; min-height: 46px; padding: 0 var(--rd-sp-3);
                   font-size: var(--rd-step--1); min-width: 0; max-width: 100%;
                   overflow-wrap: anywhere; }
/* 390px (iPhone 12-15, the commonest phone width) is the one size where all
   three buttons share a single row and "WhatsApp" wrapped onto two lines — the
   handbook's "an icon button looks broken the moment its label wraps". Tighter
   padding plus nowrap on these specific short labels. nowrap is safe HERE but
   stays off `.btn` generally, where one long label once overran a 280px screen.

   PX, NOT EM (12 Nov 2026). A media-query `em` is relative to the BROWSER's font
   setting, not the root element, so at a 32px default `27em` = 864px and this rule
   fired on a 390px phone - pinning two-line German labels to one line and pushing
   the bar 47px wider than the screen. The rule is about PHYSICAL width, so it is
   now stated in physical units. 27em x 16px = 432px, i.e. the same breakpoint. */
@media (max-width: 432px) {
  .action-bar .btn { padding: 0 var(--rd-sp-2); font-size: 13px; white-space: nowrap; }
}
.action-bar--tour { align-items: center; }
.action-bar__price { display: grid; flex: 0 0 auto; padding-left: var(--rd-sp-2); }
.action-bar__price span { font-size: 12px; color: var(--rd-slate-400); font-weight: 650; text-transform: uppercase; letter-spacing: .06em; }
.action-bar__price b { font-size: var(--rd-step-1); color: var(--rd-espresso); font-variant-numeric: tabular-nums; line-height: 1.1; }
/* 76px clears the 64px action bar. The bar also grows by the home-indicator
   inset on a notched phone (viewport-fit=cover is set), so this must grow too
   or the last of the page hides behind it. */
body { padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }
@media (min-width: 64em) { .action-bar { display: none; } body { padding-bottom: 0; } }

/* ---- Page head (listing / contact / fleet pages) ----------------------- */
.page-head { background: var(--rd-sand); padding-block: clamp(32px, 5vw, 64px); }
.page-head__inner { max-width: min(54rem, 100%); }
.page-head h1 { font-size: var(--rd-step-4); }
.page-head p:not(.eyebrow) { color: var(--rd-slate-600); font-size: var(--rd-step-1); margin-top: var(--rd-sp-3); }

/* ---- Filter chips ------------------------------------------------------
   Horizontal scroller on small screens, wrapped row from 760px. The chips
   are real buttons so they are keyboard reachable; with JS off every tour
   stays visible, which is the correct fallback for a listing page.
   ----------------------------------------------------------------------- */
.filters {
  display: flex; gap: var(--rd-sp-2);
  overflow-x: auto; scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  padding-block: var(--rd-sp-2);
  margin-inline: calc(var(--rd-gutter) * -1);
  padding-inline: var(--rd-gutter);
  scrollbar-width: none;
}
.filters::-webkit-scrollbar { display: none; }
@media (min-width: 47.5em) {
  .filters { flex-wrap: wrap; overflow: visible; margin-inline: 0; padding-inline: 0; }
}
.filter-chip {
  flex: none; scroll-snap-align: start;
  min-height: 44px; padding: 0 var(--rd-sp-4);
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: var(--rd-radius-pill);
  font-size: var(--rd-step--1); font-weight: 650; color: var(--rd-slate);
  white-space: nowrap;
  transition: background var(--rd-fast) var(--rd-ease), border-color var(--rd-fast) var(--rd-ease),
              color var(--rd-fast) var(--rd-ease);
}
.filter-chip:hover { border-color: var(--rd-espresso); color: var(--rd-espresso); }
.filter-chip[aria-pressed="true"] {
  background: var(--rd-rust); border-color: var(--rd-rust); color: #fff;
}
.filter-chip__count { opacity: .6; font-weight: 600; margin-left: 4px; }

.listing-bar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--rd-sp-3); margin-bottom: var(--rd-sp-5);
  color: var(--rd-slate-600); font-size: var(--rd-step--1);
}
.listing-bar select {
  min-height: 44px; padding: 0 var(--rd-sp-3);
  border: 1px solid var(--rd-line); border-radius: var(--rd-radius-pill);
  /* 16px minimum: iOS Safari zooms the whole page in when a focused control is
     under 16px, and never zooms back out. */
  background: var(--rd-white); font-size: 16px; font-weight: 600;
}
.card[hidden] { display: none; }
.empty-state {
  padding: var(--rd-sp-7) var(--rd-sp-5); text-align: center;
  border: 1px dashed var(--rd-line); border-radius: var(--rd-radius-card);
  color: var(--rd-slate-600);
}

/* ---- Fleet card -------------------------------------------------------- */
.fleet-grid { display: grid; gap: var(--rd-sp-5); grid-template-columns: 1fr; }
@media (min-width: 42.5em)  { .fleet-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 68.75em) { .fleet-grid { grid-template-columns: repeat(3, 1fr); } }

.fleet { display: flex; flex-direction: column; background: var(--rd-white);
         border: 1px solid var(--rd-line-soft); border-radius: var(--rd-radius-card); overflow: hidden;
         box-shadow: var(--rd-shadow-sm);
         transition: box-shadow var(--rd-base) var(--rd-ease), transform var(--rd-base) var(--rd-ease); }
.fleet__media {
  position: relative; aspect-ratio: 16 / 10; flex: 0 0 auto; min-height: 0;
  background: linear-gradient(180deg, #FFF 0%, var(--rd-sand) 100%);
  border-bottom: 1px solid var(--rd-line-soft);
  padding: var(--rd-sp-4);
}
.fleet__media img { width: 100%; height: 100%; object-fit: contain; }

.fleet__body { display: flex; flex-direction: column; flex: 1; gap: var(--rd-sp-3); padding: var(--rd-sp-4) var(--rd-sp-4) var(--rd-sp-5); }
.spec { display: grid; grid-template-columns: auto 1fr; gap: var(--rd-sp-2) var(--rd-sp-4); margin: 0; font-size: var(--rd-step--1); }
.spec dt { color: var(--rd-slate-400); font-weight: 600; }
.spec dd { margin: 0; color: var(--rd-espresso); font-weight: 600; text-align: right; }

/* ---- Contact ----------------------------------------------------------- */
.contact-grid { display: grid; gap: var(--rd-sp-5); grid-template-columns: minmax(0, 1fr); align-items: start; }
.contact-grid > *, .contact-cards > * { min-width: 0; }
@media (min-width: 56.25em) { .contact-grid { grid-template-columns: 1fr 1.1fr; gap: var(--rd-sp-7); } }
.contact-cards { display: grid; gap: var(--rd-sp-3); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 35em) { .contact-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.contact-card {
  display: grid; gap: 6px; align-content: start;
  padding: var(--rd-sp-4);
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: var(--rd-radius-card); text-decoration: none;
  transition: border-color var(--rd-fast) var(--rd-ease), box-shadow var(--rd-base) var(--rd-ease);
}
.contact-card__label { display: flex; align-items: center; gap: 8px; font-size: 12.5px; font-weight: 650;
                       letter-spacing: .06em; text-transform: uppercase; color: var(--rd-slate-400); }
.contact-card__label .icon { color: var(--rd-rust); }
.contact-card b { font-size: var(--rd-step-1); color: var(--rd-espresso); overflow-wrap: anywhere; }
.contact-card { overflow-wrap: anywhere; }
/* an email address and a street address do not fit a half-width card without breaking
   mid-word; give them the full row instead of hyphenating "reddunestourism.co / m" */
.contact-card--email b { font-size: var(--rd-step-0); overflow-wrap: break-word; }
@media (min-width: 35em) {
  .contact-card--email, .contact-card--office { grid-column: 1 / -1; }
}
.contact-card span.small { color: var(--rd-slate-600); }

.map-frame { position: relative; border-radius: var(--rd-radius-card); overflow: hidden;
             border: 1px solid var(--rd-line); aspect-ratio: 16 / 11; background: var(--rd-sand-200); }
.map-frame img { width: 100%; height: 100%; object-fit: cover; }
.map-frame__tag {
  position: absolute; left: var(--rd-sp-3); bottom: var(--rd-sp-3);
  background: rgba(254,253,253,.94); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  padding: var(--rd-sp-2) var(--rd-sp-3); border-radius: var(--rd-radius-sm);
  font-size: 12.5px; font-weight: 600; color: var(--rd-espresso); box-shadow: var(--rd-shadow-sm);
}
.hours { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: var(--rd-step--1); }
.hours td { overflow-wrap: anywhere; }
.hours th, .hours td { text-align: left; padding: var(--rd-sp-2) 0; border-bottom: 1px solid var(--rd-line-soft); }
.hours td:last-child { text-align: right; font-weight: 600; color: var(--rd-espresso); }

/* ---- "Standard on every tour" strip ------------------------------------
   Replaces the live site's floating highlights bar (Availability: Daily,
   Instant Confirmation, Pick up & drop back, Duration, Guide, Pickup time).
   ----------------------------------------------------------------------- */
.strip {
  display: grid; gap: var(--rd-sp-3);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  padding: var(--rd-sp-5); margin-top: calc(var(--rd-sp-7) * -1);
  position: relative; z-index: 2;
  background: var(--rd-white); border: 1px solid var(--rd-line-soft);
  border-radius: var(--rd-radius-card); box-shadow: var(--rd-shadow-lg);
}
@media (min-width: 42.5em)  { .strip { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--rd-sp-4); } }
@media (min-width: 64em) { .strip { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
.strip__item { display: grid; gap: 2px; min-width: 0; }
.strip__item .icon { color: var(--rd-rust); }
.strip__label { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 650;
                letter-spacing: .06em; text-transform: uppercase; color: var(--rd-slate-400); }
.strip__item b { font-size: var(--rd-step-0); color: var(--rd-espresso); line-height: 1.3; }

/* ---- Compact tiles (attractions, emirates) ---------------------------- */
.tile-grid { display: grid; gap: var(--rd-sp-4); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 42.5em)  { .tile-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 64em) { .tile-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.tile {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr);
  align-content: end; isolation: isolate;
  min-height: 184px; padding: var(--rd-sp-4);
  border-radius: var(--rd-radius-card); overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .10);
  color: #fff; text-decoration: none;
}
.tile img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -2;
            transition: transform 400ms var(--rd-ease); }
.tile::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to top, rgba(30,18,14,.9) 0%, rgba(30,18,14,.42) 58%, rgba(30,18,14,.1) 100%);
}
.tile b { font-size: var(--rd-step-1); letter-spacing: var(--rd-tracking-tight); line-height: 1.2;
          overflow-wrap: break-word; }
.tile span { font-size: 12.5px; color: #E6D8CF; margin-top: 4px; }

/* ---- Vehicle row (homepage summary of the fleet) ---------------------- */
.vehicle-row { display: grid; gap: var(--rd-sp-4); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 47.5em)  { .vehicle-row { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 68.75em) { .vehicle-row { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
.vehicle {
  display: grid; gap: var(--rd-sp-2); padding: var(--rd-sp-3);
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: var(--rd-radius-card); text-decoration: none; min-width: 0;
  transition: border-color var(--rd-fast) var(--rd-ease), box-shadow var(--rd-base) var(--rd-ease);
}
.vehicle img {
  width: 100%; aspect-ratio: 16 / 10; object-fit: contain;
  background: linear-gradient(180deg, #FFF 0%, var(--rd-sand) 100%);
  border-radius: var(--rd-radius-sm); padding: var(--rd-sp-2);
}
.vehicle b { font-size: var(--rd-step--1); color: var(--rd-espresso); line-height: 1.25; }
.vehicle span { font-size: 12px; color: var(--rd-slate-400); }

/* ---- About ------------------------------------------------------------- */
.about { display: grid; gap: var(--rd-sp-6); align-items: center; }
@media (min-width: 56.25em) { .about { grid-template-columns: 1.05fr 1fr; gap: var(--rd-sp-8); } }
.about__media { border-radius: var(--rd-radius-card); overflow: hidden; aspect-ratio: 5 / 4; }
.about__media img { width: 100%; height: 100%; object-fit: cover; }
.stat-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--rd-sp-4);
            margin-top: var(--rd-sp-5); padding-top: var(--rd-sp-5); border-top: 1px solid var(--rd-line); }
.stat b { display: block; font-size: var(--rd-step-3); color: var(--rd-espresso); line-height: 1.1;
          letter-spacing: var(--rd-tracking-tight); }
.stat span { font-size: 12.5px; color: var(--rd-slate-600); }

/* About page variants ----------------------------------------------------- */
/* media on the right, so two About blocks on one page don't mirror each other */
@media (min-width: 56.25em) {
  .about--reverse { grid-template-columns: 1fr 1.05fr; }
  .about--reverse .about__media { order: 2; }
}
.about--reverse .about__media { aspect-ratio: 16 / 10; background: var(--rd-sand-200); }

/* the fleet spec list, reused as a company fact list: labels left, values left */
.spec--wide {
  grid-template-columns: minmax(0, 9.5rem) minmax(0, 1fr);
  gap: var(--rd-sp-3) var(--rd-sp-5);
  font-size: var(--rd-step-0);
  border-top: 1px solid var(--rd-line);
  padding-top: var(--rd-sp-4);
}
.spec--wide dt { color: var(--rd-slate-400); font-weight: 650; font-size: var(--rd-step--1);
                 letter-spacing: .04em; text-transform: uppercase; padding-top: 2px; }
.spec--wide dd { text-align: left; font-weight: 500; color: var(--rd-slate); }
@media (max-width: 34.9375em) {
  .spec--wide { grid-template-columns: minmax(0, 1fr); gap: var(--rd-sp-1); }
  .spec--wide dd { margin-bottom: var(--rd-sp-3); }
}

/* steps without the counted circle — a rule instead, for lists that are
   principles rather than an order of events */
.steps--plain .step::before {
  content: ""; width: 32px; height: 3px; border-radius: 2px;
  background: var(--rd-rust); margin-bottom: var(--rd-sp-4);
}
.steps--plain .step h3 { font-size: var(--rd-step-1); text-wrap: balance; }

/* ---- Gallery strip (the live site's "portfolio" + Instagram) ---------- */
.gallery-strip {
  display: grid; grid-auto-flow: column; grid-auto-columns: 78%;
  gap: var(--rd-sp-3); overflow-x: auto; scroll-snap-type: x mandatory;
  margin-inline: calc(var(--rd-gutter) * -1); padding-inline: var(--rd-gutter);
  padding-bottom: var(--rd-sp-2);
  scrollbar-width: none;   /* same reasoning as .review-grid--4 above */
}
.gallery-strip::-webkit-scrollbar { display: none; }
@media (min-width: 42.5em)  { .gallery-strip { grid-auto-columns: 38%; } }
@media (min-width: 64em) { .gallery-strip { grid-auto-flow: row; grid-template-columns: repeat(4, 1fr);
                                              overflow: visible; margin-inline: 0; padding-inline: 0; } }
.gallery-strip figure { margin: 0; scroll-snap-align: start; border-radius: var(--rd-radius-card);
                        overflow: hidden; aspect-ratio: 4 / 3; background: var(--rd-sand-200); }
.gallery-strip img { width: 100%; height: 100%; object-fit: cover; }

.social-row { display: flex; flex-wrap: wrap; gap: var(--rd-sp-3); }
.social-link {
  display: inline-flex; align-items: center; gap: var(--rd-sp-2);
  min-height: 44px; padding: 0 var(--rd-sp-4);
  border: 1px solid var(--rd-line); border-radius: var(--rd-radius-pill);
  background: var(--rd-white); color: var(--rd-espresso);
  font-size: var(--rd-step--1); font-weight: 650; text-decoration: none;
}
.social-link { transition: background-color var(--rd-fast) var(--rd-ease),
                        border-color var(--rd-fast) var(--rd-ease),
                        color var(--rd-fast) var(--rd-ease); }
/* Each social link takes its own brand colour on hover, wherever it appears.
   In the footer the generic `.site-footer a:hover { color: #fff }` outranked the
   pill and painted its label white on a white chip — these carry the white label
   themselves, on the brand background. */
.social-link:hover, .social-link:focus-visible,
.site-footer .social-link:hover, .site-footer .social-link:focus-visible {
  color: #fff; border-color: transparent; text-decoration: none;
}
.social-link.social--ig:hover, .social-link.social--ig:focus-visible { background-image: var(--rd-ig-grad); }
.social-link.social--yt:hover, .social-link.social--yt:focus-visible { background-color: var(--rd-yt); }
.social-link.social--fb:hover, .social-link.social--fb:focus-visible { background-color: var(--rd-fb); }

/* ---- Footer: pills REST in the brand colour -----------------------------
   A white chip on the espresso footer shouted louder than anything round it.
   Must come after the hover rules above: `.site-footer .social-link` (0,2,0) ties
   with `.social-link:hover` (0,2,0), so source order is what decides it.
   Hover darkens rather than lightens — lightening would drop below AA.        */
.site-footer .social-link {
  border-color: transparent; color: #fff;
  background-color: var(--rd-espresso);
}
.site-footer .social-link.social--ig { background-color: var(--rd-ig-pill); }
.site-footer .social-link.social--yt { background-color: var(--rd-yt-pill); }
.site-footer .social-link.social--fb { background-color: var(--rd-fb-pill); }
.site-footer .social-link.social--ig:hover, .site-footer .social-link.social--ig:focus-visible,
.site-footer .social-link.social--yt:hover, .site-footer .social-link.social--yt:focus-visible,
.site-footer .social-link.social--fb:hover, .site-footer .social-link.social--fb:focus-visible {
  background-image: none; filter: brightness(.88);
}

/* Top bar: text links on espresso, so the brand colour goes on the text itself. */
.topbar .social-row a:hover, .topbar .social-row a:focus-visible { text-decoration: none; }
.topbar .social-row a.social--ig:hover, .topbar .social-row a.social--ig:focus-visible { color: var(--rd-ig-on-dark); }
.topbar .social-row a.social--yt:hover, .topbar .social-row a.social--yt:focus-visible { color: var(--rd-yt-on-dark); }
.topbar .social-row a.social--fb:hover, .topbar .social-row a.social--fb:focus-visible { color: var(--rd-fb-on-dark); }
.topbar .social-row { gap: var(--rd-sp-3); }
.topbar .social-row a { display: inline-flex; align-items: center; gap: 5px; }

/* ---- Grouped mobile nav ------------------------------------------------ */
.nav-group + .nav-group { margin-top: var(--rd-sp-4); padding-top: var(--rd-sp-4); border-top: 1px solid var(--rd-line-soft); }
.nav-group > p { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
                 color: var(--rd-slate-400); margin-bottom: var(--rd-sp-2); }
/* ---- Reviews as a slider (9 Nov 2026, user's request: slider on desktop for
   reviews, and on mobile for reviews + gallery) ---------------------------
   Was a wrapping grid: 1 / 2 / 4 columns. Now one scroll-snap track at EVERY
   width, which is what makes it a slider on desktop too. Three cards show at
   >=68.75em rather than four, deliberately - a track where everything already
   fits is not a slider, and it leaves room for more than four reviews later.

   Built on scroll-snap, NOT on a JS carousel: touch swipe, momentum and the
   keyboard all come free from the platform, there is no autoplay to pause, and
   with JS off it degrades to a plain scrollable strip rather than to nothing.
   The negative margin lets cards bleed to the screen edge on a phone while the
   first one still lines up with the text above it. */
.review-grid--4 {
  display: grid; grid-auto-flow: column; grid-template-columns: none;
  grid-auto-columns: 86%;
  overflow-x: auto; scroll-snap-type: x mandatory;
  margin-inline: calc(var(--rd-gutter) * -1); padding-inline: var(--rd-gutter);
  padding-bottom: var(--rd-sp-2);
  overscroll-behavior-x: contain;
  /* Scrollbar hidden (9 Nov, user's request): the track advances on its own and the
     arrows are the control, so a scrollbar underneath read as a second, competing
     one. The element is still a real scroll container - swipe, arrow keys and the
     buttons all work; only the bar is not painted. */
  scrollbar-width: none;
}
.review-grid--4::-webkit-scrollbar { display: none; }
.review-grid--4 > .review { scroll-snap-align: start; }
@media (min-width: 42.5em)  { .review-grid--4 { grid-auto-columns: 46%; } }
@media (min-width: 68.75em) {
  .review-grid--4 {
    grid-auto-columns: calc((100% - var(--rd-sp-4) * 2) / 3);
    margin-inline: 0; padding-inline: 0;
  }
}

/* ---- Shared slider controls -------------------------------------------
   Deliberately INLINE and right-aligned under the track, not absolutely
   positioned over it. Overlaying works on a wide 4-up grid and fails on a phone,
   where a card is 86% of the screen and a button would sit on top of a face or a
   price. It also keeps this component out of z-index and stacking-context
   trouble, which has bitten this project repeatedly (see the mega-nav scrim).

   The buttons are INJECTED BY ui.js and hidden unless the track really overflows,
   so the gallery - which is a plain 4-column grid at >=64em - shows none there and
   shows them on a phone, with no media query needed to tell them apart. */
.slider-controls {
  display: flex; justify-content: flex-end; gap: var(--rd-sp-2);
  margin-top: var(--rd-sp-3);
}
.slider-btn {
  width: 44px; height: 44px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--rd-line); border-radius: 50%;
  background: var(--rd-white); color: var(--rd-espresso); cursor: pointer;
}
.slider-btn .icon { width: 18px; height: 18px; }
.slider-btn:disabled { opacity: .38; cursor: default; }
@media (hover: hover) {
  .slider-btn:not(:disabled):hover { background: var(--rd-sand); border-color: var(--rd-espresso); }
}
/* Targets `svg`, not `.icon`: these buttons are injected by ui.js with
   createElementNS and carry no `.icon` class, so a `.icon` selector silently did
   nothing and the "previous" chevron pointed RIGHT. Found by looking at the
   screenshot - no checker tests which way an arrow points. */
.slider-btn--prev svg { transform: rotate(180deg); }
/* Play/pause is drawn inline rather than added to assets/icons.svg: the sprite is
   inlined on EVERY page on wp_body_open, and two more symbols there cost every
   page bytes for a control that exists on one section of one page. */
.slider-btn svg { display: block; }

/* ==========================================================================
   Header dropdowns (desktop ≥1024px)
   Hover-intent on mouse, focus/ArrowDown for keyboard, Escape closes.
   Panels are plain markup in the page — no JS builds them, so with JS off the
   links are still in the DOM and crawlable, and the top-level item is a real
   link that navigates on click rather than a dead toggle.
   ========================================================================== */
.nav-scrim {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(30, 18, 14, .36);
  -webkit-backdrop-filter: blur(1px); backdrop-filter: blur(1px);
  opacity: 0; visibility: hidden;
  transition: opacity var(--rd-base) var(--rd-ease), visibility 0s var(--rd-base);
}
.nav-scrim.is-open { opacity: 1; visibility: visible; transition: opacity var(--rd-base) var(--rd-ease), visibility 0s; }

.nav-item { position: static; }
.nav-link { display: inline-flex; align-items: center; gap: 5px; }
.nav-caret { width: 14px; height: 14px; transition: transform var(--rd-base) var(--rd-ease); }
.nav-link[aria-expanded="true"] { background: var(--rd-sand); color: var(--rd-espresso); }
.nav-link[aria-expanded="true"] .nav-caret { transform: rotate(180deg); }

.nav-panel-mega {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 102;
  background: var(--rd-page);
  border-top: 1px solid var(--rd-line-soft);
  border-radius: 0 0 var(--rd-radius-card) var(--rd-radius-card);
  box-shadow: var(--rd-shadow-lg);
  opacity: 0; visibility: hidden; transform: translateY(-8px);
  transition: opacity var(--rd-base) var(--rd-ease),
              transform 320ms var(--rd-ease),
              visibility 0s 320ms;
  overflow: hidden;
}
.nav-panel-mega.is-open {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity var(--rd-base) var(--rd-ease), transform 320ms var(--rd-ease), visibility 0s;
}
@media (prefers-reduced-motion: reduce) { .nav-panel-mega { transform: none; } }

.nav-panel__body {
  display: grid; gap: var(--rd-sp-7);
  padding: var(--rd-sp-6) var(--rd-gutter);
  max-width: var(--rd-wrap); margin-inline: auto;
}
.nav-panel__body--split { grid-template-columns: minmax(0, 1fr) 300px; }
.nav-panel__label {
  display: flex; align-items: center; justify-content: space-between; gap: var(--rd-sp-4);
  margin-bottom: var(--rd-sp-4);
  font-size: 12px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--rd-slate-400);
}
.nav-panel__label a {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--rd-step--1); font-weight: 650; letter-spacing: 0; text-transform: none;
  color: var(--rd-rust); text-decoration: none;
}
.nav-panel__label a:hover { text-decoration: underline; }

.nav-panel__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px var(--rd-sp-4); list-style: none; margin: 0; padding: 0; }
.nav-panel__grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.nav-panel__grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.nav-card {
  display: flex; align-items: center; gap: var(--rd-sp-3);
  padding: var(--rd-sp-2) var(--rd-sp-3);
  border-radius: var(--rd-radius-sm); text-decoration: none; min-height: 56px;
  transition: background var(--rd-fast) var(--rd-ease);
}
.nav-card:hover { background: var(--rd-sand); }
.nav-card img {
  width: 44px; height: 44px; flex: none; object-fit: cover;
  border-radius: 9px; background: var(--rd-sand-200);
}
.nav-card__text { display: grid; gap: 1px; min-width: 0; }
.nav-card__text b { font-size: var(--rd-step--1); font-weight: 650; color: var(--rd-espresso); line-height: 1.25; }
.nav-card__text span { font-size: 12px; color: var(--rd-slate-400); line-height: 1.3; }

.nav-promo {
  display: grid; gap: var(--rd-sp-3); align-content: start;
  padding: var(--rd-sp-4);
  background: var(--rd-sand); border-radius: var(--rd-radius-card);
  text-decoration: none;
}
.nav-promo img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; border-radius: var(--rd-radius-sm); }
.nav-promo b { font-size: var(--rd-step-0); color: var(--rd-espresso); line-height: 1.25; }
.nav-promo .price__value { font-size: var(--rd-step-1); }

.nav-panel__bar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--rd-sp-3) var(--rd-sp-5);
  padding: var(--rd-sp-3) var(--rd-gutter);
  background: var(--rd-sand-200);
  font-size: var(--rd-step--1);
}
.nav-panel__bar > * { max-width: var(--rd-wrap); }
.nav-panel__bar-inner {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--rd-sp-3) var(--rd-sp-5);
  width: 100%; max-width: var(--rd-wrap); margin-inline: auto;
}
.nav-panel__bar b { color: var(--rd-espresso); }
.nav-panel__bar a { display: inline-flex; align-items: center; gap: 6px; font-weight: 650; text-decoration: none; }

@media (max-width: 63.999em) {
  .nav-panel-mega, .nav-scrim { display: none; }
}
.nav-promo .badge { justify-self: start; }
.nav-panel__label a .icon, .nav-panel__bar a .icon:not(.icon--solid) { transform: rotate(-90deg); }
.nav-panel__label a, .nav-panel__bar a { min-height: 32px; padding-inline: 2px; }

/* White-on-photo links must stay white on hover. `a:hover { color: rust }`
   from base.css is (0,1,1) and beats `.tile`/`.family` at (0,1,0), so without
   this the caption on a dark photo turned rust mid-hover. Outside the pointer
   query on purpose: the same thing happens on a touch tap. */
.tile:hover, .tile:focus-visible,
.family:hover, .family:focus-visible,
.nav-promo:hover, .nav-promo:focus-visible { color: #fff; }

/* ---- Pointer interaction ------------------------------------------------
   Every hover that MOVES something lives in here.

   Why the query: a touch browser fires :hover on tap and leaves it applied
   until you tap somewhere else. Without this guard, tapping a tour card on a
   phone leaves it lifted, shadowed and zoomed — it reads as a stuck card, and
   it is one of the commonest giveaways of a desktop-first build.
   `(hover: hover)` alone, deliberately: adding `(pointer: fine)` also drops
   hover on hybrid machines whose primary pointer is coarse but which do have
   a mouse.

   Everything animated is transform/opacity/box-shadow and nothing here
   measures the scroll position. Colour-only hovers stay in the cascade above,
   because a colour that lingers for a moment on touch costs nothing.
   `prefers-reduced-motion` is already handled globally in base.css, which
   collapses every transition duration.
   ----------------------------------------------------------------------- */
@media (hover: hover) {

  /* buttons: a small rise, and a real press on the way down */
  .btn:hover { transform: translateY(-1px); }

  /* three product families */
  .family:hover img { transform: scale(1.04); }
  .family:hover .family__arrow { transform: translateX(4px); }

  /* tour cards */
  .card:hover { box-shadow: var(--rd-shadow-lg); transform: translateY(-3px); border-color: var(--rd-line); }
  .card:hover .card__media img { transform: scale(1.04); }
  /* the card's own button answers when the card is hovered, so the whole card
     reads as one target rather than a button sitting on a panel */
  .card:hover .btn--ghost { border-color: var(--rd-espresso); color: var(--rd-espresso); }

  /* emirate + attraction tiles: lift, zoom, and the caption settles upward.
     The scrim deepens so white text stays legible over a brightened photo. */
  .tile { transition: transform var(--rd-base) var(--rd-ease), box-shadow var(--rd-base) var(--rd-ease); }
  .tile:hover { transform: translateY(-3px); box-shadow: var(--rd-shadow-lg); }
  .tile:hover img { transform: scale(1.05); }
  .tile::after { transition: opacity var(--rd-base) var(--rd-ease); }
  .tile:hover::after { opacity: .88; }
  .tile b, .tile span { transition: transform var(--rd-base) var(--rd-ease); }
  .tile:hover b, .tile:hover span { transform: translateY(-2px); }

  /* fleet cards and the homepage vehicle row */
  .fleet:hover { box-shadow: var(--rd-shadow-md); transform: translateY(-2px); }
  .fleet__media img { transition: transform var(--rd-slow) var(--rd-ease); }
  .fleet:hover .fleet__media img { transform: scale(1.03); }
  .vehicle { transition: transform var(--rd-base) var(--rd-ease), border-color var(--rd-fast) var(--rd-ease),
                         box-shadow var(--rd-base) var(--rd-ease); }
  .vehicle:hover { border-color: var(--rd-line); box-shadow: var(--rd-shadow-md); transform: translateY(-2px); }
  .vehicle img { transition: transform var(--rd-slow) var(--rd-ease); }
  .vehicle:hover img { transform: scale(1.04); }

  /* contact cards */
  a.contact-card { transition: transform var(--rd-base) var(--rd-ease), border-color var(--rd-fast) var(--rd-ease),
                               box-shadow var(--rd-base) var(--rd-ease); }
  a.contact-card:hover { border-color: var(--rd-line); box-shadow: var(--rd-shadow-md); transform: translateY(-2px); }

  /* dropdown cards: match the grid they stand in for */
  .nav-card img { transition: transform var(--rd-slow) var(--rd-ease); }
  .nav-card:hover img { transform: scale(1.05); }

  /* gallery strip */
  .gallery-strip img { transition: transform var(--rd-slow) var(--rd-ease); }
  .gallery-strip a:hover img, .gallery-strip figure:hover img { transform: scale(1.04); }

  /* filter chips */
  .filter-chip:hover { background: var(--rd-sand); }
}

/* A press is a press on every device: the element returns to rest while held,
   which is the oldest and cheapest way to make a control feel physical. */
.btn:active, .tile:active, .card:active, .fleet:active, .vehicle:active,
a.contact-card:active, .family:active { transform: translateY(0) scale(.995); }

/* =====================================================================
   Competitor-informed additions (4 Nov 2026)
   Structure borrowed from mavericksafariuae.com, rebuilt in our own
   tokens. No copy was taken from them - every string here is ours or
   the client's own. See HANDBOOK "competitor" note.
   ===================================================================== */

/* A - fact chips on the tour card, and the price unit ----------------- */
.card__chips { display: flex; flex-wrap: wrap; gap: 6px; }
.meta-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: var(--rd-radius-pill);
  background: var(--rd-sand-200); color: var(--rd-slate-600);
  font-size: 12.5px; font-weight: 650; line-height: 1.5;
}
.meta-chip .icon { width: 13px; height: 13px; color: var(--rd-rust); flex: none; }
.price__unit { font-size: 12.5px; color: var(--rd-slate-600); font-weight: 500; }

/* B - review date line. Avatars and names already existed; the relative
   date is what the competitor had and we did not. */
.review__when { font-size: 12.5px; color: var(--rd-slate-400); }
.review__source {
  margin-left: auto; align-self: flex-start; flex: none;
  font-size: 12px; font-weight: 700; color: var(--rd-slate-400);
}

/* C - "which safari suits you" comparison table ----------------------- */
.compare-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.compare {
  width: 100%; min-width: 640px; border-collapse: collapse;
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: var(--rd-radius-card); overflow: hidden;
}
.compare th,
.compare td {
  padding: var(--rd-sp-3) var(--rd-sp-4); text-align: left;
  font-size: var(--rd-step--1); border-bottom: 1px solid var(--rd-line-soft);
  vertical-align: top;
}
.compare thead th {
  background: var(--rd-sand); color: var(--rd-espresso);
  font-weight: 700; white-space: nowrap;
}
.compare tbody tr:last-child th,
.compare tbody tr:last-child td { border-bottom: 0; }
.compare tbody tr:hover td,
.compare tbody tr:hover th { background: var(--rd-sand-050, var(--rd-sand)); }
.compare th[scope="row"] { font-weight: 650; color: var(--rd-espresso); }
.compare th[scope="row"] a { color: inherit; text-decoration: none; }
.compare th[scope="row"] a:hover { text-decoration: underline; }

/* ---- Comparison table on a phone: CARDS, not a sideways scroll ---------
   (9 Nov 2026, user's call: "cant we update the layout ... instead of swiping")
   `min-width: 640px` in a 358px box meant a 640px table that had to be swiped, and
   the columns are DYNAMIC - Duration and Pickup come back the moment the client
   sends them, taking it to six - so narrowing the columns only postpones the
   problem. One card per safari survives any number of columns.

   Each `td` prints its own column heading from `data-label`, which is why the PHP
   emits one per cell; `thead` is hidden because its headings now live in the rows.
   `min-width` MUST be reset or the block-level table still forces 640px and the
   page scrolls sideways anyway - that is the whole bug, just moved. */
@media (max-width: 47.99em) {
  .compare { min-width: 0; border: 0; background: none; border-radius: 0; }
  /* `display: none`, NOT a visually-hidden clip. Two reasons, and the second is the
     honest one: (1) a clipped 1px thead holding 190px of headings is exactly what the
     clipped-content check is built to catch, and excusing it would mean teaching the
     checker to ignore a real class of bug; (2) setting `display: block` on table
     elements ALREADY removes the table semantics for assistive tech, so the
     `scope="col"` associations in that thead are void in this mode either way -
     keeping it readable-but-hidden buys nothing. The column names are not lost: each
     cell prints its own from `data-label` via `td::before`, which screen readers
     announce. */
  .compare thead { display: none; }
  .compare, .compare tbody, .compare tr, .compare th, .compare td { display: block; }
  .compare tbody tr {
    background: var(--rd-white); border: 1px solid var(--rd-line);
    border-radius: var(--rd-radius-card); padding: var(--rd-sp-3) var(--rd-sp-4);
    margin-bottom: var(--rd-sp-3);
  }
  .compare tbody tr:last-child { margin-bottom: 0; }
  .compare th[scope="row"] {
    padding: 0 0 var(--rd-sp-2); font-size: var(--rd-step-0); font-weight: 700;
    border-bottom: 1px solid var(--rd-line-soft); margin-bottom: var(--rd-sp-2);
  }
  .compare td {
    padding: 0; border-bottom: 0;
    display: flex; justify-content: space-between; align-items: baseline;
    gap: var(--rd-sp-4); min-height: 26px;
  }
  .compare td::before {
    content: attr(data-label);
    font-size: 12.5px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase;
    color: var(--rd-slate-400); flex: none;
  }
  /* The row-hover tint is a desktop affordance for scanning ACROSS a row; on a card
     it just makes the whole card flash on tap. */
  .compare tbody tr:hover td,
  .compare tbody tr:hover th { background: none; }
}
/* --rd-wa-700 (#138A46) is WhatsApp's functional green and only reaches 4.42:1
   on white - under AA. This darker green is a state colour, not a brand colour,
   so it is defined here rather than added to the palette, where it would show up
   in the editor's colour picker for no good reason. 5.95:1 on white, 4.88 on sand. */
.compare { --rd-ok-700: #0F7338; }
.compare .is-yes { color: var(--rd-ok-700); font-weight: 700; }
.compare .is-no  { color: var(--rd-slate-400); }
.compare__note { margin-top: var(--rd-sp-3); font-size: 12.5px; color: var(--rd-slate-400); }
.compare__hint { display: none; }
@media (max-width: 48em) {
  .compare th, .compare td { padding: var(--rd-sp-3); }
  /* The table is wider than a phone, so it scrolls sideways. Pin the tour name
     column: without it you scroll two columns across and no longer know which
     safari the row belongs to. The header cell needs the higher z-index so it
     stays above the body cells it overlaps while scrolling. */
  .compare th[scope="row"] {
    position: sticky; left: 0; z-index: 1;
    background: var(--rd-white);
    box-shadow: 1px 0 0 var(--rd-line-soft);
  }
  .compare thead th:first-child {
    position: sticky; left: 0; z-index: 2;
    background: var(--rd-sand);
    box-shadow: 1px 0 0 var(--rd-line-soft);
  }
  .compare__hint {
    display: block; margin-top: var(--rd-sp-3);
    font-size: 12.5px; color: var(--rd-slate-400);
  }
}

/* ---- Form status + consent (5 Nov 2026, forms wired up) ----------------
   The status banner is the ONLY feedback a visitor gets after submitting, so it
   has to be unmissable and readable on both the cream page and the white card.
   `role="status"` announces it; `tabindex="-1"` lets the handler's #enquire jump
   land focus on it rather than on the top of the form. */
.form-status {
  /* The handler jumps the browser to this element, and the sticky header would park
     it underneath - which defeats the whole point of the banner. That clearance now
     comes from `html { scroll-padding-top: 96px }` in base.css, which covers every
     in-page anchor target rather than this one element. Do NOT re-add a
     scroll-margin-top here: the two SUM, and the banner lands 192px down. */
  margin: 0 0 var(--rd-sp-4);
  padding: var(--rd-sp-3) var(--rd-sp-4);
  border-radius: var(--rd-radius-sm);
  border-left: 4px solid;
  font-size: var(--rd-step--1);
}
.form-status:focus { outline: 2px solid var(--rd-rust); outline-offset: 2px; }
.form-status--ok  { background: #EAF6EE; border-color: #0F7338; color: #10492A; }
.form-status--bad { background: var(--rd-rust-050); border-color: var(--rd-rust); color: var(--rd-espresso); }

/* Consent: the box and its wording are one tap target, and the label must not
   shrink to the width of the checkbox. */
.field--consent label {
  display: flex; align-items: flex-start; gap: var(--rd-sp-3);
  font-size: var(--rd-step--1); color: var(--rd-slate-600);
  font-weight: 500; cursor: pointer;
  /* The LABEL is the tap target - clicking the wording toggles the box - so it is
     the thing that has to clear 44px, not the 18px box itself. `center` so a
     one-line consent line does not leave the box floating at the top of a 44px
     row; flex-start still governs once the wording wraps to two lines. */
  min-height: var(--rd-tap-min, 44px); align-items: center;
}
.field--consent label:has(br), .field--consent label.is-wrapped { align-items: flex-start; }
.field--consent input[type="checkbox"] {
  flex: none; width: 20px; height: 20px;
  accent-color: var(--rd-rust); cursor: pointer;
}

/* ---------------------------------------------------------------------------
   Camp dinner menus
   Homepage: two cards side by side. Tour page: the full menu in <details>.
   <details> rather than JS tabs so every dish stays in the HTML for search.
--------------------------------------------------------------------------- */
.camps {
  display: grid; gap: 20px; margin-top: 24px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
}
.camp {
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: 10px; padding: 22px 20px;
}
.camp--premium { border-color: var(--rd-espresso); }
.camp__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px; }
.camp__head h3 { margin: 0; }
.camp__count {
  margin: 0; font-size: .82rem; letter-spacing: .04em; text-transform: uppercase;
  color: var(--rd-espresso); background: var(--rd-sand-200);
  border-radius: 999px; padding: 3px 10px;
}
.camp__summary { margin: 10px 0 14px; }
.camp__picks { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.camp__picks li { display: flex; gap: 9px; align-items: flex-start; }
.camp__picks .icon { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 3px; color: var(--rd-ok-700); }
.camps__credit { margin: 18px 0 0; font-style: italic; }
.camps__note,
.camp-menu__note { margin: 6px 0 0; font-size: .9rem; color: var(--rd-espresso); }

.camp-menu { margin-block-start: 0; }
.camp-menu__lead { margin: 0 0 16px; }
.camp-menu__count {
  display: inline-block; margin-left: 8px; font-size: .82rem; letter-spacing: .04em;
  text-transform: uppercase; color: var(--rd-espresso);
  background: var(--rd-sand-200); border-radius: 999px; padding: 3px 10px;
}
.camp-menu__sec { border-bottom: 1px solid var(--rd-line-soft); }
.camp-menu__sec > summary {
  cursor: pointer; padding: 12px 0; font-weight: 600;
  list-style: none; display: flex; align-items: center; gap: 10px;
}
.camp-menu__sec > summary::-webkit-details-marker { display: none; }
.camp-menu__sec > summary::before {
  content: ""; flex: 0 0 auto; width: 9px; height: 9px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(-45deg); transition: transform var(--rd-fast) var(--rd-ease);
}
.camp-menu__sec[open] > summary::before { transform: rotate(45deg); }
.camp-menu__sec > summary:focus-visible { outline: 2px solid var(--rd-espresso); outline-offset: 3px; }
.camp-menu__grp { padding: 0 0 14px 19px; }
.camp-menu__grp h3 { margin: 6px 0 4px; font-size: .95rem; color: var(--rd-espresso); }
.camp-menu__grp ul { margin: 0; padding-left: 18px; }
.camp-menu__grp li { margin: 2px 0; }
.camp-menu__credit { margin: 16px 0 0; font-style: italic; }

@media (prefers-reduced-motion: reduce) {
  .camp-menu__sec > summary::before { transition: none; }
}

/* =========================================================================
 * SINGLE-TOUR PAGE, rebuilt 6 Nov 2026.
 * Structure follows extremeadventuresdubai.com's tour template (see the note in
 * inc/blocks.php). Everything here is a reassembly of components that already
 * existed - .badge, .trust, .price__*, .faq, .incl, .gallery - into their order,
 * not a new visual language. mockups/tour.html is the OLD approved layout and is
 * now behind the theme; regenerate the review pack from the theme, not from it.
 * ========================================================================= */

/* Wider rail. The old 352px held three bullets; it now holds a price, a rating,
   a WhatsApp button and a four-field form, and 352 made every label wrap. */
@media (min-width: 64em) {
  .tour-layout { grid-template-columns: minmax(0,1fr) 404px; gap: var(--rd-sp-7); }
}
/* Only once there is room for it. At 1024 a 440px rail would leave the body column
   at 472px, which is too narrow for the three-column inclusions table. */
@media (min-width: 75em) {
  .tour-layout { grid-template-columns: minmax(0,1fr) 440px; }
}

/* ---- The title block --------------------------------------------------- */
.tour-head { margin-top: var(--rd-sp-5); }
.tour-head > * + * { margin-top: var(--rd-sp-4); }
.tour-head .badge { display: inline-flex; }
.tour-head h1 { margin: 0; }
.tour-head__lead { font-size: var(--rd-step-1); color: var(--rd-slate-600); max-width: 60ch; }

/* Price and rating share a row on anything wider than a small phone: they are
   read together ("how much, and is it any good?"), and stacking them pushes the
   fact strip below the fold on a 390px screen. */
.tour-head__price {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--rd-sp-3) var(--rd-sp-5);
  padding-block: var(--rd-sp-4);
  border-block: 1px solid var(--rd-line-soft);
}
.tour-head__price .price { display: flex; align-items: baseline; gap: var(--rd-sp-2); margin: 0; }
.tour-head__price .price__value { font-size: var(--rd-step-3); line-height: 1; }
/* "Ask for today's price" is a sentence, not a number - at step-3 it shouts and
   wraps. One step down and it still leads the row. */
/* Scoped, not `.price__value--ask` on its own: `.tour-head__price .price__value`
   is (0,2,0) and beat the bare modifier (0,1,0), so the sentence rendered at the
   step-3 price size and read as a second headline. Fourth time this trap has bitten
   in this project - scope a modifier whenever the base rule is itself scoped. */
.tour-head__price .price__value--ask { font-size: var(--rd-step-1); font-weight: 650; }
/* Private-vehicle price (9 Nov 2026). A SECOND price point, so it sits inside the
   same bordered pricing block as the headline price rather than becoming a list item
   further down the page. `flex: 0 0 100%` drops it onto its own row BELOW the
   price+rating row - the DOM order is price, rating, private for exactly that reason;
   putting it before the rating pushed the rating onto a third row and broke the
   approved one-row pairing. */
.price-private {
  margin: var(--rd-sp-2) 0 0;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--rd-sp-2);
  font-size: var(--rd-step--1);
}
.price-private__label {
  font-size: 12.5px; font-weight: 650; letter-spacing: .07em; text-transform: uppercase;
  color: var(--rd-slate-400);
}
.price-private__value {
  font-weight: 750; color: var(--rd-espresso);
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
}
.price-private__unit { font-size: 12.5px; color: var(--rd-slate-600); font-weight: 500; }

/* `flex: 0 0 100%` is what drops this onto its own row - but ONLY inside
   `.tour-head__price`, which is a flex ROW. The card is a flex COLUMN, where
   flex-basis is the MAIN axis, i.e. HEIGHT: unscoped, this rule gave the line 100%
   of the card's height and overflowed every card by ~340px (80 failing viewport
   combinations, caught by check-responsive.mjs). Scope any flex-basis to the
   container whose direction you meant. */
.tour-head__price .price-private { flex: 0 0 100%; }

/* In the rail `.booking__price` is a GRID (and `align-items: baseline`), so the line
   is a grid item and needs no flex-basis at all - it already occupies its own row.
   Sized like the card variant so the rail does not shout a second price. */
.price-private--rail {
  margin-top: var(--rd-sp-2);
  font-size: 12.5px; gap: var(--rd-sp-1) var(--rd-sp-2);
}
.price-private--rail .price-private__value { font-size: 13.5px; }

/* On a card it sits under the price/Details row, above the WhatsApp button, and must
   not compete with the per-person headline - so no uppercase label treatment and one
   size down. `.price-slot` has the dashed top border; this line stays inside it
   visually by hugging it with a small margin rather than adding a second rule. */
.price-private--card {
  margin-top: var(--rd-sp-2);
  font-size: 12.5px; gap: var(--rd-sp-1) var(--rd-sp-2);
}
.price-private--card .price-private__label { letter-spacing: .04em; }
.price-private--card .price-private__value { font-size: 13.5px; }

/* A tour with no private-vehicle price still renders this row, EMPTY, so that the
   price block sits at the same height on every card in a row - `.price-slot` is
   `margin-top: auto`, so an omitted row drops the whole block to the card's floor.
   The real line wraps to two lines in a card, hence a two-line reservation. Both the
   real and the empty row carry this `min-height`, so they can never disagree.
   46px is MEASURED, not guessed, and an em calc cannot predict it: the two lines use
   different font sizes (label 12.5px, value 13.5px), so `calc(2 * 1.5em ...)` came out
   at 42px and left the price rows 4px apart. Re-measure if this copy or the type scale
   changes - `node tools/shot.mjs` plus a getBoundingClientRect on `.price-private`. */
.price-private--card { min-height: 46px; }
.price-private--empty { visibility: hidden; }

.tour-head__rating { margin: 0; color: var(--rd-slate-600); }
.tour-head__rating .stars { color: var(--rd-star); margin-right: var(--rd-sp-1); }

.tour-head .trust { margin: 0; }

/* ---- Inclusions: one group per ROW, each list two-up --------------------
   `.incl` was a row of columns - Included beside Not included - which on a real
   tour meant 15 ticks stacked in a 300px column while the short "Not included"
   list left the other half of the row empty, and the section ran ~760px tall.
   Each group now takes a full row and its LIST runs in two columns instead, so
   the line length is unchanged (~300px, same as before) and the height halves.
   This also means a third group, Optional extras, just adds a row rather than
   squeezing all three into thirds - so `.incl--3` is gone. */
@media (min-width: 47.5em) {
  .incl { grid-template-columns: 1fr; gap: var(--rd-sp-6); }
  /* Multi-column, not a 2-column grid. A grid sizes each ROW to its tallest item,
     so a one-line tick sitting beside a wrapped one leaves a visible hole in the
     list - and half these lines wrap. Columns flow continuously and pack tight.
     It also reads down-then-across, which is how a list is normally read.
     `break-inside: avoid` stops an item being split across the column boundary. */
  .incl ul { display: block; columns: 2; column-gap: var(--rd-sp-6); }
  .incl li { break-inside: avoid; margin-bottom: var(--rd-sp-3); }
}
.incl--extra .icon { color: var(--rd-rust); }

/* ---- Booking rail ------------------------------------------------------ */
/* The rail now carries a form, so it can outgrow the viewport. Without the
   max-height a tall card simply stops sticking and scrolls away with the page. */
@media (min-width: 64em) {
  .booking { max-height: calc(100vh - var(--rd-sp-7) - var(--rd-sp-6)); overflow-y: auto; }
}
.booking__price { align-items: baseline; }
.booking__price .price__value { display: flex; align-items: baseline; gap: var(--rd-sp-2); }
.booking__rating { margin-top: var(--rd-sp-2); color: var(--rd-slate-600); }
.booking__rating .stars { color: var(--rd-star); margin-right: var(--rd-sp-1); }
.booking__form { border-top: 1px dashed var(--rd-line); padding-top: var(--rd-sp-4); }
.booking__formhead { margin: 0 0 var(--rd-sp-3); font-size: var(--rd-step--1); }
/* `.hero-form` is a CARD: its own background, 14px radius, 16px padding and a
   `0 18px 50px rgba(30,18,14,.4)` shadow - all of which exist because on the
   homepage it floats over a dark hero photograph. Reused inside the booking rail
   that made a card inside a card, and the doubled padding squeezed every field to
   322px in a 404px rail. Strip the card here; the rail already IS the surface, and
   the dashed rule above the form is separation enough. Fields go 322 -> 392. */
.booking__form .hero-form {
  margin-top: 0;
  padding: 0;
  background: none;
  box-shadow: none;
  border-radius: 0;
  gap: var(--rd-sp-3);
}
.booking__form .hero-form input:not([type="checkbox"]),
.booking__form .hero-form select { min-height: 46px; }

/* ---- Last-checked stamp ------------------------------------------------ */
.tour-stamp { margin-top: var(--rd-sp-6); color: var(--rd-slate-400); }

/* ---- Related tours as a scroller --------------------------------------- */
/* Six cards, not three. A grid of six would be two rows of dead space at the end
   of a page people are leaving anyway; a single scrollable row keeps the section
   one card tall and lets them flick. Scrollbar stays visible on desktop - a
   hidden one on a horizontal list is a known discoverability trap. */
@media (min-width: 47.5em) {
  .tour-grid--scroll {
    grid-auto-flow: column;
    /* The max is CAPPED, not 1fr. With `1fr` a lone card took the whole 1136px track:
       the jet ski and yacht pages have exactly one related tour each (both are the only
       other `adventures` tour), so the strip rendered one card nearly a metre wide while
       the desert pages showed three at 363px (client, 12 Nov 2026). 364px is what a
       three-card row already resolves to at the widest the wrap allows, so capping here
       changes nothing on the pages that have three and only reins in the short ones. */
    grid-auto-columns: minmax(276px, 364px);
    grid-template-columns: none;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    padding-bottom: var(--rd-sp-4);
    overscroll-behavior-x: contain;
  }
  .tour-grid--scroll > * { scroll-snap-align: start; }
}
.booking__note { margin: 0; }

/* ---- .trust on a light background -------------------------------------
   `.trust` was written for the homepage hero: #F0E4DC text on a dark photo with a
   white 18% rule. Dropped onto the white tour page it measured near-invisible.
   This variant restyles it for light and carries FOUR items rather than three, so
   the base rule's `repeat(3, auto)` has to be overridden too or the fourth wraps
   alone onto a second row. */
.trust--light { border-top-color: var(--rd-line-soft); }
.trust--light li { color: var(--rd-slate-600); }
.trust--light .icon { color: var(--rd-wa-700); }
@media (min-width: 40em) {
  .trust--light { grid-template-columns: repeat(2, minmax(0,1fr)); gap: var(--rd-sp-3) var(--rd-sp-5); }
}
@media (min-width: 64em) {
  .trust--light { grid-template-columns: repeat(2, minmax(0,1fr)); }
}

/* ---------------------------------------------------------------------------
   Weekday discount note (10 Nov 2026, client's instruction)
   Rendered by `reddunes_weekday_offer()` in tour-head and in the booking rail.
   A NOTE, NOT A SECOND CTA: the WhatsApp button is already the loud element on
   both surfaces, so this is quiet - small, slate, one inline link. Making it a
   button would give the tour page two competing green CTAs within 200px.
   Hanging indent rather than flex, for the same reason as `.incl` on 9 Nov: the
   line mixes a text node with an inline <a>, and flex would make each child its
   own item and split them into ragged columns at 390px.
   --------------------------------------------------------------------------- */
.offer-note {
  margin: var(--rd-sp-4) 0 0;
  padding: var(--rd-sp-3) var(--rd-sp-4);
  /* The hanging indent is built on the LEFT PADDING, so it has to include it or the
     wrapped line sits under the icon instead of under the text. */
  padding-left: calc(var(--rd-sp-4) + 26px); text-indent: -26px;
  background: var(--rd-sand);
  border: 1px solid var(--rd-line-soft);
  border-left: 3px solid var(--rd-wa-700);
  border-radius: var(--rd-radius-sm);
  font-size: var(--rd-step--1); line-height: 1.5;
  color: var(--rd-espresso); font-weight: 550;
}
.offer-note .icon {
  width: 18px; height: 18px; margin-right: 8px;
  color: var(--rd-wa-700); vertical-align: -3px;
}
/* NO `white-space: nowrap` HERE. It was added to keep the link on one line and it
   overflowed the page by 35px at 390px with a 32px base font - "Ask about weekday
   rates" simply cannot fit, and an unbreakable link drags the whole column wider.
   Measured: hiding this note took scrollWidth 425 -> 390, while hiding the gallery
   the checker blamed changed nothing. An un-wrappable string in a narrow column is
   always a zoom bug waiting to happen. */
.offer-note a {
  color: var(--rd-espresso); font-weight: 750;
  text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 2px;
}

/* The tour page's trust row draws a `border-top` as a divider and spaces itself with
   `padding-top` only - no margin - so the RULE sat hard against the line above it
   (0px, measured on staging). The padding lives BELOW the rule, so it cannot help.
   Scoped to `.tour-head` so the homepage hero's own `.trust` is untouched. */
.tour-head .trust { margin-top: var(--rd-sp-5); }

/* The `--rail` variant was removed on 10 Nov (user's call) - the rail is a fixed-height
   column around a 4-field form and the extra line gave the form its own scrollbar. The
   note is tour-head only. Nothing renders `.offer-note--rail`, so its rule is gone too
   rather than left as dead CSS. */

/* =========================================================================
   SINGLE-TOUR REDESIGN (11 Nov 2026, user's call)
   One full-width column, booking form at the END of the tour, booking actions
   at the TOP, and a modal so the top button does not have to send anyone on a
   6,000px scroll. REPLACES the sticky right-hand rail.

   The rail's own rules (`.tour-layout` 2-column, all of `.booking`) are LEFT IN
   PLACE above on purpose - `reddunes/tour-booking` still exists and putting the
   rail back is one line in templates/single-tour.html. Nothing renders them today.
   ========================================================================= */

/* The content column is now the whole wrap at every width. These override the
   352px / 404px / 440px rail columns declared earlier in this file. */
@media (min-width: 64em) { .tour-layout { grid-template-columns: minmax(0, 1fr); gap: var(--rd-sp-7); } }
@media (min-width: 75em) { .tour-layout { grid-template-columns: minmax(0, 1fr); } }

/* MEASURE. The body column went from ~740px to the full 1200px wrap, which puts
   running prose at roughly 150 characters a line - unreadable. Only the PROSE is
   capped: the gallery, the fact strip, the inclusions columns, the camp menu and
   the FAQ all WANT the extra width, which is the point of the change. */
.tour-main > .entry-content > p,
.tour-main > .entry-content > ul,
.tour-main > .entry-content > ol,
.tour-main > .entry-content > h2,
.tour-main > .entry-content > h3,
.tour-main > .entry-content > blockquote { max-width: none; }

/* FULL WIDTH, 11 Nov 2026 - the user's explicit call, overriding the measure cap this
   section originally shipped with (58ch, ~70 characters). Edge to edge the description
   runs ~130 characters a line, which is long for reading; it is a deliberate trade for
   a page that does not look half empty. To get BOTH, give `.tour-main > .entry-content`
   `columns: 2; column-gap: var(--rd-sp-8)` - same full width, ~65 characters, and the
   `.incl ul` two lines below already uses exactly that treatment. */

/* The rest of the right-hand gaps, same call. The lead and the FAQ answers carried
   their own measure caps (60ch / 68ch) and stopped ~410px short of the column. */
.tour-head__lead { max-width: none; }
.tour-main .faq__answer { max-width: none; }

/* `.facts` declared `repeat(4, 1fr)` - four tracks for the four facts it was designed
   around - but `location` is still unanswered by the client, so only THREE boxes render
   and the fourth track sat empty, 287px of it, directly under the booking buttons.
   The SECOND instance of this bug found today; `.footer-grid` was the first. auto-fit
   collapses the track nothing fills, and the fourth returns by itself the day a
   location is entered. */
@media (min-width: 47.5em) { .facts { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } }

/* ---- Booking actions at the top of the tour ---------------------------- */
.tour-head__actions { display: flex; flex-wrap: wrap; gap: var(--rd-sp-3); }
.tour-head__actions .btn { flex: 1 1 auto; }
@media (min-width: 35em) { .tour-head__actions .btn { flex: 0 0 auto; } }

/* Declared AFTER `.btn` (line ~9) so it wins on source order - both are (0,1,0)
   and specificity cannot separate them. */
.btn--lg { min-height: 56px; padding: 0 var(--rd-sp-6); }

/* ---- The booking section that replaced the rail ------------------------ */
/* The BASE `.price` is `display: grid` (stacked, for a card), so dropped in here
   it put "per person" on its own line under the figure. Same flex-baseline row as
   `.tour-head__price .price` - both are (0,2,0) and scoped, so neither can reach
   the other. */
.tour-enquire .price { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--rd-sp-2); margin: 0; }
.tour-enquire .price__value { font-size: var(--rd-step-3); line-height: 1; }
.tour-enquire .trust { margin-top: var(--rd-sp-5); }
/* `.trust--light` is two columns from 40em up, but here it sits in the enquiry
   card's NARROW left column, where two columns wrapped every line in half and
   left the third item alone on a row. One column, whatever the viewport. */
.tour-enquire .trust--light { grid-template-columns: minmax(0, 1fr); }

/* ---- Booking modal ------------------------------------------------------
   A native <dialog>: the focus trap, Escape-to-close and the backdrop come with
   it, where the mobile nav drawer had to build all three by hand. Closed, it is
   display:none, so it costs a visitor with no JS nothing at all - the button
   that opens it is an ordinary anchor to #enquire. */
.rd-modal {
  /* A modal <dialog> is centred ONLY by the UA stylesheet's `margin: auto`, and
     base.css:5 `* { margin: 0 }` wipes it - so without this the panel parks in the
     top-left corner. Nothing errors and the dialog still opens, which is why it
     survived a markup check and only a screenshot caught it. Same family as the
     other "core gives you a box your CSS never styled" bugs in the Gotchas. */
  margin: auto;
  width: min(560px, calc(100vw - 2 * var(--rd-sp-4)));
  max-height: calc(100dvh - 2 * var(--rd-sp-4));
  padding: 0;
  border: 0;
  border-radius: var(--rd-radius-card);
  background: var(--rd-white);
  color: var(--rd-slate);
  box-shadow: var(--rd-shadow-lg);
  overflow: auto;
  overscroll-behavior: contain;
}
.rd-modal::backdrop { background: rgba(40, 25, 20, .55); }
.rd-modal__panel { position: relative; padding: clamp(20px, 3vw, 32px); }
.rd-modal__title { margin: 0 var(--rd-sp-8) 0 0; font-size: var(--rd-step-2); }
/* Same stacked-`.price` problem as the booking section: the spans need an explicit
   baseline row or "per person" drops under the figure. */
.rd-modal__price {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--rd-sp-2); margin: var(--rd-sp-3) 0 0;
}
.rd-modal__price .price__value { font-size: var(--rd-step-2); line-height: 1; }
.rd-modal form { margin-top: var(--rd-sp-5); }
.rd-modal__alt { margin-top: var(--rd-sp-4); color: var(--rd-slate-600); }
.rd-modal__close {
  position: absolute; top: var(--rd-sp-2); right: var(--rd-sp-2);
  width: 44px; height: 44px;
  display: grid; place-items: center;
  padding: 0; border: 0; border-radius: var(--rd-radius-pill);
  background: none; color: var(--rd-slate-600); cursor: pointer;
}
.rd-modal__close:hover { background: var(--rd-sand-200); color: var(--rd-espresso); }

/* Scroll-lock the page behind the modal. On <html>, not <body>: `overflow` on
   body would hide real sideways overflow from every checker, and the browser
   keeps the scroll position by itself, so nothing has to call scrollTo on close
   (which would hit the `scroll-behavior: smooth` trap). */
html.rd-modal-open { overflow: hidden; }

/* The trust row was two columns because it lived in a ~740px body column. At the
   full 1136px its four short lines left the right third of the row empty. auto-fit
   gives four across where there is room and falls back on its own below. */
@media (min-width: 64em) {
  .tour-head .trust--light { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
}

/* =========================================================================
   HEADER UTILITY STRIP (11 Nov 2026)
   The one piece of header real estate none of the five competitors occupies
   (probed 11 Nov: extremeadventuresdubai, mavericksafari, platinum-heritage,
   arabian-adventures, raynatours - zero utility strips between them). It also
   puts the BRAND's rust at the top of the page, where previously the loudest
   element in the header was WhatsApp's green #25D366 - another company's colour.

   NOT STICKY, and outside `.site-header` on purpose: inside, its height would
   add to the sticky bar permanently, and both `html { scroll-padding-top: 96px }`
   (base.css - every in-page anchor, including the #enquire fallback) and the
   homepage hero's `margin-top: -96px` are measured off that bar. See the block's
   PHP comment.
   ========================================================================= */
.utility-strip {
  background: var(--rd-rust-700);
  color: #fff;
  font-size: var(--rd-step--1);
}
.utility-strip__inner {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--rd-sp-2) var(--rd-sp-5);
  /* No padding-block: the links' own 44px min-height sets the bar from 48em up, and
     padding would stack on top of it (54px, not 44). On a phone the links are hidden
     and min-height alone gives the 34px band. */
  min-height: 34px;
}
.utility-strip p { margin: 0; display: flex; align-items: center; gap: var(--rd-sp-2); flex-wrap: wrap; }
.utility-strip__contact { gap: var(--rd-sp-5); }
.utility-strip .icon { width: 15px; height: 15px; flex: none; }
/* min-height 44px is NOT decoration: check-responsive measures a link's HEIGHT for
   WCAG 2.5.5, and at the strip's natural line height these anchors came out 22-23px
   and failed at 768/834/844/1024. It is what sets the strip's height from 48em up -
   34px on a phone, where the links are hidden and there is nothing to tap. */
.utility-strip a {
  color: #fff; text-decoration: none;
  display: inline-flex; align-items: center; gap: var(--rd-sp-2);
  min-height: 44px;
}
.utility-strip a:hover { text-decoration: underline; }
.utility-strip__sep { opacity: .55; }
/* The `ph` marker is an underline on a light background; on rust it needs to read
   as white too, or an unconfirmed number looks like a rendering fault. */
.utility-strip .ph { color: inherit; }

/* NARROW: the strip's job here is the trust line only. Both numbers are already one
   tap away in the sticky bottom action bar, so repeating them costs a wrapped second
   row to say nothing new. */
/* SCOPED to (0,2,0) deliberately: `.utility-strip p` above is (0,1,1) and sets
   `display: flex`, so the bare `.utility-strip__contact` (0,1,0) lost and the numbers
   stayed on screen, wrapping the strip to 94px on a phone. Measured, not assumed.
   FIFTH time this trap has bitten in this project - scope a modifier whenever the rule
   it modifies is itself scoped. */
/* BREAKPOINT MOVED 47.99em -> 63.99em, 11 Nov. At 48em the contact links came back but
   did not FIT: measured 74px (two rows) at 768, 820, 834 and 900, settling to 44px only
   at 1024. 48em was where they stopped overlapping, not where they belonged. */
@media (max-width: 63.99em) {
  .utility-strip .utility-strip__contact { display: none; }
  .utility-strip__inner { justify-content: center; }
}

/* PHONE: the trust claim, and nothing else.
   THE BUG THIS FIXES (measured 11 Nov at 320/360/390/430/480, all identical): the
   from-price was added to the trust paragraph AFTER the rule above was written, so the
   phone strip carried two items that cannot share a line - the claim is 216px and the
   price 257px against a 288px content box at 320. It wrapped, the strip went to 53px
   instead of 34, and the SEPARATOR was left stranded at the end of the first line as a
   dot with nothing after it. That trailing dot is what reads as broken.
   Hiding every span after the first also future-proofs it: the `hours` field is blank
   today, and the day someone fills it the phone strip stays one line instead of
   silently regrowing. `__sep` is hidden by name as well, so an empty trust claim cannot
   leave a lone dot behind. */
@media (max-width: 47.99em) {
  .utility-strip__trust > span:not(:first-of-type),
  .utility-strip__sep { display: none; }
}

/* =========================================================================
   TOUR GALLERY - AUTOMATIC CROSS-FADE (11 Nov 2026)
   The grid above is unchanged; this only makes each frame cycle through every
   photo of the tour. No arrows, no dots, no play/pause - the user's call, and
   dots could not work anyway with three photos on screen at once.
   ========================================================================= */

/* Each frame stacks all the tour's photos and shows one. `.gallery figure` is
   already `position: relative; overflow: hidden`, so the box - and therefore the
   alignment - is unchanged whether one photo or five sit inside it. */
.gallery__frame > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0;
  transition: opacity 900ms var(--rd-ease);
}
.gallery__frame > img.is-on { opacity: 1; }

/* The first photo of each frame must be painted before JS runs, or a visitor with
   no JS gets three empty sand-coloured boxes. `:first-child` is the one the PHP
   marks `is-on`, so this and the class agree; the class is what JS then moves. */
.gallery__frame > img:first-child { opacity: 1; }
.gallery__frame > img:first-child:not(.is-on) { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  /* ui.js does not start the rotation at all here, so there is nothing to fade -
     but kill the transition too in case a frame is mid-fade when the preference
     changes. */
  .gallery__frame > img { transition: none; }
}

/* ---- Gallery: enlarge + the pause control ------------------------------
   THE PAUSE BUTTON IS BACK, BUT NOT WHERE IT WAS. It was removed on request when
   it sat in a control bar UNDER the photos; that left a WCAG 2.2.2 gap (content
   that updates itself past five seconds must be stoppable). This one lives in the
   corner of the gallery at opacity 0 and appears only on hover or keyboard focus,
   so at rest the gallery looks exactly as approved - while still being present and
   focusable, which is what the criterion actually asks for. */
.gallery, .tour-hero-wrap { position: relative; }
.tour-hero-wrap { margin-top: var(--rd-sp-5); }
.tour-hero-wrap > .tour-hero { margin-top: 0; }

/* The whole frame is the enlarge target - a transparent button over the photo
   rather than a visible affordance, so the grid is unchanged. */
.gallery__zoom {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  padding: 0; border: 0; background: none;
  cursor: zoom-in;
}
.gallery__zoom:focus-visible { outline: 3px solid var(--rd-rust); outline-offset: -3px; }

.gallery__controls { position: absolute; right: var(--rd-sp-3); bottom: var(--rd-sp-3); z-index: 2; }
.gallery__ctl {
  width: 44px; height: 44px;          /* tap-target floor, not a look */
  display: grid; place-items: center;
  padding: 0; border: 1px solid rgba(255,255,255,.55);
  border-radius: var(--rd-radius-pill);
  background: rgba(30,18,14,.55); color: #fff;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  cursor: pointer;
  opacity: 0; transition: opacity var(--rd-fast) var(--rd-ease);
}
.gallery:hover .gallery__ctl,
.gallery:focus-within .gallery__ctl,
.tour-hero-wrap:hover .gallery__ctl,
.tour-hero-wrap:focus-within .gallery__ctl,
.gallery__ctl:focus-visible { opacity: 1; }
/* A touch screen has no hover, so the control would never appear. Show it always. */
@media (hover: none) { .gallery__ctl { opacity: 1; } }

/* ---- Lightbox ----------------------------------------------------------
   Native <dialog> again - focus trap, Escape and backdrop for free. Arrows ARE
   present here, which does not contradict removing them from the gallery: in a
   lightbox they are the only way between photos, and this is not the designed page. */
.rd-lightbox {
  margin: auto;                        /* base.css `* { margin: 0 }` kills the UA's
                                          centring - the same trap as .rd-modal */
  width: min(1200px, calc(100vw - 2 * var(--rd-sp-4)));
  max-height: calc(100dvh - 2 * var(--rd-sp-4));
  padding: 0; border: 0; overflow: visible;
  background: none;
}
.rd-lightbox::backdrop { background: rgba(20, 12, 9, .88); }
.rd-lightbox__fig { margin: 0; }
.rd-lightbox__fig img {
  display: block; width: 100%; height: auto;
  /* Leave room for the counter UNDER the photo. With the dialog's own max-height
     here too, the image ate the whole box: the "2 of 5" line was pushed off-screen
     and the close button, positioned above the figure, was clipped by the viewport.
     48px covers the counter line plus its margin. */
  max-height: calc(100dvh - 2 * var(--rd-sp-4) - 48px);
  object-fit: contain;                 /* contain, not cover - the point of opening
                                          it is to see the WHOLE photograph */
  border-radius: var(--rd-radius-card);
  background: var(--rd-espresso);
}
.rd-lightbox__close,
.rd-lightbox__arrow {
  position: absolute;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  padding: 0; border: 1px solid rgba(255,255,255,.5);
  border-radius: var(--rd-radius-pill);
  background: rgba(20,12,9,.6); color: #fff;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  cursor: pointer;
}
.rd-lightbox__close:hover,
.rd-lightbox__arrow:hover { background: rgba(20,12,9,.9); }
/* INSIDE the frame at every width. Floating it above the photo put it off-screen
   whenever the dialog filled the viewport, which is most of the time. The dark
   translucent fill and blur keep it legible over any photograph. */
.rd-lightbox__close { top: var(--rd-sp-3); right: var(--rd-sp-3); z-index: 2; }
.rd-lightbox__arrow { top: 50%; transform: translateY(-50%); }
.rd-lightbox__arrow--prev { left: var(--rd-sp-3); }
.rd-lightbox__arrow--next { right: var(--rd-sp-3); }
.rd-lightbox__arrow--prev .icon { transform: rotate(90deg); }
.rd-lightbox__arrow--next .icon { transform: rotate(-90deg); }
.rd-lightbox__count {
  margin: var(--rd-sp-3) 0 0; text-align: center;
  color: var(--rd-on-dark); font-size: var(--rd-step--1);
}

/* ---- Header: from-price in the strip, and a SAND bar ------------------- */
/* The price is the second thing in the strip none of the five competitors does
   (the first being the strip itself). `b` carries the weight; the surrounding
   words stay regular so it reads as a sentence, not a shouty badge. */
.utility-strip__price b { font-weight: 650; }

/* SAND, NOT WHITE. All five competitors run a white or transparent bar. Sand is
   the brand's own surface token and, with the rust strip above it, the top of the
   page is finally in Red Dunes' palette rather than the generic white + WhatsApp
   green every Dubai operator ships. Only the SOLID bar changes: pages with a photo
   hero keep the frosted-glass treatment, which has to stay translucent to let the
   photograph through. */
.site-header { background: rgba(248, 242, 237, .92); }

/* =========================================================================
   LANGUAGE SWITCHER (12 Nov 2026)
   Two placements, because the header has two shapes:
     --strip  the utility strip, desktop only. The strip's contact links are already
              display:none below 64em, so this follows them and the measured 34px
              phone band is untouched.
     --panel  inside the burger nav panel, which is the only header chrome on a phone.
   ========================================================================= */

/* The strip's inner is `justify-content: space-between` sized for TWO children. A third
   would have parked the contact links in the middle. An auto margin on the first child
   absorbs the free space instead, so contact + switcher stay together on the right and
   the existing rule is left exactly as it was measured. */
.utility-strip__inner > .utility-strip__trust { margin-right: auto; }

/* `:not(.lang-drop)` is load-bearing. The header dropdown carries BOTH classes, so
   without it this made its menu a horizontal flex row and every language name wrapped
   to one letter per line. Seen on a screenshot, not in any probe - the markup and the
   counts were correct throughout. */
.lang-switch:not(.lang-drop) ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; align-items: center; gap: var(--rd-sp-2);
}
.lang-switch abbr { text-decoration: none; border: 0; font-variant: all-small-caps; letter-spacing: .04em; }

.lang-switch--strip a {
  color: #fff; opacity: .72;
  padding-inline: 2px;
  min-height: 44px;              /* same WCAG 2.5.5 floor as the strip's other links */
  display: inline-flex; align-items: center;
}
.lang-switch--strip a:hover { opacity: 1; text-decoration: underline; }
.lang-switch--strip a[aria-current] { opacity: 1; font-weight: 650; text-decoration: underline; }
.lang-switch--strip li + li { border-inline-start: 1px solid rgba(255, 255, 255, .35); padding-inline-start: var(--rd-sp-2); }

.lang-switch--panel ul { flex-wrap: wrap; gap: var(--rd-sp-2) var(--rd-sp-3); }
.lang-switch--panel a {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 44px;
  padding-inline: var(--rd-sp-3);
  border: 1px solid var(--rd-line, rgba(0, 0, 0, .18));
  border-radius: var(--rd-radius-sm, 6px);
  text-decoration: none; color: inherit;
}
.lang-switch--panel a[aria-current] { border-color: currentColor; font-weight: 650; }

/* Below 64em the strip carries the trust claim and nothing else - the switcher moves
   into the nav panel. Resetting the auto margin lets the existing centring rule win. */
@media (max-width: 63.99em) {
  .utility-strip .lang-switch--strip { display: none; }
  .utility-strip__inner > .utility-strip__trust { margin-right: 0; }
}

/* The "what every tour includes" strip: an uppercase, letter-spaced label in a flex
   row. German labels are longer than the English ones they were measured with, and a
   flex item will not shrink below its content, so the row pushed the page sideways at
   280px. Wrapping plus min-width:0 lets the label use a second line instead. */
.strip__label { flex-wrap: wrap; }
/* Every child is a GRID item of .strip__item, so each defaults to min-width:auto and
   will not shrink below its longest word. min-width:0 is what actually lets the
   column hold them; overflow-wrap alone does not. */
.strip__item > * { min-width: 0; }

/* ---- Language switcher in the MAIN HEADER BAR (12 Nov 2026) -------------
   Moved here from the utility strip at the client's request ("as the competitors
   website"). The strip is display:none below 64em, so a switcher living there was
   invisible to exactly the visitors most likely to need it. In the header bar it sits
   beside the WhatsApp button on desktop; below 48em the bar is already logo + burger
   + CTA, so it hands over to the copy inside the burger panel rather than crowding
   a phone header. */
/* The header control is a DROPDOWN showing the CURRENT language, which is the pattern
   the client asked for ("as the competitors website"). The burger panel keeps the flat
   list - a dropdown inside an already-open drawer is a second layer for no gain. */
.lang-drop { position: relative; }
.lang-drop__btn {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; padding-inline: var(--rd-sp-3);
  background: none; border: 1px solid transparent; border-radius: var(--rd-radius-sm, 6px);
  font: inherit; font-size: var(--rd-step--1); font-weight: 650;
  color: var(--rd-slate-600); cursor: pointer; white-space: nowrap;
}
.lang-drop__btn:hover { color: var(--rd-rust); border-color: var(--rd-line); }
.lang-drop__btn .icon { width: 17px; height: 17px; flex: none; }
.lang-flag { width: 20px; height: 14px; flex: none; border-radius: 2px; object-fit: cover;
             box-shadow: 0 0 0 1px rgba(0, 0, 0, .09); }
.lang-drop__now { letter-spacing: .04em; }
.lang-drop__caret { width: 13px; height: 13px; transition: transform var(--rd-base) var(--rd-ease); }
.lang-drop.is-open .lang-drop__caret { transform: rotate(180deg); }
/* `display: block` is needed to beat the flat-list `display: flex` above - but it ALSO
   beats the UA's `[hidden] { display: none }`, which is a plain element-less rule. So the
   menu stayed painted whatever the attribute said and the dropdown never closed. The
   [hidden] rule below has to come after it and be at least as specific.
   LESSON: setting `display` on anything that is toggled with the `hidden` attribute
   silently disables `hidden`. Test what is PAINTED, not what the attribute says - the
   first test here checked `hasAttribute('hidden')` and passed while the menu was on
   screen the whole time. */
.lang-drop__menu[hidden] { display: none; }
.lang-drop__menu {
  display: block; position: absolute; z-index: 130; top: calc(100% + 6px); inset-inline-end: 0;
  min-width: 11rem; margin: 0; padding: 6px; list-style: none;
  background: var(--rd-white); border: 1px solid var(--rd-line);
  border-radius: var(--rd-radius-card); box-shadow: var(--rd-shadow-lg);
}
.lang-drop__menu a {
  display: flex; align-items: center; gap: 8px; min-height: 44px;
  padding-inline: var(--rd-sp-3); border-radius: var(--rd-radius-sm, 6px);
  color: var(--rd-espresso); text-decoration: none; font-size: var(--rd-step--1);
}
.lang-drop__menu b { font-weight: 700; letter-spacing: .04em; }
.lang-drop__menu span { color: var(--rd-slate-400); }
.lang-drop__menu a:hover { background: var(--rd-sand); color: var(--rd-rust); }
.lang-drop__menu a[aria-current] { font-weight: 800; background: var(--rd-sand); }
@media (max-width: 47.99em) { .lang-switch--header { display: none; } }

/* ---- Tour variants: the size / engine picker (12 Nov 2026) --------------
   Only renders on a tour that has a `variants` field, so the four safari packages
   are untouched. */
.variants { margin-top: var(--rd-sp-7); }
.variants h2 { font-size: var(--rd-step-2); margin-bottom: var(--rd-sp-4); }
.variants__pills { display: flex; flex-wrap: wrap; gap: var(--rd-sp-2); margin-bottom: var(--rd-sp-5); }
.variant-pill {
  min-height: 44px; padding: 0 var(--rd-sp-4);
  border: 1px solid var(--rd-line); border-radius: 999px;
  background: var(--rd-white); color: var(--rd-espresso);
  font: inherit; font-weight: 650; cursor: pointer; min-width: 0;
  transition: background var(--rd-base) var(--rd-ease), border-color var(--rd-base) var(--rd-ease);
}
.variant-pill:hover { border-color: var(--rd-rust); color: var(--rd-rust); }
.variant-pill.is-on { background: var(--rd-rust); border-color: var(--rd-rust); color: #fff; }
.variant-panel__capacity {
  display: flex; align-items: center; gap: 8px; margin: 0 0 var(--rd-sp-4);
  color: var(--rd-slate-600); font-weight: 650;
}
.variant-panel__capacity .icon { color: var(--rd-rust); width: 18px; height: 18px; flex: none; }
.variant-prices { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--rd-sp-2); }
.variant-prices li {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--rd-sp-4);
  padding: var(--rd-sp-3) var(--rd-sp-4); min-width: 0;
  background: var(--rd-sand); border-radius: var(--rd-radius-card);
}
.variant-prices__label { color: var(--rd-slate-600); min-width: 0; }
.variant-prices b { font-size: var(--rd-step-1); color: var(--rd-espresso); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* A photo belonging to another variant: out of the cycle, not just invisible. */
.gallery img.is-variant-off, .tour-hero img.is-variant-off { display: none; }

/* ---- Gallery thumbnails: the PHONE layout (12 Nov 2026) -----------------
   On a phone the 3-up grid became three tall stacked boxes - three screens of scrolling
   to see three photos of one tour. Below 64em it is now one main image with a thumbnail
   strip, the shape a shopper expects from a product page. The strip still auto-advances;
   the thumbs mark the photo that is up and jump to one on tap.
   From 64em the desktop 3-up grid is unchanged and the strip is hidden. */
.gallery__thumbs { display: none; }
@media (max-width: 63.99em) {
  /* frames 2 and 3 are the other two of the 3-up grid - one main image on a phone */
  .gallery > .gallery__frame:nth-of-type(n + 2) { display: none; }
  .gallery__thumbs {
    display: flex; gap: var(--rd-sp-2); margin-top: var(--rd-sp-2);
    overflow-x: auto; scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .gallery__thumbs::-webkit-scrollbar { display: none; }
  .gallery__thumb {
    flex: 0 0 auto; width: 72px; aspect-ratio: 16 / 10;
    padding: 0; border: 2px solid transparent; border-radius: var(--rd-radius-sm, 6px);
    background: var(--rd-sand-200); overflow: hidden; cursor: pointer;
    scroll-snap-align: start; opacity: .62;
    transition: opacity var(--rd-base) var(--rd-ease), border-color var(--rd-base) var(--rd-ease);
  }
  .gallery__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .gallery__thumb.is-on { opacity: 1; border-color: var(--rd-rust); }
  .gallery__thumb.is-variant-off { display: none; }
}

/* ---- Header layout: logo left, nav CENTRED, language + WhatsApp right ----
   Client's request, 12 Nov 2026. `.logo` already carries `margin-right: auto`; giving
   the nav an auto margin on BOTH sides parks it in the middle of the space between the
   logo and the right-hand group, and leaves the language dropdown and the WhatsApp
   button together on the right. Only from 64em, which is where the inline nav exists
   at all - below that the burger owns that slot. Flex, not grid, on purpose: the mobile
   drawer is a flex item of this same bar and its height behaviour was measured once
   already (see the `.header-bar > .logo` note above). */
@media (min-width: 64em) {
  .site-header .nav { margin-inline: auto; }
  .site-header .nav > ul { justify-content: center; }
}

/* Restated last: `.lang-drop__menu[hidden]` (0,2,0) beats `.lang-drop__menu` (0,1,0), but
   keeping it at the end of the file as well makes the intent impossible to miss. */
.lang-drop__menu[hidden] { display: none; }
