/*! Phase 2026-W2 — tgz-unified-2026.bundle.css
 *  Wave-2 design-system unification layer. Registered LAST in
 *  data/assets_registry.json and in the inc/asset_registry.php fallback
 *  manifest, so it wins the cascade on load order without needing extreme
 *  specificity.
 *
 *  Scope (deliberately narrow — this file owns FOUR things and nothing else):
 *    1. one canonical .btn / .btn.primary component driven by --tgz-ui-* tokens
 *    2. a WCAG 2.1 SC 1.4.11 compliant :focus-visible ring (>= 3:1)
 *    3. a theme-agnostic 44px tap-target floor for the shared control classes
 *    4. .tgzUtilityByline (an inline-styled byline expects this class)
 *  plus a no-JS / preflight-failure dark-mode fallback.
 *
 *  It is opt-out-safe: every rule either sets :focus-visible state only, or
 *  raises a minimum, or is scoped to a class that has no other definition.
 *  The optional body class .tgzUnified2026 (emitted by tgz_ui_body_class() in
 *  inc/ui.php and by the header bootstrap) selects the stronger, !important
 *  variants; the base rules below work with or without it.
 */

/* ------------------------------------------------------------------ *
 * 0. Colour-scheme + no-JS dark fallback.
 *    inc/head.php's preflight always stamps data-theme, so the media
 *    query only ever fires when JS is disabled or the preflight threw.
 * ------------------------------------------------------------------ */
html { color-scheme: light dark; }
html[data-theme="light"] { color-scheme: light; }
html[data-theme="dark"] { color-scheme: dark; }

/* Owner policy 2026-08-04: light is the site default. This block now only
   catches the no-JS / preflight-failed case, where html carries no data-theme. */
@media (prefers-color-scheme: dark) {
  html:not([data-theme]):not([data-theme-mode]) {
    color-scheme: dark;
    --tgz-ui-bg: #071015;
    --tgz-ui-surface: #0d1a22;
    --tgz-ui-surface-alt: #132530;
    --tgz-ui-card: #101f29;
    --tgz-ui-text: #f7efe3;
    --tgz-ui-muted: #b7c2c4;
    --tgz-ui-subtle: #c7aa7a;
    --tgz-ui-line: #29404b;
    --tgz-ui-line-strong: #4b6a73;
    --tgz-ui-primary: #71d7c2;
    --tgz-ui-primary-hover: #95e7d3;
    --tgz-ui-primary-text: #071015;
    --tgz-ui-secondary: #182a35;
    --tgz-ui-secondary-text: #f7efe3;
    --tgz-ui-accent: #d58b43;
    --tgz-ui-accent-hover: #e4a664;
    --tgz-ui-accent-text: #0a1116;
    --tgz-ui-teal: #59c9bd;
    --tgz-ui-focus: #71d7c2;
  }
  html:not([data-theme]) body {
    background: var(--tgz-ui-bg);
    color: var(--tgz-ui-text);
  }
}

/* ------------------------------------------------------------------ *
 * 1. Canonical button component.
 *    One accent, taken from the corrected tokens. Only applied under the
 *    opt-in body class so that legacy per-phase .btn.primary rules keep
 *    their current appearance on any route that has not opted in yet.
 * ------------------------------------------------------------------ */
body.tgzUnified2026 .btn,
body.tgzUnified2026 a.btn,
body.tgzUnified2026 button.btn {
  min-height: var(--tgz-ui-control-min, 44px);
  border-radius: var(--tgz-ui-control-radius, 15px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-align: center;
  cursor: pointer;
  touch-action: manipulation;
  transition:
    background var(--tgz-ui-motion-fast, 120ms) var(--tgz-ui-motion-ease, ease),
    border-color var(--tgz-ui-motion-fast, 120ms) var(--tgz-ui-motion-ease, ease),
    color var(--tgz-ui-motion-fast, 120ms) var(--tgz-ui-motion-ease, ease),
    transform var(--tgz-ui-motion-fast, 120ms) var(--tgz-ui-motion-ease, ease);
}

body.tgzUnified2026 .btn.primary {
  background: var(--tgz-ui-primary) !important;
  color: var(--tgz-ui-primary-text) !important;
  border: 1px solid var(--tgz-ui-primary) !important;
}
body.tgzUnified2026 .btn.primary:hover {
  background: var(--tgz-ui-primary-hover) !important;
  border-color: var(--tgz-ui-primary-hover) !important;
}
body.tgzUnified2026 .btn.secondary {
  background: var(--tgz-ui-secondary) !important;
  color: var(--tgz-ui-secondary-text) !important;
  border: 1px solid var(--tgz-ui-line) !important;
}
body.tgzUnified2026 .btn.accent {
  background: var(--tgz-ui-accent) !important;
  color: var(--tgz-ui-accent-text) !important;
  border: 1px solid var(--tgz-ui-accent) !important;
}
body.tgzUnified2026 .btn.accent:hover {
  background: var(--tgz-ui-accent-hover) !important;
  border-color: var(--tgz-ui-accent-hover) !important;
}
body.tgzUnified2026 .btn.ghost {
  background: transparent !important;
  color: var(--tgz-ui-text) !important;
  border: 1px solid var(--tgz-ui-line) !important;
}
body.tgzUnified2026 .btn[disabled],
body.tgzUnified2026 .btn[aria-disabled="true"] {
  opacity: .55;
  cursor: not-allowed;
  transform: none !important;
}

/* ------------------------------------------------------------------ *
 * 2. Focus ring — WCAG 2.1 SC 1.4.11 (non-text contrast >= 3:1).
 *    Light  #0c7a76 on #fbf4e8 page  = 4.87:1
 *    Dark   #71d7c2 on #071015 page  = 11.15:1
 *    Only ever paints on :focus-visible, so no resting-state layout can
 *    change. Wins over public-shell-2026 (:where, 0,1,0) and core-post
 *    (:is,  0,2,0) on load order + !important.
 * ------------------------------------------------------------------ */
:where(a, button, input, select, textarea, summary, [role="button"], [tabindex]):focus-visible {
  outline: 3px solid var(--tgz-ui-focus, #0c7a76) !important;
  outline-offset: 2px !important;
  box-shadow: 0 0 0 5px rgba(12, 122, 118, .22) !important;
}
html[data-theme="dark"] :where(a, button, input, select, textarea, summary, [role="button"], [tabindex]):focus-visible {
  outline-color: var(--tgz-ui-focus, #71d7c2) !important;
  box-shadow: 0 0 0 5px rgba(113, 215, 194, .26) !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme]) :where(a, button, input, select, textarea, summary, [role="button"], [tabindex]):focus-visible {
    outline-color: #71d7c2 !important;
    box-shadow: 0 0 0 5px rgba(113, 215, 194, .26) !important;
  }
}
/* Never remove the indicator for people who cannot see it move. */
@media (prefers-reduced-motion: reduce) {
  body.tgzUnified2026 .btn { transition: none !important; }
}

/* ------------------------------------------------------------------ *
 * 3. Theme-agnostic 44px tap-target floor.
 *    NO !important on purpose: the existing dark-only
 *    `html[data-theme="dark"] … {min-height:44px!important}` rules in
 *    theme-phase407.bundle.css still win, so dark mode is byte-identical
 *    and light mode converges upward to the same 44px instead of the
 *    32/34/35/36/37/38px per-bundle values.
 *    .flightChip is deliberately excluded — it sits in dense horizontal
 *    rails on /flights/ and raising it changes the rail height. Tracked
 *    in the design brief as the last item of the roll-out.
 * ------------------------------------------------------------------ */
/* Tier A — controls whose owning route bundle already declares a smaller
   min-height (32/34/35/36/37/38px depending on bundle and breakpoint). A bare
   `:where(...)` (specificity 0,0,0) would lose to those class rules and do
   nothing, so this tier uses `body :is(...)` (0,1,1) to beat a single class
   selector — and still no !important, so theme-phase407's dark-mode
   `html[data-theme="dark"] body :where(…){min-height:44px!important}` blocks
   continue to win and dark mode stays byte-identical. Light mode therefore
   converges UP to the dark value instead of down to the bundle value. */
body :is(.bfBtn,.thingsBtn,.tripsBtn,.tgzStayMoneyBtn,.flightTab,.tgzCommercialIntentBtn,.tgzProfitCta,.tgzProfitGhost,.surfSmartBtn,.oceanLockButton,.tgzShareButton) {
  min-height: 44px;
}

/* Tier B — the generic controls. Kept at `:where(...)` (0,0,0) on purpose: it
   raises the floor only where nothing else has an opinion, so no existing
   layout that deliberately sizes a .btn can shift.
   `.flightChip` is deliberately absent from BOTH tiers: it sits at 32px inside
   dense horizontal scroll rails on /flights/ and /flights-to-agadir-from/*, and
   raising it changes the rail height at two breakpoints. The design brief flags
   it as the last item of the roll-out — it needs a visual diff first. */
:where(.btn, button[type="submit"]) {
  min-height: 44px;
}

/* ------------------------------------------------------------------ *
 * 4. .tgzUtilityByline — inc/article_utility.php renders this section with
 *    an inline-styled <p>; the class had no rule anywhere. Styling the
 *    section (not the <p>) leaves the inline declarations untouched.
 * ------------------------------------------------------------------ */
.tgzUtilityByline {
  margin: clamp(10px, 1.6vw, 16px) 0;
  padding: clamp(8px, 1.2vw, 12px) clamp(10px, 1.6vw, 14px);
  border: 1px solid var(--tgz-ui-line, #e4d4bf);
  border-radius: var(--tgz-ui-radius-sm, 12px);
  background: var(--tgz-ui-surface, #fffaf1);
  color: var(--tgz-ui-muted, #647083);
  font-size: .9rem;
  line-height: 1.45;
}
.tgzUtilityByline strong { color: var(--tgz-ui-text, #172235); font-weight: 650; }
.tgzUtilityByline a { color: var(--tgz-ui-teal, #0c7a76); text-decoration: underline; text-underline-offset: 2px; }
.tgzUtilityByline time { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ *
 * 5. Booking-partner trust strip (homepage). Text-chip wordmarks only —
 *    no third-party logo images are hotlinked.
 * ------------------------------------------------------------------ */
.tgzPartnerStrip {
  margin: clamp(18px, 3vw, 34px) 0 0;
  padding: clamp(14px, 2vw, 20px) clamp(14px, 2.2vw, 22px);
  border: 1px solid var(--tgz-ui-line, #e4d4bf);
  border-radius: var(--tgz-ui-radius-md, 18px);
  background: var(--tgz-ui-surface, #fffaf1);
}
.tgzPartnerStrip__head {
  display: block;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--tgz-ui-muted, #647083);
  margin: 0 0 10px;
}
.tgzPartnerStrip__list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.tgzPartnerStrip__chip {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 6px 12px;
  border: 1px solid var(--tgz-ui-line, #e4d4bf);
  border-radius: var(--tgz-ui-radius-pill, 999px);
  background: var(--tgz-ui-card, #fff);
  color: var(--tgz-ui-text, #172235);
  font-size: .82rem;
  font-weight: 650;
  letter-spacing: .005em;
  white-space: nowrap;
}
.tgzPartnerStrip__chip--logo {
  padding: 5px 14px;
  /* Wave A.1b (law #22): logo chips sit on a light surface in BOTH themes so any
     dark-ink partner mark stays legible; the img box is fixed at 24px for zero CLS. */
  background: #fff;
}
.tgzPartnerStrip__chip--logo img {
  display: block;
  height: 24px;
  width: auto;
  max-width: 120px;
  object-fit: contain;
}
html[data-theme="dark"] .tgzPartnerStrip__chip--logo { background: #f4f6f8; border-color: rgba(255,255,255,.18); }
.tgzPartnerStrip__note {
  margin: 10px 0 0;
  font-size: .78rem;
  line-height: 1.45;
  color: var(--tgz-ui-muted, #647083);
}
html[data-theme="dark"] .tgzPartnerStrip { background: var(--tgz-ui-surface, #0d1a22); }

/* ------------------------------------------------------------------ *
 * 6. Header brand lockup — ONE logo, sized to be legible.
 *
 *    OWNER DECISION 2026-08-04: "keep only the one with white text font
 *    and black back". The header BAR stays near-black in both themes and
 *    the light-ink <picture> is gone from inc/header.php, so there is no
 *    swap left to do here — only geometry.
 *
 *    The bug this replaces: the plate was absolutely centred at
 *    left:50%/translate(-50%,-50%) inside a `display:block` nav (Phase
 *    441), while the tools cluster on the right is ~3x wider than the
 *    menu button on the left. Centring therefore had to cap the plate at
 *    a width that still overlapped the pickers (measured at 375px: plate
 *    109..266, tools 252..368) and squeezed the artwork — with
 *    `object-fit:contain` inside a 40px-tall plate the painted lockup was
 *    ~93x23 CSS px.
 *
 *    Fix: on <=980px the nav goes back to a real flex row — menu button,
 *    logo, `margin-left:auto` tools. No absolute positioning, no overlap
 *    at any width, and the plate is free to size to its content. The
 *    lockup then paints at clamp(30px, 9.6vw, 38px) tall (36px at the
 *    375px primary target => ~145px wide, the owner's 140-155px band).
 * ------------------------------------------------------------------ */
.tgz411LogoPic { display: block; }

/* --- Shared: the plate hugs the artwork instead of letterboxing it. --- */
html body .tgz411Header .tgz411Logo,
html body .tgz411Header .tgz411Logo picture {
  width: auto !important;
  height: auto !important;
  max-width: 100% !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  min-width: 0 !important;
}
html body .tgz411Header .tgz411Logo img {
  display: block !important;
  width: auto !important;
  min-width: 0 !important;
  max-width: 100% !important;
  height: 44px !important;
  max-height: 44px !important;
  object-fit: contain !important;
  object-position: center center !important;
}

/* --- Desktop (>=981px): 44px tall lockup, plate hugs it. --- */
@media (min-width: 981px) {
  html body .tgz411Header .tgz411LogoPlate {
    width: auto !important;
    min-width: 0 !important;
    max-width: 100% !important;
    height: auto !important;
    padding: 6px 14px !important;
  }
}

/* --- Mobile / tablet (<=980px): real flex row, no absolute overlap. --- */
@media (max-width: 980px) {
  html body .tgz411Header .tgz411HeaderNav {
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: 8px !important;
    position: relative !important;
    width: 100% !important;
    height: 100% !important;
    direction: ltr !important;
  }
  html body .tgz411Header .tgz411MenuButton,
  html body .tgz411Header .tgz411LogoPlate,
  html body .tgz411Header .tgz411HeaderTools {
    position: relative !important;
    inset: auto !important;
    left: auto !important;
    right: auto !important;
    top: auto !important;
    bottom: auto !important;
    transform: none !important;
    translate: none !important;
    margin: 0 !important;
  }
  /* header-polish-479 re-asserts translate(-50%,-50%) on every interaction
     state of the plate; with static flow that would fling the logo off the
     bar, so it has to be neutralised for the same states. */
  html body .tgz411Header .tgz411LogoPlate:active,
  html body .tgz411Header .tgz411LogoPlate:focus,
  html body .tgz411Header .tgz411LogoPlate:focus-visible,
  html body .tgz411Header .tgz411LogoPlate:hover {
    transform: none !important;
    translate: none !important;
    scale: none !important;
  }
  html body .tgz411Header .tgz411MenuButton {
    flex: 0 0 auto !important;
    width: 44px !important;
    min-width: 44px !important;
    height: 44px !important;
    min-height: 44px !important;
  }
  html body .tgz411Header .tgz411LogoPlate {
    flex: 0 1 auto !important;
    width: auto !important;
    min-width: 0 !important;
    max-width: none !important;
    height: auto !important;
    padding: 5px 10px !important;
    border-radius: 14px !important;
    overflow: hidden !important;
  }
  html body .tgz411Header .tgz411HeaderTools {
    flex: 0 0 auto !important;
    margin-left: auto !important;
    max-width: none !important;
    width: auto !important;
    gap: 4px !important;
  }
  html body .tgz411Header .tgz411Logo img {
    height: clamp(30px, 9.6vw, 38px) !important;
    max-height: 38px !important;
  }
  /* Tap-target floor for the header controls (was 34-38px on phones).
     42px is the largest value that still leaves the 140-155px lockup and
     the 44px menu button on one row at 375px. */
  html body .tgz411Header .tgz411PickerButton,
  html body .tgz411Header .tgz411ThemeButton {
    width: 42px !important;
    min-width: 42px !important;
    max-width: 42px !important;
    height: 42px !important;
    min-height: 42px !important;
    max-height: 42px !important;
    border-radius: 12px !important;
  }
  /* 2026-08-04: raised 11px -> 12px, the owner's stated floor. */
  html body .tgz411Header .tgz411PickerLabel { font-size: 12px !important; }
}

/* Very narrow phones: the flex row shrinks the plate first (flex:0 1 auto
   + max-width:100% on the img), and the currency picker still yields at
   <=340px via header-polish-479. Nothing overlaps at any width. */
@media (max-width: 359px) {
  html body .tgz411Header .tgz411HeaderNav { gap: 6px !important; }
  html body .tgz411Header .tgz411HeaderTools { gap: 3px !important; }
  html body .tgz411Header .tgz411PickerButton,
  html body .tgz411Header .tgz411ThemeButton {
    width: 40px !important;
    min-width: 40px !important;
    max-width: 40px !important;
    height: 40px !important;
    min-height: 40px !important;
  }
}

/* Homepage hero lockup. The <img> declares its intrinsic 564x140 so the browser
   can reserve the box, and CSS drives the painted size — without this the hero
   would paint the bitmap at full intrinsic width. */
.hcHeroBrand { display: block; margin: 0 0 clamp(8px, 1.2vw, 14px); }
.hcHeroBrand img {
  display: block;
  width: auto;
  height: clamp(30px, 4.4vw, 46px);
  max-width: 100%;
}
.hcHeroActions--duo { flex-wrap: wrap; }

/* Footer brand mark. The footer chrome is dark, so the light-ink artwork
   is the correct one there too. (assets/brand/secondary/logo_white.png is a
   1536x1024 raster WITH an opaque white background — it would render as a
   white box on the dark footer, so it is deliberately not used.) */

/* Footer newsletter form (wired to /newsletter_submit.php). */

/* ------------------------------------------------------------------ *
 * 7. MOBILE LEGIBILITY + TAP-TARGET FLOOR (Phase 2026-W2b).
 *
 *    Owner: "focus on mobile view responsive as its our main entry
 *    point for visitors". Measured at 375px on / before this block:
 *    37 interactive elements under 40px tall and 105 visible text
 *    elements under 13px.
 *
 *    Two reasons the existing floors did not hold:
 *      a) --tgz-ui-control-min was 38px, and the <=740px media query
 *         LOWERED it to 36px (fixed at source in tgz-ui-system).
 *      b) ~10 route bundles hard-code a `@media(max-width:760px){…
 *         min-height:36px; font-size:.8rem}` block that overrides the
 *         token entirely on exactly the viewport that needs it most.
 *    This block outranks (b) on load order + `html body :is(…)`
 *    (0,1,2 vs their 0,1,0) without a single !important, so anything
 *    already larger keeps its own value.
 * ------------------------------------------------------------------ */
@media (max-width: 760px) {
  /* --- Interactive: 44px minimum, 13px minimum label. --- */
  html body :is(
    .btn, .button, .pill, .chip, .routeChip,
    .flightBtn, .flightMiniBtn, .flightMiniLink, .flightChip, .flightTab,
    .bfBtn, .bfNext a,
    .thingsBtn, .thingsChip, .thingsTab, .thingsFilter,
    .tripsBtn,
    .surfSmartBtn, .surfSmartChip, .surfSmartFilter, .surfSmartTab,
    .hcBtn, .hcAction, .hcChip, .hcRouteChip,
    .tgzShareButton, .tgzStayMoneyBtn, .tgzCommercialIntentBtn,
    .tgzProfitCta, .tgzProfitGhost, .oceanLockButton,
    .localAppCardCta, .stay22CardCta, .tgzProofRail__actions a
  ) {
    /* !important is required, not cosmetic: theme-phase407.bundle.css ships
       `body :where(.btn,.button,.chip,.tab,.pill,.routeChip,…){min-height:36px
       !important}` — a 0,0,1 selector that no amount of specificity can beat
       without matching its priority. Measured on /where-to-stay/ before this:
       .btn.primary 38px, .tgzShareButton 34px, .localAppCardCta 27px,
       .stay22CardCta 19px. It only ever RAISES a minimum. */
    min-height: 44px !important;
    font-size: max(13px, .8125rem);
  }
  html body :is(.localAppCardCta, .stay22CardCta) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding-block: 8px;
  }

  /* --- Homepage cockpit rails: chips were 30-34px tall at 11-12px. --- */
  html body :is(.hcHeroChips, .hcSpotChips, .hcSurfCardActions) > a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding-block: 8px;
    font-size: max(13px, .8125rem);
    line-height: 1.2;
  }

  /* --- Footer: three separate link families, all 31-36px at ~11px.
         Restored 2026-08-24: a truncated edit had deleted these rules and
         left only the heading. Rewritten against the current footer-2026
         markup. Plain specificity, no !important - the theme-phase407
         35px lock this section once answered has since been removed, so
         late load order + html body is enough, and anything already
         larger keeps its own value. --- */
  html body .tgzF26__links a {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding-block: 10px;
    font-size: max(13px, .8125rem);
  }
  /* .tgzF26 in the selector is required: footer-2026 sets these at
     .tgzF26 .tgzF26__legal a (0,2,1), which outranks a bare html-body
     floor — measured 34px/11.5px surviving until this matched it. */
  html body .tgzF26 :is(.tgzF26__legal a, .tgzF26__legal button) {
    min-height: 44px;
    font-size: max(12.5px, .78rem);
  }
  html body .tgzF26 .tgzF26__legal .tgzF26__social {
    min-width: 44px;
    justify-content: center;
  }

  /* --- Caption / meta floor. Nothing readable drops below 12.5px. --- */
  html body :is(.hcKicker,.hcSurfCardMeta,.hcWeatherSpot,.tpOfferDeal,.tpOfferMeta,.tpOfferView,.tpBlockBadge,.tpDisclaimer,.tpPrice small,.tpRoute small,.tgzPartnerStrip__head,.tgzPartnerStrip__note,.tgzPartnerStrip__chip,.flightTiny,.flightsDealsKicker) {
    font-size: max(12.5px, .78rem);
    line-height: 1.4;
  }
}

/* ------------------------------------------------------------------ *
 * Repair note (2026-08-24). A truncated edit beheaded the docblock
 * that used to sit here: its surviving prose had no opening delimiter,
 * so it parsed as a selector and swallowed the media block below -
 * the caption floor was silently dead. The old prose argued this rule
 * needed !important against hard locks in core-post and a
 * theme-phase407 line 1493 that no longer exists (the file is shorter
 * now, and its only surviving .78rem font lock targets
 * .tgzInternalGraph__copy p, not span). Plain specificity is enough
 * today, so the floor ships without !important.
 * ------------------------------------------------------------------ */
@media (max-width: 760px) {
  html body .tgzInternalGraph__copy span {
    font-size: max(12.5px, .78rem);
  }
}

/* Back-to-top button — the last sub-44px control on the homepage. */
@media (max-width: 760px) {
  html body .tgzBackTop {
    width: 44px !important;
    height: 44px !important;
    min-width: 44px !important;
    min-height: 44px !important;
  }
  html body :is(.hcSurfCardMeta,.hcMetricGrid b,.hcHeroDealsCta__body em,.hcHeroMini span) {
    font-size: max(12.5px, .78rem);
  }
}

/* ------------------------------------------------------------------ *
 * 9. Cookie banner — compact bar on phones (Phase 2026-W2b).
 *
 *    Owner: "permanently overlays the bottom ~12% of mobile viewport
 *    until dismissed". Measured 146px of a 812px viewport (18%) at
 *    375px. This block only changes LAYOUT and TYPE SIZE — the markup
 *    (js/core-base.bundle.js), the three actions, their data-cookie-action
 *    hooks and the whole consent state machine are untouched, so nothing
 *    about what is stored or when changes.
 *
 *    The explanatory sentence is clamped to two lines rather than
 *    removed: it stays in the DOM and in the accessibility tree, and the
 *    "Settings" action (plus /cookies/) still carries the full detail.
 * ------------------------------------------------------------------ */
@media (max-width: 620px) {
  html body .cookieBanner {
    left: 8px;
    right: 8px;
    bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  }
  html body .cookieBannerInner {
    gap: 8px;
    padding: 10px 12px;
    border-radius: 16px;
  }
  html body .cookieBannerText strong { font-size: 14px; line-height: 1.15; }
  html body .cookieBannerText p {
    margin: 3px 0 0;
    font-size: 12.5px;
    line-height: 1.32;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* One row, three equal actions, all still >= 44px tall. */
  html body .cookieBannerActions {
    flex-wrap: nowrap;
    gap: 6px;
  }
  html body .cookieBannerActions .btn {
    flex: 1 1 0;
    min-width: 0;
    /* theme-phase407 locks these at 38px with !important. */
    min-height: 44px !important;
    padding: 8px 6px;
    font-size: 12.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}
/* Very narrow: the title alone carries the message, the sentence yields. */
@media (max-width: 380px) {
  html body .cookieBannerText p { -webkit-line-clamp: 1; line-clamp: 1; }
  html body .cookieBannerActions .btn { font-size: 12px; padding: 8px 4px; }
}

/* Cookie-banner secondary actions: light-theme contrast repair (pre-existing).
   tgz-ui-system.bundle.css paints these buttons `background:#07161c!important`
   in BOTH themes, but public-shell-2026.bundle.css then forces
   `color:#07161c!important` on any non-primary .btn via
   `html:not([data-theme="dark"]):not([data-theme-mode="dark"]) :where(.btn…)`
   (0,2,1 — it outranks the 0,2,0 cookie rule). Result in light mode: 1:1
   contrast, "Settings" and "Reject optional" render as blank pills. Verified
   against HEAD, so this predates Wave 2. 0,2,2 + later load order fixes it. */
html body .cookieBannerActions .btn,
html body .cookieActionRow .btn,
html body .cookieInlineActions .btn,
html body .cookieInlineHead .btn {
  color: #fffaf1 !important;
}
html body .cookieBannerActions .btn.primary,
html body .cookieActionRow .btn.primary,
html body .cookieInlineActions .btn.primary {
  color: var(--tgz-ui-primary-text, #fff) !important;
}

/* ------------------------------------------------------------------ *
 * 10. Hero map sheet (homepage). Markup + behaviour live inline in
 *     index.php; only presentation is here.
 *     Mobile: full-height bottom sheet. Desktop: centred dialog.
 * ------------------------------------------------------------------ */
.hcMapChip {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 44px;
  padding: 8px 14px;
  border-radius: var(--tgz-ui-radius-pill, 999px);
  border: 1px solid rgba(255, 255, 255, .26);
  background: rgba(255, 255, 255, .10);
  color: inherit;
  font: 850 13px/1.15 Inter, system-ui, sans-serif;
  cursor: pointer;
  touch-action: manipulation;
}
.hcMapChip:hover { background: rgba(255, 255, 255, .18); }

/* ------------------------------------------------------------------ *
 * 10b. Coast map card (2026-08-04, owner directive 2).
 *      Replaces the map chip in the hero. Markup: inc/coast_map_card.php.
 *      It has to READ as a map instantly, so the drawn area leads and the
 *      label sits under it as a caption rather than competing with it.
 * ------------------------------------------------------------------ */
.tgzMapCard {
  --tgzCoast-sea-top: #0e5f74;
  --tgzCoast-sea-bottom: #062f3f;
  --tgzCoast-land-top: #1d2b3c;
  --tgzCoast-land-bottom: #2a3a4c;
  --tgzCoast-line: rgba(255, 244, 226, .82);
  --tgzCoast-swell: rgba(190, 235, 240, .34);
  --tgzCoast-road: rgba(255, 216, 156, .5);
  --tgzCoast-ink: #fdf6ea;
  --tgzCoast-pin: var(--tgz-ui-sand, #e6c793);
  --tgzCoast-home: var(--tgz-ui-accent, #a95c1e);
  --tgzCoast-homeRing: #fff4e2;

  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  margin: 14px 0 0;
  padding: 0;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, .26);
  border-radius: var(--tgz-ui-radius-lg, 24px);
  background: var(--tgzCoast-land-top);
  color: var(--tgzCoast-ink);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  box-shadow: 0 16px 38px rgba(4, 14, 22, .34);
  transition: transform var(--tgz-ui-motion-fast, 120ms) var(--tgz-ui-motion-ease, ease),
              border-color var(--tgz-ui-motion-fast, 120ms) var(--tgz-ui-motion-ease, ease);
}
.tgzMapCard:hover { transform: translateY(-1px); border-color: rgba(255, 216, 156, .6); }
.tgzMapCard:focus-visible {
  outline: 3px solid var(--tgz-ui-focus, #0f8f8a);
  outline-offset: 3px;
}
.tgzMapCard__art { display: block; }
.tgzCoastMap { display: block; width: 100%; height: auto; }

.tgzMapCard__label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  padding: 9px 14px 11px;
  border-top: 1px solid rgba(255, 255, 255, .14);
  background: rgba(6, 20, 28, .58);
}
.tgzMapCard__label b {
  font: 850 15px/1.2 Inter, system-ui, sans-serif;
  letter-spacing: -.01em;
}
.tgzMapCard__label em {
  font: 700 13px/1.2 Inter, system-ui, sans-serif;
  font-style: normal;
  opacity: .82;
}
.tgzMapCard__label em::before { content: "— "; }

/* One pin breathes: the village the whole site is about. */
.tgzCoastMap__halo { transform-box: fill-box; transform-origin: center; }
@keyframes tgzCoastPulse {
  0%, 100% { transform: scale(1); opacity: .38; }
  50%      { transform: scale(2.1); opacity: 0; }
}
.tgzCoastMap__halo { animation: tgzCoastPulse 2.8s var(--tgz-ui-motion-ease, ease) infinite; }
@media (prefers-reduced-motion: reduce) {
  .tgzCoastMap__halo { animation: none; opacity: .38; }
  .tgzMapCard { transition: none; }
  .tgzMapCard:hover { transform: none; }
}

/* Compact variant for the town pages' stay sections. */
.tgzMapCard--compact {
  /* Deliberately smaller than the hero card: on a town page the rooms are the
     bold element, and this is the "see them on a map" door next to them. */
  max-width: 320px;
  margin: 14px auto 4px;
  border-color: var(--tgz-ui-line, rgba(0, 0, 0, .14));
  box-shadow: var(--tgz-ui-shadow-soft, 0 12px 34px rgba(23, 34, 53, .1));
}
.tgzMapCard--compact .tgzMapCard__label { padding: 8px 12px 9px; }
.tgzMapCard--compact .tgzMapCard__label b { font-size: 14px; }
.tgzMapCard--compact .tgzMapCard__label em { font-size: 12px; }

@media (max-width: 420px) {
  .tgzMapCard__label { padding: 8px 12px 10px; }
  .tgzMapCard__label b { font-size: 15px; }
}

[dir="rtl"] .tgzMapCard__label { text-align: right; }
[dir="rtl"] .tgzMapCard__label em::before { content: "— "; }

.tgzMapSheet { position: fixed; inset: 0; z-index: 11500; }
.tgzMapSheet[hidden] { display: none; }
.tgzMapSheet__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 16, 21, .62);
  backdrop-filter: blur(4px);
}
.tgzMapSheet__panel {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(1040px, calc(100vw - 32px));
  max-height: min(86dvh, 780px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--tgz-ui-line, #e4d4bf);
  border-radius: var(--tgz-ui-radius-lg, 24px);
  background: var(--tgz-ui-surface, #fffaf1);
  color: var(--tgz-ui-text, #172235);
  box-shadow: var(--tgz-ui-shadow-strong, 0 24px 70px rgba(23, 34, 53, .18));
}
.tgzMapSheet__head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--tgz-ui-line, #e4d4bf);
}
.tgzMapSheet__headText { min-width: 0; flex: 1 1 auto; }
.tgzMapSheet__head h2 {
  margin: 0;
  font: 900 clamp(1.05rem, 3.4vw, 1.4rem)/1.15 'Inter Tight', Inter, system-ui, sans-serif;
  letter-spacing: -.03em;
}
.tgzMapSheet__head p {
  margin: 4px 0 0;
  font-size: max(12.5px, .8rem);
  line-height: 1.4;
  color: var(--tgz-ui-muted, #647083);
}
.tgzMapSheet__close {
  appearance: none;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--tgz-ui-radius-sm, 12px);
  border: 1px solid var(--tgz-ui-line, #e4d4bf);
  background: var(--tgz-ui-card, #fff);
  color: inherit;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
.tgzMapSheet__frame { flex: 1 1 auto; min-height: 0; }
.tgzMapSheet__iframe { display: block; width: 100%; height: 100%; min-height: 320px; border: 0; }

html.tgzMapSheetOpen, html.tgzMapSheetOpen body { overflow: hidden; }

@media (max-width: 760px) {
  /* Full-height sheet: on a phone a centred dialog wastes the only axis
     a map actually needs. */
  .tgzMapSheet__panel {
    left: 0;
    top: auto;
    bottom: 0;
    transform: none;
    width: 100%;
    max-height: 92dvh;
    height: 92dvh;
    border-radius: 20px 20px 0 0;
    border-bottom: 0;
  }
  .tgzMapSheet__head { padding: 12px 12px 10px; }
}

html[data-theme="dark"] .tgzMapSheet__panel {
  background: var(--tgz-ui-surface, #0d1a22);
  border-color: var(--tgz-ui-line, #29404b);
}
html[data-theme="dark"] .tgzMapSheet__close {
  background: var(--tgz-ui-card, #101f29);
  border-color: var(--tgz-ui-line, #29404b);
}

/* ==========================================================================
   PHOTO BAND FALLBACK + CLS RESERVATIONS  (imagery restore, 2026-08)
   Every hero/gallery slot on the site now has a self-hosted photo, but a
   failed request (bad deploy, blocked CDN, slow 3G abort) used to leave a
   blank grey box. These media wrappers get a branded ocean->sand band behind
   the photo so a missing image still reads as designed instead of broken.
   Purely additive: the photo, when it loads, paints over the band.
   ====================================================================== */
:root{
  --tgz-photo-band:linear-gradient(135deg,#0d2a3d 0%,#14465e 38%,#2b7a86 68%,#e8a05a 118%);
  --tgz-photo-band-soft:linear-gradient(135deg,#f3e7d5 0%,#e4d3bd 45%,#cfd9db 100%);
}
html[data-theme="dark"]{
  --tgz-photo-band:linear-gradient(135deg,#061219 0%,#0d2c3c 42%,#1d5560 72%,#8a5a2c 120%);
  --tgz-photo-band-soft:linear-gradient(135deg,#0a1a22 0%,#12303c 55%,#1c4450 100%);
}
:where(.heroWrap,.heroWrapV18,.tgzArticleHero__media,.tgzDest3HeroMedia,.tgzDurationHeroMedia,
       .surfSmartHeroPhoto,.homeMediaMain,.tgzUtilityHero figure){
  background-image:var(--tgz-photo-band);
  background-size:cover;
  background-position:center;
  background-repeat:no-repeat;
}
:where(.phase21MediaFigure,.v29RouteMedia,.cmpTileMedia,.tgzArticleLibraryMedia,.homeMediaCard,.homeMediaSide){
  background-image:var(--tgz-photo-band-soft);
  background-size:cover;
  background-position:center;
  background-repeat:no-repeat;
}
/* An <img> that fails still occupies the box; give it the band too so the
   broken-image glyph sits on brand colour rather than page white. */
:where(.phase21MediaFigure,.v29RouteMedia,.cmpTileMedia,.tgzArticleLibraryMedia,.homeMediaCard) img{
  background-image:var(--tgz-photo-band-soft);
  background-size:cover;
  background-position:center;
}
/* CLS: reserve the box for lazy gallery tiles whose markup carries no
   width/height attributes. Wrappers that already set height:100% on the image
   keep winning, so this only fires where nothing else reserved space. */
:where(.phase21MediaFigure,.v29RouteMedia,.tgzArticleLibraryMedia,.homeMediaCard) img[loading="lazy"]:not([width]):not([height]){
  aspect-ratio:16/10;
  width:100%;
}
:where(.cmpTileMedia) img[loading="lazy"]:not([width]):not([height]){aspect-ratio:4/3;width:100%}
/* /credits/ photography attribution list (Unsplash guideline: visible credit). */
.creditsPhotos{margin-top:26px}
.creditsPhotoList{list-style:none;margin:14px 0 0;padding:0;display:grid;gap:8px;
  grid-template-columns:repeat(auto-fill,minmax(min(100%,280px),1fr))}
.creditsPhotoList li{margin:0;padding:10px 12px;border:1px solid var(--tgz-line,#e4dcd0);
  border-radius:14px;background:var(--tgz-card,#fff);line-height:1.45;font-size:.95rem}
.creditsPhotoAlt{display:block;margin-top:3px;color:var(--tgz-muted,#5c6b78);font-size:.84rem}
html[data-theme="dark"] .creditsPhotoList li{background:#0a1a22;border-color:rgba(246,243,235,.14)}

/* --- hotfix 2026-08-04: map sheet must beat the cookie banner (z 2147482500) ---
   Bug: banner intercepted all clicks over the open map. Two belts: hide the banner
   while the sheet is open, and stack the sheet above it regardless. */
.tgzMapSheetOpen .cookieBanner{display:none!important}
.tgzMapSheet{z-index:2147482800!important}

/* ======================================================================
   2026-08-04 — dark-theme surface safety net.
   The card families that ship their surface in css/tgz-release-4743b are
   fixed at source (tokenised there). The components below declare a
   hard-coded white/cream surface in bundles that carry no theme layer at
   all, so dark mode rendered light text on them. Measured at 375x812 with
   data-theme="dark" before this block:
     .flightBoardRow      bg #ffffff, child b #fffaf0        -> 1.04:1
     .flightPill.soft     bg #e9ddc8, text #b9c4c5           -> 1.33:1
     .hcSpotChips a       bg #ffffff, text #8fe6d2           -> 1.46:1
     .hcLevelRail span    bg #ffffff, text #b9c4c5           -> 1.78:1
     .tgzDurationKicker   bg #fff3e8, text #b9c4c5           -> 1.64:1
     .weatherLiveRead__score bg #fdeaea, text #b9c4c5        -> 1.54:1
   This bundle is registered second-to-last, and the rules are !important
   at (0,3,x), so they hold against the async-loaded offers bundle too.
   Light theme is untouched — every selector is scoped to [data-theme="dark"].
   ====================================================================== */
html[data-theme="dark"]{
  --tgz-dark-surface:#0d1e26;
  --tgz-dark-surface-2:#0a1920;
  --tgz-dark-line:rgba(151,230,214,.20);
  --tgz-dark-ink:#f2faf7;
  --tgz-dark-muted:#b9c4c5;
}
html[data-theme="dark"] body :is(
  .flightBoardRow,
  .flightAviasalesWidget,
  .flightPill.soft,
  .flightExternalLinks a,
  .flightRouteTop span,
  .flightTrustRow span,
  .flightDecisionMeta span,
  .flightNextGrid a,
  .flightMiniStatusGrid b,
  .flightMiniStatusGrid span,
  .hcWeatherScore,
  .hcSpotChips a,
  .hcSurfCardActions a,
  .hcLevelRail span,
  .tgzProfitGhost,
  .tgzDurationKicker,
){
  background:var(--tgz-dark-surface)!important;
  border-color:var(--tgz-dark-line)!important;
}
/* These carry their own dark-on-light ink, which has to flip with the
   surface; the chip links already inherit a mint accent that reads well. */
html[data-theme="dark"] body :is(
  .flightPill.soft,
  .flightRouteTop span,
  .flightTrustRow span,
  .flightDecisionMeta span,
  .flightMiniStatusGrid b,
  .flightMiniStatusGrid span,
  .hcWeatherScore,
  .tgzProfitGhost,
  .tgzDurationKicker,
){
  color:var(--tgz-dark-ink)!important;
}
html[data-theme="dark"] body :is(.flightBoardRow,.flightBoardRow b,.flightBoardRow span,.flightBoardRow small){
  color:var(--tgz-dark-ink)!important;
}
html[data-theme="dark"] body .flightBoardRow :is(small,span.flightBoardMeta){
  color:var(--tgz-dark-muted)!important;
}
/* Partner CTA: mint-on-teal measured 1.18:1. Same teal, readable ink. */
html[data-theme="dark"] body :is(.stay22CardCta,a.stay22CardCta){
  color:#04211d!important;
}

/* Meta/label floor (owner: nothing below 12px). The header/footer picker
   labels rendered at 11px on every page. */
html body .tgz411Header .tgz411PickerLabel,
html body .tgz411Footer .tgz411PickerLabel{
  font-size:12px!important;
}
@media (max-width:980px){
  html body .tgz411Header .tgz411PickerLabel{font-size:12px!important}
}

/* ------------------------------------------------------------------ *
 * Repair note (2026-08-24). The file previously ended mid-sentence
 * here: a truncated edit left orphaned prose plus a stray declaration
 * block from an intended .buildSummaryDialog theme repair. That
 * component is a dead selector - nothing in the codebase renders it
 * any more (the build page uses #cleanReceiptSheet / .quoteActionSheet)
 * - so the repair is dropped rather than rewritten. Its legacy paint
 * layers in core-post.bundle.css (v333-v337) stay untouched for the
 * AV-4 dead-code excision pass.
 * ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ *
 * AV-4a (2026-08-24): sitewide reduced-motion floor. 14 route bundles
 * declare animations or transitions with no prefers-reduced-motion
 * handling of their own (articles, core-tail, destination, editorial,
 * flights-deals, flights, header-phase372, home-deals, local,
 * not-build, offers, root-app, surfspots, tp-specialoffers). Instead
 * of patching fourteen files, this single owner ends all motion for
 * users who asked for none - the design-law way: one rule, one file.
 * Bundles with their own guards keep them; this only tightens.
 * ------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  html body *,
  html body *::before,
  html body *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
