/*
  components.css — every component style. References semantic/structural
  tokens only (tokens.css) — no raw hex, no raw px color/spacing values.
  Organized to match design system §B, section by section.
*/

/* ============================================================
   B1 · BUTTONS
   ============================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-family: var(--font-text);
  font-weight: var(--fw-medium);
  letter-spacing: 0.01em;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  transition: background-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
  position: relative;
}

.btn--sm { height: 40px; padding-inline: var(--space-4); font-size: var(--t-sm); }
.btn--md { height: 48px; padding-inline: var(--space-5); font-size: var(--t-base); }
.btn--lg { height: 56px; padding-inline: var(--space-6); font-size: var(--t-base); }

/* sm buttons extend their hit area to 44px without growing visually */
.btn--sm::before {
  content: "";
  position: absolute;
  inset: -2px -4px;
}

.btn__icon {
  width: 20px;
  height: 20px;
  flex: none;
}

/* Primary — ember-700 fill. One per screen. */
.btn--primary {
  background: var(--color-action);
  color: var(--color-action-text);
}
.btn--primary:hover {
  background: var(--color-action-hover);
  transform: translateY(-1px);
}
.btn--primary:active {
  background: var(--color-action-active);
  transform: translateY(0);
  transition-duration: var(--dur-instant);
}
/* Double ring: an ember ring on an ember fill is invisible. */
.btn--primary:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-on-action);
  outline-offset: var(--focus-ring-offset);
  box-shadow: 0 0 0 4px var(--color-bg);
}
.btn--primary:disabled {
  background: var(--c-ink-200);
  color: var(--c-ink-400);
  cursor: not-allowed;
  transform: none;
}
.btn--primary[aria-busy="true"] {
  pointer-events: none;
}

/* Secondary — rare. Reach for ghost instead unless two actions are genuinely equal. */
.btn--secondary {
  background: transparent;
  color: var(--color-text);
  border: 1px solid var(--color-text);
}
.btn--secondary:hover {
  background: var(--color-bg-alt);
}

/* Ghost — everything else. Underline draws on hover via background-size trick.
   H-2: ghost buttons are used sitewide WITHOUT a size modifier, so they
   previously inherited no height and rendered ~26px — under the 44px touch
   minimum. padding-block brings the hit area to 44px; background-origin:
   content-box keeps the underline tight under the text instead of dropping
   it to the bottom of the padded box.
   H-1: .btn sets white-space: nowrap, which made long ghost labels overflow
   a 320px viewport. Ghost labels are sentences, so they must wrap. */
.btn--ghost {
  background: transparent;
  color: var(--color-text);
  padding-inline: var(--space-1);
  padding-block: 9px;
  min-height: 44px;
  white-space: normal;
  text-align: left;
  background-image: linear-gradient(var(--color-action), var(--color-action));
  background-size: 0% 1px;
  background-repeat: no-repeat;
  background-position: left bottom;
  background-origin: content-box;
  border-radius: 0;
}
.btn--ghost:hover {
  background-size: 100% 1px;
}
.btn--ghost .btn__icon {
  transition: transform var(--dur-base) var(--ease-out);
}
.btn--ghost:hover .btn__icon {
  transform: translateX(3px);
}

/* Icon-only — always 44x44, always aria-label */
.btn--icon {
  width: 44px;
  height: 44px;
  color: var(--color-text-secondary);
  border-radius: var(--radius-sm);
}
.btn--icon:hover {
  color: var(--color-text);
  background: var(--color-bg-alt);
}

/* Loading — label persists, spinner appears left, no width reflow */
.btn__spinner {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: spin 600ms linear infinite;
  flex: none;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Dark-section overrides.
   H-5: the dark-band token override made --color-action ember-400 with dark
   text, so the CLOSING "Book a Call" — the highest-intent moment on every
   page — rendered lighter and softer than the primary button used everywhere
   else. Contrast was fine; hierarchy was not. Primary keeps ember-700/white
   on dark (5.76:1 white-on-ember, and it reads strongly against Harbor Ink);
   ember-400 stays reserved for links and inline accents on dark. */
[data-section="dark"] .btn--primary {
  background: var(--c-ember-700);
  color: #FFFFFF;
}
[data-section="dark"] .btn--primary:hover {
  background: var(--c-ember-600);
}
[data-section="dark"] .btn--primary:active {
  background: var(--c-ember-900);
}
[data-section="dark"] .btn--primary:focus-visible {
  outline-color: var(--c-sand-100);
  box-shadow: 0 0 0 4px var(--c-ink-800);
}
[data-section="dark"] .btn--ghost {
  color: var(--color-text);
}
[data-section="dark"] .btn--secondary {
  border-color: var(--color-text);
}

/* ============================================================
   B2 · CARDS
   ============================================================ */

.trip-card {
  position: relative;
  background: var(--c-sand-050);
  border: 1px solid var(--color-border-decorative);
  border-radius: var(--radius-md);
  overflow: hidden;
  transition: border-color var(--dur-base) var(--ease-out);
}
.trip-card:hover {
  border-color: var(--c-sand-500);
}

.trip-card__media {
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--radius-none);
}
.trip-card--feature .trip-card__media {
  aspect-ratio: 3 / 2;
}
.trip-card__media img,
.trip-card__media .placeholder-block {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}
.trip-card:hover .trip-card__media img,
.trip-card:hover .trip-card__media .placeholder-block {
  transform: scale(1.03);
}

.trip-card__body {
  padding: var(--space-5);
}

.trip-card__title {
  display: inline-block;
  margin-top: var(--space-2);
  background-image: linear-gradient(var(--c-ink-800), var(--c-ink-800));
  background-size: 0% 1px;
  background-repeat: no-repeat;
  background-position: left bottom;
  transition: background-size var(--dur-base) var(--ease-out);
}
.trip-card:hover .trip-card__title {
  background-size: 100% 1px;
}

/* The whole card is one link — a stretched pseudo-element from the title
   anchor, so the a11y tree contains exactly one link, not three (§B2). */
.trip-card__title-link {
  color: inherit;
  text-decoration: none;
}
.trip-card__title-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.trip-card__action {
  margin-top: var(--space-4);
  color: var(--color-action);
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
}

.trip-cards-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}
@media (min-width: 640px) {
  .trip-cards-grid { grid-template-columns: repeat(2, 1fr); }
}
/* The feature card spans the full grid width.

   This previously read `.trip-cards-grid .trip-card--feature`, which never
   applied: the grid items are the <li> wrappers, not the .trip-card
   <article> inside them, so grid-column had no grid to act on. The
   "double-width feature card" specified in Part IV §4 has therefore never
   actually rendered — it was the same size as every other card since the
   homepage was built. Caught by measuring rendered widths during the
   warmth pass, not by reading the CSS, which looks correct in isolation.

   :has() is Baseline and lets the rule stay in CSS rather than requiring a
   marker class on every <li> in the markup. */
.trip-cards-grid > li:has(.trip-card--feature) {
  grid-column: 1 / -1;
}

/* Story card, been-there tile, credential card, founder card, step card share
   primitives with trip-card; ratio and content differ per §B2 family table. */
.been-there-tile {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--radius-md);
}
.been-there-tile__media {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}
.been-there-tile:hover .been-there-tile__media {
  transform: scale(1.04);
}
.been-there-tile__insight {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  padding: var(--space-4);
  background: linear-gradient(to top, rgba(10,24,28,.82) 0%, rgba(10,24,28,.35) 55%, transparent 100%);
  color: var(--c-sand-100);
  font-size: var(--t-sm);
  opacity: 1; /* always visible on touch — never hover-only, §D2 */
}
@media (hover: hover) {
  .been-there-tile__insight {
    opacity: 0;
    transition: opacity var(--dur-slow) var(--ease-out);
  }
  .been-there-tile:hover .been-there-tile__insight,
  .been-there-tile:focus-within .been-there-tile__insight {
    opacity: 1;
  }
}

/* Left-aligned to match the Proof Strip it sits in — the only centered
   composition on the site is the terminal .cta-band. */
.credential-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  text-align: left;
  max-width: 220px;
}
.credential-card__mark {
  height: 32px;
  width: auto;
  color: var(--color-text-secondary);
}
.credential-card__note {
  font-size: var(--t-xs);
  color: var(--color-text-tertiary);
  line-height: 1.4;
}

.founder-card {
  display: grid;
  gap: var(--space-6);
}
@media (min-width: 1024px) {
  .founder-card {
    grid-template-columns: 5fr 7fr;
    align-items: center;
    gap: var(--space-9);
  }
}
.founder-card__portrait {
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-md);
  overflow: hidden;
}

.step-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.step-card__number {
  font-family: var(--font-display);
  font-size: var(--t-2xl);
  font-weight: var(--fw-semibold);
  color: var(--c-ink-500);
  line-height: 1;
}
[data-section="dark"] .step-card__number {
  color: var(--c-ink-300);
}

/* ============================================================
   B8 · QUOTE / TESTIMONIAL — built now, used when content.reviews
   has entries (Part I §9 Rule 4). Not emitted on the homepage at
   launch, since the array is empty — kept here so the section can
   be added later without touching CSS.
   ============================================================ */

.quote-card {
  max-width: 62ch;
}
.quote-card__text {
  font-family: var(--font-display);
  font-size: var(--t-lg);
  font-style: italic;
  line-height: 1.45;
  letter-spacing: -0.01em;
}
.quote-card--single .quote-card__text {
  font-size: var(--t-xl);
}
.quote-card__rule {
  width: 32px;
  height: 2px;
  background: var(--color-action);
  margin-block: var(--space-4);
}
.quote-card figcaption {
  font-size: var(--t-sm);
}
.quote-card__name {
  font-weight: var(--fw-semibold);
}
.quote-card__meta {
  color: var(--color-text-tertiary);
}

/* ============================================================
   B3/B4 · FORMS & INPUTS
   ============================================================ */

.field {
  display: flex;
  flex-direction: column;
  margin-bottom: var(--space-5);
}

.field__label {
  font-family: var(--font-text);
  font-weight: var(--fw-medium);
  font-size: var(--t-sm);
  color: var(--color-text);
  margin-bottom: var(--space-2);
}

.field__optional {
  color: var(--color-text-tertiary);
  font-weight: var(--fw-regular);
}

.field__input,
.field__textarea,
.field__select {
  height: 52px;
  padding-inline: var(--space-4);
  font-size: var(--t-base);
  font-family: var(--font-text);
  color: var(--color-text);
  background: var(--c-sand-050);
  border: 1px solid var(--color-border-interactive);
  border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.field__textarea {
  height: auto;
  min-height: 120px;
  padding-block: var(--space-3);
  resize: vertical;
}
.field__input::placeholder,
.field__textarea::placeholder {
  color: var(--c-ink-400);
}
.field__input:hover,
.field__textarea:hover,
.field__select:hover {
  border-color: var(--color-border-interactive-hover);
}
.field__input:focus,
.field__textarea:focus,
.field__select:focus {
  outline: none;
  border: 2px solid var(--color-action);
  padding-inline: calc(var(--space-4) - 1px);
  box-shadow: 0 0 0 3px var(--c-ember-100);
  background: #FFFFFF;
}
.field--error .field__input,
.field--error .field__textarea {
  border: 2px solid var(--color-error-text);
  background: color-mix(in srgb, var(--color-error-surface) 40%, var(--c-sand-050));
  padding-inline: calc(var(--space-4) - 1px);
}
.field__input:disabled,
.field__textarea:disabled {
  border-color: var(--c-ink-200);
  color: var(--c-ink-300);
  background: var(--c-sand-200);
  cursor: not-allowed;
}

.field__helper {
  font-size: var(--t-sm);
  color: var(--color-text-tertiary);
  margin-top: var(--space-2);
}
.field__error {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--t-sm);
  color: var(--color-error-text);
  margin-top: var(--space-2);
}
.field__error svg {
  width: 16px;
  height: 16px;
  flex: none;
}

/* Radio card — full-width tap target, real input hidden beneath the label */
.radio-card {
  position: relative;
  display: block;
  min-height: 56px;
}
.radio-card__input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}
.radio-card__label {
  display: flex;
  align-items: center;
  min-height: 56px;
  padding-inline: var(--space-4);
  border: 1px solid var(--color-border-interactive);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
.radio-card__input:checked + .radio-card__label {
  border: 2px solid var(--color-action);
  background: var(--c-ember-050);
  padding-inline: calc(var(--space-4) - 1px);
}
.radio-card__input:focus-visible + .radio-card__label {
  outline: var(--focus-ring-width) solid var(--color-focus);
  outline-offset: var(--focus-ring-offset);
}

.checkbox {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--space-3);
  cursor: pointer;
  padding: var(--space-2);
  margin: calc(var(--space-2) * -1);
}
.checkbox__input {
  appearance: none;
  width: 24px;
  height: 24px;
  flex: none;
  border: 1px solid var(--color-border-interactive);
  border-radius: var(--radius-sm);
  display: grid;
  place-items: center;
}
.checkbox__input:checked {
  background: var(--color-action);
  border-color: var(--color-action);
}
.checkbox__input:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus);
  outline-offset: var(--focus-ring-offset);
}

/* Selection highlight, global */
::selection {
  background: var(--color-selection);
}

.field__summary {
  font-size: var(--t-sm);
  color: var(--color-error-text);
  margin-bottom: var(--space-4);
}

.form-success {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6);
  background: var(--c-seaglass-100);
  border-radius: var(--radius-md);
}
.form-success[hidden] {
  display: none;
}
.form-success:focus {
  outline: none;
}

.form-fallback-error {
  font-size: var(--t-sm);
  color: var(--color-error-text);
  margin-top: var(--space-4);
}
.form-fallback-error[hidden] {
  display: none;
}

/* Booking embed — reserved-height slot that loads on click, never on page
   load (a scheduler iframe at load can cost a full second of LCP, §V10).
   Sandboxed to the minimum the provider needs once one is wired up (VI-4). */
.booking-embed {
  min-height: 480px;
  border-radius: var(--radius-md);
  border: var(--border-hairline);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-6);
  background: var(--c-sand-050);
}
.booking-embed iframe {
  width: 100%;
  height: 640px;
  border: none;
}

/* ============================================================
   B6 · NAVIGATION — header, dropdown, drawer, sticky bar
   ============================================================ */

/* position: fixed, not sticky — deliberately. Sticky still occupies its
   96px in normal document flow, which caused two real, connected bugs
   caught live: (1) on hero pages, the hero was silently pushed down 96px,
   so the "transparent over-hero" header actually rendered over the plain
   page background instead of the photo — white text on a light background,
   which is why it read as washed out/illegible, not a screenshot artifact.
   (2) height changes on an in-flow element reflow everything after it, so
   any state that changed the header's own height shifted the whole page.
   Fixed position removes the header from flow entirely, so neither
   problem can occur. Non-hero pages compensate with top padding on
   .page-intro/.page-center (below) since there's no longer flow-space
   reserved for the header automatically.

   Height is also deliberately CONSTANT across condense states — see
   .is-condensed below. Only background is allowed to change; nothing
   about the header's box ever moves or resizes. */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-header);
  height: var(--header-height);
  display: flex;
  align-items: center;
  background: var(--c-sand-100);
  transition: background-color var(--dur-base) var(--ease-in-out),
              border-color var(--dur-base) var(--ease-in-out);
}
.site-header.is-condensed {
  /* Height intentionally NOT set here — condense is a pure color swap.
     An earlier version also shrank the header 96px->68px on condense;
     live testing found the resulting size change felt like a jarring
     "shrink" even after the reflow bug above was fixed, so dimensions
     now stay fixed and only the background transitions. */
  /* Solid fallback declared first: if color-mix() or backdrop-filter is
     unsupported (older Safari, or any engine that mishandles the
     computation), the header still gets an opaque, readable background
     instead of silently resolving to transparent. The translucent
     version overrides it wherever both are supported. */
  background: var(--c-sand-100);
  background: color-mix(in srgb, var(--c-sand-100) 92%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: var(--border-hairline);
}
/* §Part IV/B6: transparent glass over the hero, swapping to solid on
   condense. Only background/color are allowed to differ between over-hero
   states — position and size are never on the list (see above). */
.site-header--over-hero {
  background: transparent;
}
.site-header--over-hero .site-header__logo,
.site-header--over-hero .site-header__nav-link,
.site-header--over-hero .logo__sub,
.site-header--over-hero .site-header__hamburger,
.site-header--over-hero .site-header__phone {
  color: var(--c-sand-100);
  text-shadow: 0 1px 3px rgba(10,24,28,.35);
  transition: color var(--dur-base) var(--ease-in-out), text-shadow var(--dur-base) var(--ease-in-out);
}
.site-header--over-hero.is-condensed {
  background: var(--c-sand-100);
  background: color-mix(in srgb, var(--c-sand-100) 92%, transparent);
}
.site-header--over-hero.is-condensed .site-header__logo,
.site-header--over-hero.is-condensed .site-header__nav-link,
.site-header--over-hero.is-condensed .site-header__hamburger {
  color: var(--color-text);
  text-shadow: none;
}
.site-header--over-hero.is-condensed .logo__sub {
  color: var(--color-text-tertiary);
  text-shadow: none;
}
.site-header--over-hero.is-condensed .site-header__phone {
  color: var(--color-text-secondary);
  text-shadow: none;
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text);
  min-height: 44px; /* touch target — the lockup is only ~32px tall on its own */
}
.logo__mark {
  width: 32px;
  height: 32px;
  flex: none;
}
.logo__word {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--t-md);
  letter-spacing: -0.02em;
  line-height: 1.1;
  display: flex;
  flex-direction: column;
}
.logo__sub {
  font-family: var(--font-text);
  font-weight: var(--fw-semibold);
  font-size: 38%;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}

.site-header__nav {
  display: none;
  align-items: center;
  gap: var(--space-7);
}
@media (min-width: 1024px) {
  .site-header__nav { display: flex; }
  .site-header__actions--desktop { display: flex; }
  .site-header__hamburger { display: none; }
}

.site-header__nav-link {
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-repeat: no-repeat;
  background-position: left bottom;
  padding-bottom: 2px;
  transition: background-size var(--dur-base) var(--ease-out);
}
.site-header__nav-link:hover {
  background-size: 100% 1px;
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.site-header__actions--desktop { display: none; }
.site-header__phone {
  display: none;
  font-size: var(--t-sm);
  color: var(--color-text-secondary);
}
@media (min-width: 1280px) {
  .site-header__phone { display: inline; }
}

/* Nav dropdown — a disclosure, not a menu. See §B5. */
.nav-dropdown {
  position: relative;
}
.nav-dropdown__panel {
  position: absolute;
  top: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  min-width: 280px;
  background: var(--c-sand-050);
  border: 1px solid var(--color-border-decorative);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-overlay);
  padding: var(--space-3);
  z-index: var(--z-dropdown);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out), visibility 0s var(--dur-base);
}
.nav-dropdown__panel.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out), visibility 0s;
}
.nav-dropdown__item {
  display: block;
  padding: var(--space-3) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-text);
}
.nav-dropdown__item:hover,
.nav-dropdown__item:focus-visible {
  background: var(--c-sand-200);
}
.nav-dropdown__item-label {
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
}
.nav-dropdown__item-sub {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--t-xs);
  color: var(--color-text-tertiary);
  margin-top: 2px;
}
/* Without this, the trigger's text and icon broke onto two lines — the
   global reset sets svg { display: block } (correct everywhere an image
   stands alone), which forces a line break when an SVG follows inline
   text inside a button with no flex context. inline-flex puts both back
   on one line, vertically centered as a single unit, icon immediately
   after the word "Go" rather than wrapping beneath it. */
.nav-dropdown__trigger {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.nav-dropdown__trigger-icon {
  width: 16px;
  height: 16px;
  flex: none;
  transition: transform var(--dur-base) var(--ease-out);
}
.nav-dropdown__trigger[aria-expanded="true"] .nav-dropdown__trigger-icon {
  transform: rotate(180deg);
}

/* Mobile drawer */
.site-header__hamburger {
  display: inline-flex;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  color: var(--color-text);
}
@media (min-width: 1024px) {
  .site-header__hamburger { display: none; }
}

.drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  visibility: hidden;
}
.drawer.is-open {
  visibility: visible;
}
.drawer__scrim {
  position: absolute;
  inset: 0;
  background: rgba(10,24,28,.45);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
}
.drawer.is-open .drawer__scrim {
  opacity: 1;
}
.drawer__panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(340px, 88vw);
  background: var(--c-ink-800);
  color: var(--c-sand-100);
  display: flex;
  flex-direction: column;
  padding: var(--space-6) var(--space-5);
  transform: translateX(100%);
  transition: transform var(--dur-slow) var(--ease-in-out);
}
.drawer.is-open .drawer__panel {
  transform: translateX(0);
}
.drawer__close {
  align-self: flex-end;
  color: var(--c-sand-100);
}
.drawer__nav {
  display: flex;
  flex-direction: column;
  margin-top: var(--space-6);
}
.drawer__nav-link {
  font-family: var(--font-display);
  font-size: var(--t-lg);
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--c-ink-700);
}
.drawer__footer {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Sticky mobile CTA bar */
.sticky-cta {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-sticky-cta);
  display: flex;
  gap: var(--space-3);
  align-items: center;
  height: 64px;
  padding-inline: var(--space-4);
  padding-bottom: env(safe-area-inset-bottom, 0);
  background: var(--c-sand-050);
  border-top: var(--border-hairline);
  transform: translateY(100%);
  transition: transform var(--dur-slow) var(--ease-out);
}
.sticky-cta.is-visible {
  transform: translateY(0);
}
.sticky-cta .btn--primary {
  flex: 1;
}
@media (min-width: 1024px) {
  .sticky-cta { display: none; }
}

/* Skip links */
.skip-link {
  position: absolute;
  top: -100px;
  left: var(--space-4);
  z-index: var(--z-skip-link);
  background: var(--c-sand-100);
  color: var(--color-action);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-sm);
  font-weight: var(--fw-medium);
  transition: top var(--dur-base) var(--ease-out);
}
.skip-link:focus {
  top: var(--space-4);
}

/* ============================================================
   B10 · FOOTER
   ============================================================ */

.site-footer {
  background: var(--c-ink-900);
  color: var(--c-sand-100);
  padding-block: var(--space-9) var(--space-6);
}
/* M-7: previously every footer link was ember-400, which flattened hierarchy
   into a wall of orange CTAs. Links are muted sand; ember is reserved for
   the one action that matters. */
.site-footer a {
  color: var(--c-ink-200);
  transition: color var(--dur-fast) var(--ease-out);
}
.site-footer a:hover {
  color: var(--c-sand-100);
}

/* H-4: the logo is a link, so it inherited the footer link color into the
   mark's currentColor while the wordmark was set separately — rendering the
   mark and wordmark in two different colors, which the brand rules forbid.
   Scoped here so the whole lockup is one color. */
.site-footer .logo,
.site-footer .logo:hover {
  color: var(--c-sand-100);
}
.site-footer .logo__sub {
  color: var(--c-ink-300);
}

/* H-2: footer links measured 20px tall. Padding brings each row to a 44px
   target; the li margin is dropped so the column doesn't gain extra height. */
.site-footer__list a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.site-footer__grid {
  display: grid;
  gap: var(--space-6);
  padding-bottom: var(--space-7);
}
@media (min-width: 768px) {
  .site-footer__grid {
    grid-template-columns: 1.4fr 1fr 1fr 1fr;
    gap: var(--space-6);
  }
}
.site-footer__heading {
  font-family: var(--font-text);
  font-weight: var(--fw-semibold);
  font-size: var(--t-sm);
  color: var(--c-sand-100);
  margin-bottom: var(--space-3);
}
.site-footer__list li {
  margin-bottom: 0; /* height now comes from the 44px link target above */
}
.site-footer__promise {
  color: var(--c-ink-200);
  font-size: var(--t-sm);
  max-width: 32ch;
  margin-top: var(--space-3);
}
.site-footer__credentials {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  padding-block: var(--space-6);
  border-top: 1px solid var(--c-ink-700);
}
.site-footer__newsletter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  padding-block: var(--space-6);
  border-top: 1px solid var(--c-ink-700);
}
.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--space-5);
  padding-top: var(--space-6);
  border-top: 1px solid var(--c-ink-700);
  font-size: var(--t-xs);
  color: var(--c-ink-300);
}
/* Legal links were 19px tall — below even the 24px WCAG 2.5.8 floor. */
.site-footer__legal a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* ============================================================
   ACCORDION — FAQ. One sanctioned exception to transform/opacity-only
   motion: expand uses grid-template-rows (§D, flagged deliberately).
   ============================================================ */

.accordion-item {
  border-bottom: var(--border-hairline);
}
.accordion-item__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  min-height: 56px;
  padding-block: var(--space-4);
  text-align: left;
  font-family: var(--font-text);
  font-weight: var(--fw-medium);
  font-size: var(--t-md);
}
.accordion-item__icon {
  width: 20px;
  height: 20px;
  flex: none;
  transition: transform var(--dur-base) var(--ease-out);
}
.accordion-item__trigger[aria-expanded="true"] .accordion-item__icon {
  transform: rotate(180deg);
}
.accordion-item__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-slow) var(--ease-out);
}
.accordion-item__panel.is-open {
  grid-template-rows: 1fr;
}
.accordion-item__panel-inner {
  overflow: hidden;
  contain: layout;
}
.accordion-item__panel-inner p {
  padding-bottom: var(--space-4);
  color: var(--color-text-secondary);
  max-width: 62ch;
}

/* ============================================================
   B9 · CAROUSEL — user-driven only. Never autoplaying. See §B9.
   ============================================================ */

.gallery {
  position: relative;
}
.gallery__track {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  padding-bottom: var(--space-2);
}
.gallery__track::-webkit-scrollbar {
  display: none;
}
.gallery__item {
  flex: 0 0 78%;
  scroll-snap-align: start;
}
@media (min-width: 640px) {
  .gallery__item { flex-basis: 42%; }
}
@media (min-width: 1024px) {
  .gallery__item { flex-basis: 23%; }
}
.gallery__controls {
  display: none;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
@media (min-width: 768px) {
  .gallery__controls { display: flex; }
}
.gallery__arrow {
  width: 44px;
  height: 44px;
  border: 1px solid var(--color-border-decorative);
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.gallery__arrow:hover {
  border-color: var(--color-text);
}
.gallery__arrow:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  .gallery__track { scroll-behavior: auto; }
}

/* ============================================================
   Credential strip (Part IV §3) — plain-English badge row, plus
   the honest empty-state fallback for launch (no credentials yet).
   ============================================================ */

/* Template 7 — Proof Strip. Left-aligned like every other head; only the
   terminal Statement Band is centered (see .cta-band). */
.credential-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-7);
  padding-block: var(--space-6);
}
.credential-strip--fallback {
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  gap: var(--space-3);
  max-width: 52ch;
}
.credential-strip--fallback .text-h3 {
  color: var(--color-text);
}

/* Stat row — honest numbers only; absent entirely while content.stats is empty */
.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  text-align: left;
}
.stat-row__label {
  font-size: var(--t-sm);
  color: var(--color-text-tertiary);
  margin-top: var(--space-1);
}

/* ============================================================
   Comparison table (Why Work With Me — real <table>, §B2/E3)
   ============================================================ */

.comparison-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: var(--space-6);
}
.comparison-table caption {
  text-align: left;
  margin-bottom: var(--space-4);
}
.comparison-table th,
.comparison-table td {
  text-align: left;
  padding: var(--space-4);
  border-bottom: var(--border-hairline);
  font-size: var(--t-sm);
  vertical-align: top;
}
.comparison-table th {
  font-family: var(--font-text);
  font-weight: var(--fw-semibold);
  color: var(--color-text-tertiary);
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ============================================================
   Placeholder image blocks — §A4. Unmistakable, self-documenting,
   correct aspect ratio so real media produces zero layout shift.
   ============================================================ */

.placeholder-block {
  display: flex;
  align-items: flex-end;
  width: 100%;
  height: 100%;
  padding: var(--space-4);
  background: linear-gradient(155deg, var(--c-ink-800), var(--c-ink-600));
  color: var(--c-sand-100);
  position: relative;
  overflow: hidden;
}
.placeholder-block::before {
  content: "PLACEHOLDER";
  position: absolute;
  top: var(--space-3);
  left: var(--space-4);
  font-family: var(--font-text);
  font-size: 10px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.14em;
  color: var(--c-ember-300);
  opacity: 0.85;
}
.placeholder-block__note {
  font-size: var(--t-xs);
  line-height: 1.4;
  opacity: 0.85;
  max-width: 32ch;
}

/* ============================================================
   Dev-mode outline — every PLACEHOLDER:true element gets an ember
   outline so what's fake is visible by looking, not grepping.
   Toggled via <html data-dev-mode="true">. See Part III intake register.
   ============================================================ */

[data-dev-mode="true"] [data-placeholder="true"] {
  outline: 1.5px dashed var(--c-ember-600);
  outline-offset: 3px;
}

/* ============================================================
   THE SECTION TEMPLATE LIBRARY — a closed set of seven.
   Every page on this site is composed only from these:

     1. Hero            .hero              full-bleed image, page opener
     2. Page Intro      .page-intro        typographic opener, utility pages
     3. Editorial Split .split             image + text, alternating, offset
     4. Index Grid      .trip-cards-grid   card collections
     5. Ledger          .step-row/.accordion  numbered or listed rows
     6. Statement Band  .cta-band          dark, centered, one idea
     7. Proof Strip     .credential-strip  trust marks, stats, quotes

   If a page appears to need an eighth template, that is a signal the
   content is wrong — not the system. Coherence here comes from
   refusing variants, not from adding polish.
   ============================================================ */

/* ============================================================
   SECTION HEAD — ONE header anatomy, used in every positional
   context. Previously there were four different constructions
   (asymmetric, centered fallback, page-intro, centered CTA), which is
   the single biggest reason the site read as assembled rather than
   authored.

   Anatomy, always in this order:
     eyebrow (with optional chapter numeral) → title → standfirst → action

   Always left-aligned. The ONE documented exception is .cta-band,
   which stays centered because a terminal statement earns symmetry.
   ============================================================ */

.section-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
}
.section-head > * + * {
  margin-top: var(--space-3);
}
.section-head .text-body-l,
.section-head .text-body-s {
  margin-top: var(--space-4);
  color: var(--color-text-secondary);
}
.section-head .btn {
  margin-top: var(--space-6);
}

/* Chapter numeral — deliberately slight. Tabular figures so numbers
   align down the page; tertiary colour so it reads as a mark of
   sequence rather than a label competing with the eyebrow. */
/* ============================================================
   PULL QUOTE — the editorial relief mechanism.

   Distinct from .quote-card, which is a testimonial (a real person, a real
   destination, a figcaption attribution) and renders nothing until real
   reviews exist. This is typographic punctuation for long text runs: no
   attribution, no quotation marks, not a claim about anyone.

   Added because the text-heavy pages had no relief of any kind — /faq runs
   648 words and /how-it-works 461, both with zero imagery and nothing but
   headings and hairlines to break them up. For a brand referenced against
   travel magazines that was the clearest structural gap in the build.

   The ember bar above it is the same mark as .figure-caption::before and a
   sibling of the Horizon Rule's sun — one recurring warm detail reused in a
   third place rather than a new decoration invented for this component.

   Content rule: a pull quote must say something the surrounding copy does
   NOT already say verbatim. A duplicated line forces a screen reader to
   read it twice and adds nothing for a sighted reader either.
   ============================================================ */

.pull-quote {
  max-width: 26ch;
  margin-block: var(--space-8);
  font-family: var(--font-display);
  font-style: italic;
  font-weight: var(--fw-regular);
  font-size: var(--t-xl);
  line-height: 1.32;
  letter-spacing: -0.015em;
  font-variation-settings: "opsz" 48, "SOFT" 30, "WONK" 1;
  color: var(--color-text);
}
.pull-quote::before {
  content: "";
  display: block;
  width: 32px;
  height: 2px;
  margin-bottom: var(--space-4);
  background: var(--color-action);
}
@media (min-width: 1024px) {
  /* Sits in the header column of the asymmetric grid, where the sticky
     header would otherwise run out of content — the same dead-column
     problem solved structurally on the homepage, solved here with copy. */
  .pull-quote { margin-block: var(--space-7) 0; }
}

/* ============================================================
   PHOTOGRAPHIC STATEMENT BAND — a variant of template 6 (Statement
   Band), NOT an eighth template. Same anatomy and same centred
   composition as .cta-band on a flat dark ground; the only change is
   that the ground is a photograph behind a scrim.

   Exists because the homepage had exactly one cinematic moment — the
   hero — and then nothing at full width for the remaining 90% of the
   scroll. Every other image sat at 39% of viewport width or less. This
   puts a second full-bleed beat at the close, where the page asks for
   the booking.

   Scrim opacity is derived, not guessed: the graded image's brightest
   luma is 160/255, so at 0.68-0.80 alpha over ink-950 the worst pixel
   a glyph can overlap composites to ~rgb(58,68,70) — Sand text clears
   9.2:1 there, comfortably AAA. Verified per §A4's rule that contrast
   is checked against the lightest overlappable pixel, not the average.
   ============================================================ */

.section--photo {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.section--photo .section__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.section--photo .section__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.section--photo .section__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to bottom,
    rgba(10,24,28,.68) 0%,
    rgba(10,24,28,.74) 55%,
    rgba(10,24,28,.80) 100%);
}
.section--photo > .container {
  position: relative;
  z-index: 2;
}

/* BAND variant of the section head — same anatomy, different placement.

   The asymmetric grid (header in columns 1-5, sticky; content in 6-12) is
   the right form when the content beside it is short: a four-step ledger,
   an accordion, a set of bullets. It is the wrong form when the content is
   a long collection, because the header column runs out of things to say
   long before the content runs out of height.

   Measured on the homepage's "Ways to Go" before this change: a 443px-wide
   column, 2,615px tall, containing 139px of ink — 94.7% empty. Roughly
   2.75 viewport-heights of nothing running down the left third of the
   busiest section on the site. That single rectangle was the largest
   contributor to the "too much empty space" reaction.

   Collapsing it to a band also fixes a second problem for free: the card
   grid inherits the full container instead of a 7-of-12 column, so cards
   go from 301px to ~536px wide and the feature card from 629px to ~1096px.
   Same markup, same components — the images simply stop being thumbnails.

   Title keeps a measure cap so full-width placement doesn't produce a
   1,000px-wide headline. */
.section-head--band {
  max-width: 38ch;
  margin-bottom: var(--space-8);
}

/* Chapter numerals carry Ember rather than tertiary grey. Warmth pass:
   Ember was measured painting 0.24% of the homepage — the action colour
   was effectively absent, which is most of why the palette read as
   off-white-and-navy. The numerals are small (--t-xs) and appear once per
   section, so this adds brand colour at a steady rhythm down the page
   without ever competing with the CTA. --color-action re-points to
   ember-400 inside dark bands automatically. */
.section-head__num {
  font-variant-numeric: tabular-nums;
  color: var(--color-action);
  opacity: 0.85;
}
.section-head__num::after {
  content: "";
  display: inline-block;
  width: 14px;
  height: 1px;
  margin: 0 0.5em 0.28em 0.5em;
  background: currentColor;
  vertical-align: middle;
}

/* ============================================================
   PAGE INTRO — template 2. Typographic opener for pages whose job is
   information rather than persuasion.
   ============================================================ */

/* Compensates for the header now being position:fixed (out of flow) —
   without this, page content would render underneath it at load, since
   nothing reserves its height anymore. Hero pages need no such padding:
   the header is meant to float over the photo, not clear it. */
.page-intro {
  padding-top: calc(var(--header-height) + var(--space-9));
  padding-bottom: var(--space-7);
}
.page-intro__title {
  margin-top: var(--space-3);
  max-width: 24ch;
}
.page-intro__subtitle {
  margin-top: var(--space-4);
  max-width: 60ch;
  color: var(--color-text-secondary);
}

/* ============================================================
   THE HORIZON RULE — the site's structural signature.

   The Go Mark is a circle intersected by a horizon line. This divider
   repeats that geometry at architectural scale: a hairline across the
   content width with a small open circle sitting ON the line, at the
   same column 5/6 seam the asymmetric grid already breaks on. The line
   passes through the circle exactly as it does in the mark.

   Used at major section transitions only — if it appears at every
   boundary it stops being a signature and becomes wallpaper.
   ============================================================ */

/* The rule spans the same content box as .container, so it must be centered
   and clamped the same way — percentages here resolve against the full-width
   section, not the container, so the seam offset is computed from the rule's
   own width rather than from a raw percentage.

   Seam maths: with the rule centered, a point P of the way across the content
   box sits at  50% - (0.5 - P) x width.
     desktop, 12-col, col 5/6 seam  -> P = 0.41666 -> 50% - 0.08334w
     mobile,  4-col,  col 1/2 seam  -> P = 0.33    -> 50% - 0.17w            */
.section--ruled {
  position: relative;
  --rule-inset: 20px;
  --rule-width: min(
    calc(100% - (var(--rule-inset) * 2)),
    calc(var(--container-content) - (var(--rule-inset) * 2))
  );
  --rule-seam: 0.17;
}
.section--ruled::before {
  /* the horizon */
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: var(--rule-width);
  height: 1px;
  background: var(--color-border-decorative);
}
.section--ruled::after {
  /* the sun/globe — transparent fill, so the horizon reads straight
     through it exactly as the line passes through the circle in the mark.

     Warmth pass: the circle is now Ember. It was previously the same grey
     hairline as the horizon, which meant the one element in the entire
     system that literally represents the sun was painted in the coldest
     colour available. The line stays neutral so the rule still reads as a
     divider first; only the sun is warm. Six of these down the homepage,
     each tiny, but they land on exactly the beat where a warm mark
     belongs. */
  content: "";
  position: absolute;
  top: -4px;
  left: calc(50% - (var(--rule-width) * var(--rule-seam)));
  transform: translateX(-50%);
  width: 9px;
  height: 9px;
  border: 1px solid var(--color-action);
  border-radius: 50%;
  background: transparent;
}

@media (min-width: 640px) {
  .section--ruled { --rule-inset: 32px; }
}
@media (min-width: 1024px) {
  .section--ruled { --rule-inset: 48px; --rule-seam: 0.08334; }
}
@media (min-width: 1280px) {
  .section--ruled { --rule-inset: 72px; }
}

/* On dark bands the horizon inverts with the section's own token set.
   The sun does NOT invert — it stays Ember (via --color-action, which
   re-points to ember-400 inside a dark band), because a warm mark on a
   dark ground is the one place it reads most clearly. */
[data-section="dark"].section--ruled::before {
  background: var(--c-ink-700);
}
[data-section="dark"].section--ruled::after {
  border-color: var(--color-action);
  background: transparent;
}

/* ============================================================
   FIGURE CAPTION — the editorial tell on photography.
   Ships now, activates when real images land: on a travel site
   "Kotor, Montenegro — October" is content, not decoration.
   ============================================================ */

.figure-caption {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin-top: var(--space-3);
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--t-xs);
  line-height: 1.4;
  color: var(--color-text-tertiary);
  font-variation-settings: "opsz" 24, "SOFT" 30, "WONK" 0;
}
.figure-caption::before {
  content: "";
  flex: none;
  width: 16px;
  height: 1px;
  background: var(--color-action);
  transform: translateY(-0.28em);
}
[data-section="dark"] .figure-caption {
  color: var(--c-ink-200);
}

/* ============================================================
   HERO — full-bleed photography, 88vh (not 100vh — a sliver of the next
   section is a stronger scroll cue than a bouncing arrow, Part IV §2).
   Text has NO entrance animation: it's the LCP element (§D3).
   ============================================================ */

.hero {
  position: relative;
  min-height: 88vh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  background: var(--c-ink-800);
}
@media (max-width: 767px) {
  .hero { min-height: 620px; }
}
.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}
.hero__media .placeholder-block {
  height: 100%;
}
.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Hero image settle — arrives at 1.06x scale, settles to 1.0 over 900ms
   (§D3). Runs once on load via CSS animation rather than a JS image-load
   listener, since it applies equally to the placeholder block today and
   a real <picture> later. Neutralized globally under reduced motion by
   the animation-duration override in base.css. */
@keyframes hero-settle {
  from { transform: scale(1.06); }
  to { transform: scale(1); }
}

/* Slow drift, layered after the settle. 1.0 -> 1.035 over 24s, alternating,
   so the frame is never still but never appears to move either — the change
   is roughly 0.15% per second, below the threshold where the eye reads it as
   motion. The point is that the hero feels like a live frame rather than a
   printed one.

   This is the ONE ambient motion on the site and it was added deliberately
   in place of the obvious alternatives: no background video (it costs LCP,
   fights autoplay policy, and is exactly the gimmick the anti-brief exists
   to prevent) and no hero parallax (already used on the split sections;
   doubling it cheapens both).

   Runs only after the settle finishes, via animation-delay. Both animations
   are neutralised by the global prefers-reduced-motion rule in base.css,
   which forces animation-duration to 0.01ms — the image then simply sits at
   its final scale, which is the correct reduced-motion outcome (arrived,
   not hidden). */
@keyframes hero-drift {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to   { transform: scale(1.07) translate3d(-1.4%, -0.6%, 0); }
}
.hero__media img,
.hero__media .placeholder-block {
  animation:
    hero-settle var(--dur-settle) var(--ease-out) both,
    hero-drift 30s ease-in-out var(--dur-settle) infinite alternate;
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .hero__media img,
  .hero__media .placeholder-block {
    animation: none;
    will-change: auto;
  }
}
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top,
    rgba(10,24,28,.78) 0%, rgba(10,24,28,.45) 35%, rgba(10,24,28,.10) 65%, transparent 100%);
}
.hero__content {
  position: relative;
  z-index: 2;
  padding-block: var(--space-9) var(--space-9);
  color: var(--c-sand-100);
}
.hero__content .text-body-l {
  color: var(--c-sand-100);
  opacity: 0.92;
  max-width: 52ch;
  margin-top: var(--space-4);
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-6);
}
/* Light ghost text is scoped to an actual .hero — i.e. only where this row
   sits over a full-bleed photograph.

   These two rules were previously unscoped (`.hero__actions .btn--ghost`),
   but .hero__actions is reused purely for its flex layout in three places
   that are NOT heroes and NOT dark: the founder card on the homepage, the
   404 page, and the booking thank-you page. In all three the ghost link
   rendered sand-100 on a pale background — 1.00:1 against the old Sand,
   literally invisible. "More about me" on the homepage has been unreadable
   since the page was built.

   Same failure mode as the dark-band headings in tokens.css: a rule that
   is correct for one context leaking into another via a shared class. */
.hero .hero__actions .btn--ghost {
  color: var(--c-sand-100);
}
.hero .hero__actions .btn--ghost:hover {
  background-image: linear-gradient(var(--c-sand-100), var(--c-sand-100));
}
.hero__trust {
  margin-top: var(--space-5);
  color: var(--c-sand-100);
  opacity: 0.75;
}

/* ============================================================
   SPLIT — spotlight sections (Girls Trips / Family Vacations, §Part IV 5/7).
   Reversible via .split--reverse. Image always leads on mobile regardless
   of desktop order (§Part IV 7 — do not flip on mobile to match desktop).
   ============================================================ */

.split {
  display: grid;
  gap: var(--space-6);
  align-items: center;
}
@media (min-width: 1024px) {
  .split {
    grid-template-columns: repeat(12, 1fr);
    gap: var(--space-8);
  }
  .split__media { grid-column: 7 / span 6; grid-row: 1; }
  .split__content { grid-column: 1 / span 6; grid-row: 1; }
  .split--reverse .split__media { grid-column: 1 / span 6; }
  .split--reverse .split__content { grid-column: 7 / span 6; }

  /* THE OFFSET RULE — editorial images always break one gutter toward the
     page edge, and never reach it. Consistent asymmetry reads as intent;
     occasional asymmetry reads as accident. The container's own padding
     (48–72px) is what keeps a margin at the edge. */
  .split__media { margin-right: calc(var(--space-7) * -1); }
  .split--reverse .split__media {
    margin-right: 0;
    margin-left: calc(var(--space-7) * -1);
  }
}
/* .split__media is a <figure>: a media frame plus its caption. The frame
   clips (for the parallax oversize); the caption must sit outside that
   clip, so overflow lives on the frame, never on the figure. */
.split__media {
  display: flex;
  flex-direction: column;
}
.split__media__frame {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--radius-none);
}
@media (min-width: 1024px) {
  .split__media__frame { aspect-ratio: auto; flex: 1; min-height: 480px; }
}
.split__media__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Parallax target — oversized and absolutely positioned so translation
   (max 8% travel, §D3) never reveals empty space at the top/bottom edge.
   The oversize is static CSS, present whether or not the JS parallax
   handler ever attaches (e.g. reduced motion, JS disabled). */
.split__media__frame [data-parallax] {
  position: absolute;
  inset: -8% 0;
  width: 100%;
  height: 116%;
}
.split__points {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-5);
}
.split__point {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}
.split__point::before {
  content: "";
  width: 6px;
  height: 6px;
  margin-top: 0.6em;
  flex: none;
  border-radius: 50%;
  background: var(--color-action);
}

/* ============================================================
   STEP TIMELINE — Travel Planning dark band. Ordered list (§Part IV 6).
   ============================================================ */

.step-timeline {
  display: grid;
  gap: var(--space-7);
  counter-reset: step;
}
@media (min-width: 1024px) {
  .step-timeline {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-6);
  }
}

/* Single-column variant used on /how-it-works, where each step carries a
   paragraph of detail and reads as a vertical list rather than 4 columns. */
.step-timeline--stacked {
  grid-template-columns: 1fr;
}
@media (min-width: 1024px) {
  .step-timeline--stacked { grid-template-columns: 1fr; }
}

/* A single numbered row: big number in a gutter, content beside it. */
.step-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-5);
  align-items: start;
  padding-block: var(--space-6);
  border-bottom: var(--border-hairline);
}
.step-row:last-child {
  border-bottom: none;
}

/* ============================================================
   NOTICE — the bordered callout used on legal pages to mark
   draft/unreviewed content.
   ============================================================ */

.notice {
  background: var(--c-ember-050);
  border: 1px solid var(--c-ember-200);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-7);
}

/* ============================================================
   PAGE-CENTER — short pages (404, thank-you) that should sit
   optically centered rather than stuck to the top.
   ============================================================ */

/* Same fixed-header compensation as .page-intro. */
.page-center {
  min-height: 60vh;
  padding-top: var(--header-height);
  display: flex;
  align-items: center;
}

/* ============================================================
   BOOKING ADVISOR ROW — avatar + "you'll be talking with…" on /plan
   ============================================================ */

.booking-advisor {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}
.avatar-sm {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  overflow: hidden;
  padding: 0;
  flex: none;
}

/* ============================================================
   PLACES GRID — "Places I Know" tiles (§Part IV 8). Absent from the DOM
   entirely while content.beenThere is empty (Rule 4) — kept here for when
   it isn't.
   ============================================================ */

.places-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
}
@media (min-width: 1024px) {
  .places-grid { grid-template-columns: repeat(4, 1fr); }
}

/* ============================================================
   CTA BAND — the close (§Part IV 12). Only centered composition on the
   homepage; a terminal moment earns symmetry a scanning section doesn't.
   ============================================================ */

.cta-band {
  text-align: center;
  max-width: 62ch;
  margin-inline: auto;
}
.cta-band__actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-6);
}
/* Phone is a peer alternative to the CTA, not a footnote — Ken's persona
   converts on this (Part I §6), so it gets a real touch target. */
.cta-band__phone {
  color: var(--c-ember-400);
  font-size: var(--t-sm);
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--space-2);
}

/* ============================================================
   THE MOTION VOCABULARY — one gesture, four uses.

   Everything on this site SETTLES: it rises a short distance and fades,
   on a single curve (--ease-out). Nothing slides laterally, nothing
   bounces, and nothing scales except photographs. A tight vocabulary is
   what makes a site feel composed rather than animated.

     1. Section settle   [data-reveal]      24px rise + fade, 500ms
     2. Image settle     .hero__media       1.06 -> 1.0, 900ms
     3. Underline draw   .btn--ghost        0% -> 100% width, 200ms
     4. Panel fade       .nav-dropdown      8px rise + fade, 240ms

   ONE documented exception: the mobile drawer translates on X, because
   it models a physical panel entering from off-screen rather than
   content arriving. Every other lateral movement is a bug.

   Scroll reveal — see lib/reveal.js. Above-the-fold content is
   never reveal-animated (§D3) — this class is only ever applied
   below the fold.
   ============================================================ */

[data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
}
[data-reveal].is-visible {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
