/* ==========================================================================
   Reusable component styles — paired 1:1 with templates/includes/*.html
   ========================================================================== */

[x-cloak] { display: none !important; }

/* Buttons ---------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 600;
  padding: 0.7rem 1.375rem;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.1s ease;
  text-decoration: none;
  line-height: 1.1;
}
.btn:active { transform: translateY(1px); }

.btn-primary { background: var(--color-primary); color: #fff; }
.btn-primary:hover { background: var(--color-primary-light); color: #fff; text-decoration: none; }

/* White on a deepened accent, not white on --color-accent: that pairing is
   3.04:1 and .btn is 15px/600, so the WCAG large-text exemption doesn't
   apply. Deepening the background rather than darkening the label keeps
   the intended light-on-gold CTA and reaches 5.63:1.

   Dark ink on --color-accent was tried first. It passes at 5.25:1 and
   still looked wrong — a mid-dark amber under near-black ink reads as
   dark-on-dark whatever the ratio says. Contrast maths scores legibility,
   not whether a pairing looks right.

   Hover goes deeper still (6.53:1) so the button never becomes *less*
   legible than at rest, which is what the original white-on-#c08a2e did. */
.btn-accent { background: var(--color-accent-btn); color: #fff; }
.btn-accent:hover { background: var(--color-accent-btn-hover); color: #fff; text-decoration: none; }

/* --color-border-interactive, not --color-border. A button is precisely
   "anything a user has to find and operate", which is what that token
   exists for and what WCAG 1.4.11 wants 3:1 on; the decorative tone is
   1.29:1 on paper and lower still on sand, where an outline button sat
   on the trust page's closing band and on the tip block and was simply
   not visible as a button. See the note beside both tokens. */
.btn-outline { background: transparent; color: var(--color-ink); border-color: var(--color-border-interactive); }
.btn-outline:hover { border-color: var(--color-primary); color: var(--color-primary); text-decoration: none; }

.btn-ghost { background: transparent; color: var(--color-primary); }
.btn-ghost:hover { background: var(--color-sand); text-decoration: none; }

.btn-sm { padding: 0.4rem 0.9rem; font-size: 0.8125rem; }
.btn-block { width: 100%; }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }

/* Icon-only button (nav actions: messages, log in/out) — text moves to
   title/aria-label instead of a visible label, so it stays a single fixed
   width regardless of language. */
/* The visual box stays 36px so the nav and card chrome are unchanged;
   the ::after pans the *hit* area out to 44px, centred, which is Apple's
   HIG floor and roughly Material's 48dp. Doing it this way rather than
   growing the button avoids re-spacing every toolbar it sits in. */
.btn-icon { position: relative; padding: 0.5rem; width: 36px; height: 36px; gap: 0; }
.btn-icon::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}
.btn-icon svg { width: 18px; height: 18px; }

/* Icon + label together (e.g. nav "Log in") — unlike .btn-icon, keeps the
   text since first-time visitors need the cue that returning ones don't. */
.btn-icon-text svg { width: 16px; height: 16px; }

/* Unread-count badge overlaid on an icon button (nav Messages) — accent
   gold rather than the danger red used for form errors elsewhere, so it
   reads as "something new" rather than "something wrong".

   --color-accent-btn, not --color-accent: white on the lighter accent is
   3.04:1, the worst contrast on the site, and this is 10px bold text
   carrying the one piece of information the badge exists for. The button
   variant of the same gold is what the tokens file already keeps around
   for exactly this — it's 5.63:1 here. */
.nav-icon-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-paper);
  background: var(--color-accent-btn);
  color: #fff;
  font-size: 0.625rem;
  font-weight: 700;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Cards -------------------------------------------------------------------*/
.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.card-body { padding: var(--space-5); }

/* Tour card ---------------------------------------------------------------*/
.tour-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.tour-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); text-decoration: none; }
.tour-card-media { position: relative; aspect-ratio: 4 / 3; background: var(--color-sand); overflow: hidden; }
.tour-card-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* One slide of the hover carousel. The Alpine x-show lives here rather
   than on the <img>, because Wagtail's image tags reject hyphenated
   attribute names and so cannot carry a directive onto the markup they
   generate. Positioned so the absolutely-placed <img> inside still fills
   the media box. */
.tour-card-slide { position: absolute; inset: 0; display: block; }

/* Hover photo carousel (multi-photo tours only — see tour_card.html) —
   arrows only appear on hover, dots stay visible whenever there's more
   than one photo so a browsing traveller knows more views exist. */
.card-carousel-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  color: var(--color-ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s ease, background-color 0.15s ease;
}
.card-carousel-arrow svg { width: 16px; height: 16px; }
.card-carousel-arrow:hover { background: #fff; }
.card-carousel-arrow-prev { left: var(--space-2); }
.card-carousel-arrow-next { right: var(--space-2); }
.tour-card-media:hover .card-carousel-arrow { opacity: 1; }

.card-carousel-dots {
  position: absolute;
  bottom: var(--space-2);
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  gap: 4px;
}
.card-carousel-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.6);
  transition: background-color 0.15s ease, transform 0.15s ease;
}
.card-carousel-dot.is-active { background: #fff; transform: scale(1.3); }
.tour-card-body { padding: var(--space-4) var(--space-4) var(--space-5); flex: 1; display: flex; flex-direction: column; }
.tour-card-location { font-size: 0.8125rem; color: var(--color-muted); margin-bottom: var(--space-1); }
.tour-card-title { font-family: var(--font-sans); font-weight: 600; font-size: 1.0625rem; color: var(--color-ink); margin: 0 0 var(--space-2); }
/* The rating carries the auto margin, not the meta row below it: one auto
   margin takes all the free space, two would split it and leave a gap
   between them that grows with the card. Pinning the rating pins
   everything under it, so star rows line up across a row of cards
   whatever length the descriptions are. */
.tour-card-rating { margin-top: auto; }
.tour-card-meta { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
.tour-card-price { font-weight: 700; color: var(--color-ink); flex: none; }
/* One line, always. A long trading name — "by Cultural Switzerland
   Tourism" — wrapped to two, which made that card's meta row 25px taller
   than its neighbours' and pushed its star row up by the same amount:
   pinning the rating to the bottom only lines cards up when what sits
   below the rating is the same height in each. Truncated rather than
   wrapped, because the price is the thing that must never be cut and the
   guide's name is a click away regardless. */
.tour-card-by {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tour-card-price small { font-weight: 400; color: var(--color-muted); font-size: 0.8125rem; }

/* Like button (includes/like_button.html) — a private save/bookmark, so
   deliberately no count anywhere near it. Sits over a media container
   that's already position:relative (.tour-card-media). */
.like-button {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  color: var(--color-ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease, background-color 0.15s ease;
}
.like-button svg { width: 17px; height: 17px; transition: fill 0.15s ease, stroke 0.15s ease; }
.like-button:hover { background: #fff; transform: scale(1.08); }
.like-button:active { transform: scale(0.92); }
.like-button.is-liked svg { fill: var(--color-danger); stroke: var(--color-danger); }

/* Tour detail page gallery ---------------------------------------------
   Two surfaces, one component. Under 700px a swipeable scroll-snap strip
   of every photo; above it, the cover-plus-mosaic. Both open the same
   lightbox.

   The strip replaces a `.tour-gallery-extras { display: none }` that made
   a twelve-photo tour look like a one-photo tour on the surface most
   travellers browse on. Scroll-snap rather than a JS carousel: the swipe
   and the momentum are the platform's, so they feel native on each OS
   instead of approximately native on all of them. */
.tour-gallery {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-sand);
}

/* --- the swipeable strip (mobile) --- */
.tour-gallery-strip {
  display: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* No visible scrollbar: the counter says where you are, and a bar under
     a photo is chrome this design does not need. Still fully scrollable,
     and still keyboard-scrollable. */
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.tour-gallery-strip::-webkit-scrollbar { display: none; }
.tour-gallery-slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  /* It is a <button>; strip the chrome, keep the behaviour. */
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  aspect-ratio: 4 / 3;
}
.tour-gallery-slide img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* A fraction, not dots. Dots stop scaling past ~8 photos and become
   decoration; "3 / 12" says where you are and how much is left, in less
   space. Tabular figures so the number does not jitter as it changes. */
.tour-gallery-counter {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  margin: 0;
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  background: rgba(26, 36, 32, 0.62);
  color: #fff;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

/* --- the mosaic (desktop) --- */
.tour-gallery-mosaic {
  display: flex;
  gap: var(--space-2);
  aspect-ratio: 16 / 7;
}
.tour-gallery-cover,
.tour-gallery-cell {
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  position: relative;
  overflow: hidden;
  min-width: 0;
}
.tour-gallery-cover { flex: 3; }
.tour-gallery-cover img,
.tour-gallery-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tour-gallery-extras {
  flex: 2;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-content: stretch;
  gap: var(--space-2);
}
.tour-gallery-cell { flex: 1 1 calc(50% - var(--space-2) / 2); min-height: 0; }

/* The only hover affordance: a restrained lift in brightness. The mosaic
   used to look identical whether or not it did anything, which is how a
   "+7" badge ends up being clicked and ignored. */
.tour-gallery-cover img,
.tour-gallery-cell img { transition: filter 160ms ease; }
.tour-gallery-cover:hover img,
.tour-gallery-cell:hover img { filter: brightness(1.06); }
.tour-gallery-cover:focus-visible,
.tour-gallery-cell:focus-visible,
.tour-gallery-slide:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -3px; }

.tour-gallery-overflow {
  position: absolute;
  inset: 0;
  background: rgba(26, 36, 32, 0.6);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 700px) {
  .tour-gallery-strip { display: flex; }
  .tour-gallery-mosaic { display: none; }
}

/* --- the lightbox ------------------------------------------------------
   A native <dialog>. showModal() gives the focus trap, Escape, page
   inertness and a stylable ::backdrop for free — each of which a
   div-with-z-index has to reimplement, usually incompletely. */
.tour-lightbox {
  padding: 0;
  border: 0;
  background: none;
  max-width: 100vw;
  max-height: 100vh;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.tour-lightbox::backdrop { background: rgba(16, 22, 20, 0.94); }
.tour-lightbox-inner {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}
.tour-lightbox-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5);
  color: rgba(255, 255, 255, 0.85);
  flex: 0 0 auto;
}
.tour-lightbox-counter {
  margin: 0;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}
.tour-lightbox-close {
  background: rgba(255, 255, 255, 0.1);
  border: 0;
  color: inherit;
  cursor: pointer;
  /* 44px, the same minimum touch target as the arrows below. It was
     padding-only around a zero-sized icon, which made the whole control
     about 16px of nothing. */
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
  border-radius: 999px;
  transition: background 160ms ease;
}
/* Every icon control in this file declares its own svg size, because
   includes/icon.html emits a bare <svg viewBox> with no width or height
   and inherits nothing. These two were the only icon controls that
   forgot, so the close X and both chevrons rendered at 0x0: the buttons
   were there, they worked when clicked, and there was nothing on screen
   to aim at. That is why the lightbox could not be closed. */
.tour-lightbox-close svg { width: 22px; height: 22px; }
.tour-lightbox-arrow svg { width: 20px; height: 20px; }
.tour-lightbox-close:hover { color: #fff; background: rgba(255, 255, 255, 0.22); }
.tour-lightbox-close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

.tour-lightbox-strip {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.tour-lightbox-strip::-webkit-scrollbar { display: none; }
.tour-lightbox-slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  margin: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: 0 var(--space-5) var(--space-5);
}
/* contain, not cover: a lightbox exists to show the whole photograph.
   Cropping here would defeat the only reason to open it. */
.tour-lightbox-slide img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}
.tour-lightbox-caption {
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.875rem;
  text-align: center;
  max-width: 60ch;
}

.tour-lightbox-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(255, 255, 255, 0.1);
  border: 0;
  color: #fff;
  cursor: pointer;
  width: 44px;   /* 44px: the minimum comfortable touch target. */
  height: 44px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 160ms ease;
}
.tour-lightbox-arrow:hover { background: rgba(255, 255, 255, 0.22); }
.tour-lightbox-arrow:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.tour-lightbox-arrow.is-prev { left: var(--space-4); }
.tour-lightbox-arrow.is-next { right: var(--space-4); }

@media (max-width: 700px) {
  /* Swipe is the gesture here; arrows would sit on top of the photo for
     no gain. The counter still says where you are. */
  .tour-lightbox-arrow { display: none; }
  .tour-lightbox-slide { padding: 0 var(--space-3) var(--space-4); }
}

@media (prefers-reduced-motion: reduce) {
  .tour-gallery-strip, .tour-lightbox-strip { scroll-behavior: auto; }
  .tour-gallery-cover img, .tour-gallery-cell img, .tour-lightbox-arrow { transition: none; }
}

/* Badges --------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-pill);
  background: var(--color-sand);
  color: var(--color-body);
}
.badge-verified { background: var(--color-success-bg); color: var(--color-success); }
.badge-instant { background: #eaf1fb; color: #2c568f; }
.badge-accent { background: #f7ecd9; color: var(--color-accent-dark); }
.badge-pending { background: #f7ecd9; color: var(--color-accent-dark); }
/* Waiting on the *guide*, not on us — distinct from .badge-pending,
   which means we still owe them a review. This one is the only badge
   in the list that is a to-do for the person reading it. */
.badge-attention { background: var(--color-danger-bg); color: var(--color-danger); }

/* Avatar ----------------------------------------------------------------- */
.avatar {
  border-radius: 50%;
  object-fit: cover;
  background: var(--color-sand);
  display: inline-block;
  flex: none;
}
.avatar-sm { width: 32px; height: 32px; }
.avatar-md { width: 48px; height: 48px; }
.avatar-lg { width: 80px; height: 80px; }
.avatar-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-weight: 700;
  color: var(--color-primary);
  background: var(--color-sand);
  border-radius: 50%;
}

/* Overlapping avatar stack (e.g. "guides in this city") ------------------ */
.guide-avatar-stack-row { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.guide-avatar-stack { display: flex; }
.guide-avatar-stack .avatar, .guide-avatar-stack .avatar-fallback { border: 2px solid var(--color-paper); }
.guide-avatar-stack > *:not(:first-child) { margin-left: -10px; }
.guide-avatar-stack-overflow {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--color-sand);
  border: 2px solid var(--color-paper);
  margin-left: -10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--color-primary);
  flex: none;
}
.guide-avatar-stack-caption { font-size: 0.875rem; color: var(--color-muted); }

.profile-avatar-field { margin-bottom: var(--space-2); }
.profile-avatar-wrap { position: relative; width: 80px; }
.profile-avatar-edit-btn {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--color-primary);
  color: #fff;
  border: 2px solid var(--color-paper);
  cursor: pointer;
}
.profile-avatar-edit-btn:hover { background: var(--color-primary-light); }
.profile-avatar-edit-btn svg { width: 14px; height: 14px; }
/* Visually hidden but still focusable/clickable via the <label for=""> —
   the label is the actual click target; this input never needs to be seen. */
.profile-avatar-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Star rating -------------------------------------------------------------*/
.star-rating { display: inline-flex; gap: 2px; color: var(--color-star); }
.star-rating svg { width: 1em; height: 1em; }
.star-rating .star-empty { color: var(--color-border); }
.rating-summary { display: inline-flex; align-items: center; gap: var(--space-2); font-size: 0.9375rem; }
.rating-summary strong { color: var(--color-ink); }

/* Price breakdown -----------------------------------------------------------
   The transparency component: guide earnings / platform fee / extras.
   Used on tour detail, checkout, booking confirmation and guide dashboard. */
.price-breakdown {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  overflow: hidden;
}
.price-breakdown-header {
  padding: var(--space-4) var(--space-5);
  background: var(--color-sand);
  font-weight: 600;
  color: var(--color-ink);
}
.price-breakdown-rows { padding: var(--space-4) var(--space-5); }
.price-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-2) 0;
  font-size: 0.9375rem;
}
.price-row-label { display: inline-flex; align-items: center; gap: var(--space-1); color: var(--color-body); }
/* The amount never wraps; the label gives way instead. At 360px the German
   labels ("Plattformkosten", "Guide verdient") pushed "51,30 EUR" onto two
   lines mid-number, which is the one thing on this panel that has to stay
   readable at a glance. min-width:0 lets the flex label actually shrink —
   without it a flex item refuses to go below its content width. */
.price-row-info, .price-row-label { min-width: 0; }
.price-row-value, .price-row-total span:last-child { white-space: nowrap; }
.price-row-sub { color: var(--color-muted); font-size: 0.8125rem; padding-left: var(--space-3); }
.price-row-info { display: flex; flex-direction: column; gap: 2px; }
.price-row-info .price-row-sub { padding-left: 0; }
.price-row.is-guide-earnings .price-row-label { color: var(--color-primary); font-weight: 600; }
.price-row.is-guide-earnings .price-row-value { color: var(--color-primary); font-weight: 600; }
.price-row.is-platform-fee .price-row-label,
.price-row.is-platform-fee .price-row-value { color: var(--color-muted); }
.price-row-divider { border-top: 1px dashed var(--color-border); margin: var(--space-2) 0; }
.price-row-total { display: flex; justify-content: space-between; padding: var(--space-4) var(--space-5); background: var(--color-paper); border-top: 1px solid var(--color-border); font-weight: 700; font-size: 1.0625rem; color: var(--color-ink); }
.price-breakdown-note { padding: 0 var(--space-5) var(--space-4); font-size: 0.8125rem; color: var(--color-muted); }

/* Comparison table (trust page: "most marketplaces" vs GuidesUnited) ------*/
.compare-table {
  width: 100%;
  border-collapse: collapse;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-surface);
}
.compare-table th, .compare-table td {
  padding: var(--space-4) var(--space-5);
  text-align: left;
  vertical-align: top;
  font-size: 0.9375rem;
  border-bottom: 1px solid var(--color-border);
}
.compare-table th { background: var(--color-sand); font-weight: 600; color: var(--color-ink); }
.compare-table td:first-child { color: var(--color-muted); font-weight: 600; width: 34%; }
.compare-table td.is-us { color: var(--color-primary); }
.compare-table tr:last-child th, .compare-table tr:last-child td { border-bottom: none; }

/* Fee info tooltip — a small (i) trigger next to "Platform cost" that
   explains what it covers and links to the full trust-page breakdown,
   without needing a static paragraph of its own. */
.fee-tooltip { position: relative; display: inline-flex; }
.fee-tooltip-trigger { background: none; border: none; padding: 0; margin: 0; line-height: 0; cursor: pointer; color: var(--color-muted); }
.fee-tooltip-trigger svg { width: 14px; height: 14px; }
.fee-tooltip-trigger:hover, .fee-tooltip-trigger:focus-visible { color: var(--color-primary); }
.fee-tooltip-panel {
  position: absolute;
  bottom: calc(100% + var(--space-2));
  left: 50%;
  transform: translateX(-50%);
  width: 220px;
  padding: var(--space-3);
  background: var(--color-ink);
  color: var(--color-paper);
  border-radius: var(--radius-md);
  font-size: 0.8125rem;
  line-height: 1.4;
  box-shadow: var(--shadow-md);
  z-index: 20;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.12s ease;
}
.fee-tooltip-panel a { color: var(--color-paper); text-decoration: underline; font-weight: 600; white-space: nowrap; display: block; margin-top: var(--space-1); }
/* Desktop: pure CSS hover/keyboard-focus reveal, no JS involved — avoids
   fighting with the tap-to-toggle Alpine state below (mouseenter + click
   firing together on a real click would otherwise open then immediately
   re-close it). Touch: .is-open is toggled by the click handler instead,
   since touch devices don't dispatch :hover. */
.fee-tooltip:hover .fee-tooltip-panel,
.fee-tooltip:focus-within .fee-tooltip-panel,
.fee-tooltip-panel.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* Forms ---------------------------------------------------------------- */
label { display: block; font-weight: 600; font-size: 0.875rem; margin-bottom: var(--space-1); color: var(--color-ink); }
input, select, textarea {
  font-family: var(--font-sans);
  font-size: 1rem;
  width: 100%;
  padding: 0.65rem 0.85rem;
  /* Not --color-border: WCAG 1.4.11 wants 3:1 for the boundary of a
     control you have to find, and the decorative tone is 1.29:1 — fine on
     a card edge, invisible on a phone outdoors. */
  border: 1px solid var(--color-border-interactive);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-ink);
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--color-primary-light); outline-offset: 1px; }

/* Inline "Write in [language]" picker beside the Generate-with-AI button
   (dashboard/wizard_basics.html). The base select rule is sized for a
   full-width form field — width:100% would push the button off its row.

   Shaped after .lang-switcher-toggle in nav.css, because that is what
   this is: a small inline control naming the current language, not a
   field somebody types into. Same size, radius, weight and hover.

   The padding is written long-hand on purpose. It used to be the
   shorthand `padding: 0.3rem 0.5rem`, which silently reset the
   `padding-right: 2.5rem` the `select` rule reserves for its chevron —
   and a class beats an element selector whatever the source order, so
   the arrow was drawn straight through the word "English". */
.ai-language-select {
  width: auto;
  padding-block: 0.4rem;
  padding-left: 0.75rem;
  padding-right: 2.1rem;
  border-radius: var(--radius-sm);
  /* The border stays --color-border-interactive, inherited from the base
     field rule. It reads very slightly heavier than the header's own
     language switcher, and that is correct rather than an oversight: the
     base rule's comment records that --color-border is 1.29:1, below the
     3:1 WCAG 1.4.11 asks for on the boundary of a control you have to
     find. Matching the nav exactly would have meant making this one
     harder to see on a phone outdoors.
   */
  background-color: transparent;
  background-position: right 0.6rem center;
  background-size: 0.875rem;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--color-body);
}
.ai-language-select:hover { border-color: var(--color-primary); color: var(--color-primary); }
.field { margin-bottom: var(--space-4); }
.field-help { font-size: 0.8125rem; color: var(--color-muted); margin-top: var(--space-1); }
.field-error { font-size: 0.8125rem; color: var(--color-danger); margin-top: var(--space-1); }
/* Grid rather than flex, so help text and errors sit UNDER the label
   instead of beside it. As a flex row they became a third item on the
   same line and squeezed the label into a narrow column of one-word
   lines — which nothing showed until the first checkbox with help text
   shipped (the profile-visibility toggle); every earlier one had a bare
   label and looked fine either way. */
.checkbox-field {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  column-gap: var(--space-2);
}
.checkbox-field input { width: auto; margin-top: 0.15rem; }
.checkbox-field label { font-weight: 400; margin: 0; }
.checkbox-field .field-help,
.checkbox-field .field-error { grid-column: 2; }

/* Two side-by-side fields whose labels must stay row-aligned even if one
   wraps to two lines and the other doesn't (e.g. a longer translation) —
   a flex .cluster can't do this since each field's label+input stack is
   sized independently, but a grid's row height is the tallest cell in it,
   so both inputs line up automatically regardless of label length. Direct
   children in DOM order: label, label, input-wrapper, input-wrapper. */
.field-grid-row {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: var(--space-3);
  row-gap: var(--space-1);
  margin-bottom: var(--space-4);
}
/* Labels bottom-align within row 1 — so a short one-line label still sits
   flush against its input below even when a sibling label wraps to two
   lines and grows the row. Inputs top-align within row 2 for the opposite
   reason: one field having help-text under it (making its cell taller)
   must not push a shorter sibling field's input down to "bottom-align"
   too — that's what made the trip-request form's date/budget pairs look
   broken. A blanket `align-items` on the grid can't give each row a
   different answer, so each row gets its own `align-self` instead. */
.field-grid-row > label { align-self: end; margin-bottom: 0; }
.field-grid-row > .field { align-self: start; margin-bottom: 0; }

/* Number inputs — no browser spinner arrows anywhere on the site, even
   outside the dedicated .stepper-input (e.g. price/duration fields, which
   are free-typed rather than stepped). */
input[type="number"] { -moz-appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Checkboxes — fully custom box + check mark instead of the raw OS widget. */
input[type="checkbox"] {
  appearance: none;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0;
  flex: none;
  border: 1.5px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface);
  cursor: pointer;
  display: inline-grid;
  place-content: center;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
input[type="checkbox"]::after {
  content: "";
  width: 0.65rem;
  height: 0.65rem;
  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
  background: #fff;
  transform: scale(0);
  transition: transform 0.1s ease;
}
input[type="checkbox"]:checked {
  background: var(--color-primary);
  border-color: var(--color-primary);
}
input[type="checkbox"]:checked::after { transform: scale(1); }
input[type="checkbox"]:disabled { opacity: 0.5; cursor: not-allowed; }

/* Selects — native popup stays (that's the right call, not worth a JS
   rebuild), but the closed-state control gets our own chevron instead of
   the OS one. Both prefixes are needed: unprefixed `appearance` alone
   still leaves GTK/Firefox-on-Linux painting its own button chrome under
   our background, which is what "default styling" looked like. */
select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  padding-right: 2.5rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7770' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.85rem center;
  background-size: 1rem;
  cursor: pointer;
  transition: border-color 0.15s ease;
}
select:hover { border-color: var(--color-primary-light); }

/* Custom select dropdown (includes/select_dropdown.html) — a native
   <select>'s open option list is OS-rendered and can't be restyled past
   the closed-state treatment above, so this swaps in a fully themed panel
   (matching the language switcher) for the fields where that matters. The
   real <select> stays in the DOM, visually hidden but still rendered (not
   display:none), so name/required/native validation/form submission all
   keep working untouched. */
.select-dropdown { position: relative; }
.select-dropdown-native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.select-dropdown-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
  font-family: var(--font-sans);
  font-size: 1rem;
  padding: 0.65rem 0.85rem;
  border: 1px solid var(--color-border-interactive);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-ink);
  cursor: pointer;
  transition: border-color 0.15s ease;
}
.select-dropdown-trigger:hover { border-color: var(--color-primary-light); }
.select-dropdown-trigger svg { width: 1rem; height: 1rem; flex: none; color: var(--color-muted); }
/* A native <select> never wraps its closed-state text, it truncates —
   match that instead of letting a longer translation grow the box taller
   than the other fields in the same row (min-width:0 is required for
   text-overflow to take effect on a flex child at all). */
.select-dropdown-trigger span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Floating panels — dropdowns, type-ahead results, the date picker.
   They sit ON a .card, and .card is --color-surface, and so were these:
   pure white over pure white, separated by a 1px border and a soft
   shadow. "Select a date" in particular reads as part of the page rather
   than as a layer over it.

   Two changes, because colour alone doesn't carry it. --color-paper is
   only 1.05:1 against white — deliberately slight, it's the site's own
   off-white rather than a grey — so the shadow does the real work of
   saying "this is above". Together they're unmistakable.

   --color-sand would separate more (1.2:1) and is wrong: it's the hover
   colour for options inside these very panels, and a panel painted its
   own hover colour has no hover state left. Paper also gives the white
   .availability-slot rows something to sit on, so the panel reads as a
   container of cards the way the page does. */
.select-dropdown-menu {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: var(--space-2);
  max-height: 280px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  background: var(--color-paper);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--space-2);
  z-index: 110;
}
.select-dropdown-option {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm, 6px);
  font-family: inherit;
  font-size: 0.9375rem;
  color: var(--color-body);
  cursor: pointer;
}
.select-dropdown-option:hover { background: var(--color-sand); color: var(--color-primary); }
.select-dropdown-option.is-active { color: var(--color-primary); font-weight: 600; background: var(--color-sand); }
.select-dropdown-option.is-disabled { color: var(--color-muted); cursor: not-allowed; }
/* Keyboard position. Focus stays on the combobox trigger and moves through
   the list via aria-activedescendant (the APG select-only pattern), so
   :focus can't carry this — without a visible counterpart, arrowing
   through the options would be silent to anyone using a keyboard and not
   a screen reader. Outline rather than background so it stays distinct
   from .is-active, the currently *selected* option. */
.select-dropdown-option.is-highlighted {
  background: var(--color-sand);
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}

/* Booking date/time picker (includes/availability_calendar.html,
   static/js/availability-calendar.js) — an Airbnb-Experiences-style
   month-jump + date-grouped slot list. Reuses .select-dropdown-trigger/
   -native for the closed state (same hidden-native-select technique), but
   needs its own wider panel since the content is month nav + grouped
   cards, not a flat option list. */
.availability-picker-panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: var(--space-2);
  max-height: 380px;
  overflow-y: auto;
  background: var(--color-paper);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--space-4);
  z-index: 110;
}
.availability-picker-month-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
  font-weight: 600;
  color: var(--color-ink);
}
.availability-picker-month-nav button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-ink);
  cursor: pointer;
}
.availability-picker-month-nav button:hover { border-color: var(--color-primary); color: var(--color-primary); }
.availability-picker-month-nav svg { width: 16px; height: 16px; }

.availability-picker-group + .availability-picker-group { margin-top: var(--space-4); }
.availability-picker-group-label {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-ink);
  margin-bottom: var(--space-2);
}
.availability-picker-empty { color: var(--color-muted); font-size: 0.875rem; padding: var(--space-2) 0; }

.availability-slot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  text-align: left;
  padding: var(--space-3);
  margin-bottom: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border-interactive);
  border-radius: var(--radius-md);
  cursor: pointer;
  font-family: inherit;
  color: var(--color-ink);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.availability-slot:last-child { margin-bottom: 0; }
.availability-slot:hover { border-color: var(--color-primary); }
.availability-slot.is-selected { border-color: var(--color-primary); background: var(--color-sand); }
.availability-slot:disabled { cursor: not-allowed; opacity: 0.5; }
.availability-slot-time { font-weight: 600; }
.availability-slot-spots { font-size: 0.8125rem; color: var(--color-muted); }
/* A slot refused for a reason, rather than one that is simply full.
   Italic and not red: the tour needs more notice, which is the guide's
   working condition, not an error the traveller made. */
.availability-slot-spots.is-unavailable { font-style: italic; }

/* Freeform date picker (includes/date_picker.html,
   static/js/date-picker.js) — shares .availability-picker-panel/
   -month-nav for the trigger/panel shell, just with a day-grid body
   instead of a date-grouped slot list. */
.date-picker-weekdays,
.date-picker-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.date-picker-weekdays {
  margin-bottom: var(--space-1);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-muted);
  text-align: center;
}
.date-picker-day {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: none;
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--color-ink);
  cursor: pointer;
}
.date-picker-day:hover:not(:disabled) { background: var(--color-sand); }
.date-picker-day.is-today { font-weight: 700; color: var(--color-primary); }
.date-picker-day.is-selected { background: var(--color-primary); color: #fff; }
.date-picker-day:disabled { color: var(--color-border); cursor: not-allowed; }
.date-picker-day.is-empty { visibility: hidden; pointer-events: none; }
.date-picker-clear {
  display: block;
  margin: var(--space-3) auto 0;
  background: none;
  border: none;
  color: var(--color-muted);
  font-size: 0.8125rem;
  text-decoration: underline;
  cursor: pointer;
}
.date-picker-clear:hover { color: var(--color-primary); }

/* File inputs — ::file-selector-button is broadly supported now and lets
   us restyle just the native "choose file" button in place, no JS/markup
   needed to fake it. */
input[type="file"] { padding: 0.4rem; }
input[type="file"]::file-selector-button {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 600;
  color: #fff;
  background: var(--color-primary);
  border: none;
  border-radius: var(--radius-pill);
  padding: 0.5rem 1rem;
  margin-right: var(--space-3);
  cursor: pointer;
  transition: background-color 0.15s ease;
}
input[type="file"]::file-selector-button:hover { background: var(--color-primary-light); }

/* Date / time — the native picker UI can't be fully rebuilt without a JS
   calendar widget, but the trigger icon can be tinted to match instead of
   the default black glyph. */
input[type="date"], input[type="time"] { cursor: text; }
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator {
  filter: invert(28%) sepia(17%) saturate(1400%) hue-rotate(122deg) brightness(90%);
  cursor: pointer;
}

/* Star rating picker — a real input (radio group), not just the read-only
   includes/star_rating.html display. Source order is 5..1 so the CSS
   general-sibling selector can light up "this star and everything before
   it" purely from :checked/:hover, no JS. */
.star-picker { display: flex; flex-direction: row-reverse; justify-content: flex-end; gap: 0.2rem; }
.star-picker input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.star-picker label {
  width: 2rem;
  height: 2rem;
  margin: 0;
  color: var(--color-border);
  cursor: pointer;
  transition: color 0.1s ease;
}
.star-picker label svg { width: 100%; height: 100%; }
.star-picker input:checked ~ label,
.star-picker input:hover ~ label,
.star-picker label:hover ~ label,
.star-picker label:hover { color: var(--color-star); }
.star-picker input:focus-visible ~ label { outline: 2px solid var(--color-primary-light); outline-offset: 2px; border-radius: 2px; }

/* Alerts / messages ------------------------------------------------------ */
.alert { padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); margin-bottom: var(--space-4); font-size: 0.9375rem; }
.alert-success { background: var(--color-success-bg); color: var(--color-success); }
.alert-error { background: var(--color-danger-bg); color: var(--color-danger); }
.alert-info { background: var(--color-sand); color: var(--color-body); }
.alert-warning { background: var(--color-warning-bg); color: var(--color-warning); }

/* Empty state -------------------------------------------------------------*/
.empty-state { text-align: center; padding: var(--space-10) var(--space-5); color: var(--color-muted); }
.empty-state h3 { color: var(--color-ink); }

/* Pagination ----------------------------------------------------------- */
.pagination { display: flex; justify-content: center; gap: var(--space-2); margin-top: var(--space-8); }
.pagination a, .pagination span {
  min-width: 2.25rem;
  height: 2.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  font-size: 0.875rem;
  color: var(--color-body);
}
.pagination a:hover { background: var(--color-sand); text-decoration: none; }
.pagination .is-current { background: var(--color-primary); color: #fff; font-weight: 600; }

/* Section spacing ------------------------------------------------------- */
/* Longhand, not the `padding` shorthand — .section is almost always used
   together with .container on the same element ("container section"), and
   the shorthand would win the whole property (all four sides) over
   .container's own padding since this file loads after grid.css, silently
   zeroing out its left/right padding at every viewport width. */
.section { padding-top: var(--space-10); padding-bottom: var(--space-10); }
.section-tight { padding-top: var(--space-8); padding-bottom: var(--space-8); }

/* Chat thread ---------------------------------------------------------- */
/* Fixed-height chat panel: .chat-thread scrolls internally, .chat-composer
   stays pinned at the bottom regardless of how many messages pile up. */
.chat-panel { display: flex; flex-direction: column; height: 70vh; max-height: 640px; }
.chat-thread { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4); overflow-y: auto; }
.chat-bubble { max-width: 70%; padding: var(--space-3) var(--space-4); border-radius: var(--radius-lg); background: var(--color-sand); align-self: flex-start; }
.chat-bubble.is-mine { background: var(--color-primary); color: #fff; align-self: flex-end; }
.chat-bubble-meta { font-size: 0.6875rem; opacity: 0.7; margin-top: var(--space-1); }
.chat-composer-form { flex-shrink: 0; }
.chat-composer { display: flex; gap: var(--space-2); padding: var(--space-4); border-top: 1px solid var(--color-border); }
.chat-composer textarea { resize: none; }
.chat-contact-warning { margin: var(--space-3) var(--space-4) 0; }
.chat-contact-warning p { margin: var(--space-1) 0 0; }

/* Cookie consent banner ---------------------------------------------------*/
/* bottom includes the safe-area inset for the same reason .booking-bar's
   padding does: since base.html opts into viewport-fit=cover, the page
   extends under the iOS home indicator, and anything pinned to the bottom
   edge sits under it unless it says otherwise. The fallback keeps this
   identical to what it was on every browser without insets. */
.consent-banner {
  position: fixed;
  left: var(--space-4);
  right: var(--space-4);
  bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
  z-index: 200;
  max-width: 640px;
  margin: 0 auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-5);
}
.consent-banner p { font-size: 0.875rem; margin-bottom: var(--space-3); }
.consent-banner-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

/* Homepage hero -------------------------------------------------------- */
.hero-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  align-items: center;
}
/* The search bar ---------------------------------------------------------
   One control, rendered by includes/search_bar.html, on both the home hero
   (compact) and the results page (full). It replaced .hero-search — a plain
   input+button pill — and, on the results page, a ten-field grid that read
   as an admin form rather than as a way to plan a trip.

   The shape is the segmented pill every travel site converged on, because
   it does something a row of labelled boxes can't: it reads as one question
   ("where, when, how many") rather than as ten, so the page doesn't open by
   asking the visitor to fill in a form. */
.search-bar {
  display: flex;
  align-items: stretch;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-md);
  padding: var(--space-1);
}
.search-bar-compact { max-width: 480px; }

.search-bar-field {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  min-width: 0;
  padding: var(--space-2) var(--space-5);
  border-radius: var(--radius-pill);
}
/* Hairline separators between segments, not around them: the pill is one
   object, and boxing each segment rebuilds the row of form fields this is
   meant to stop being.

   A pseudo-element rather than the `inset 1px 0 0` box-shadow this started
   as. An inset shadow is clipped by its own element's border-radius, and
   these segments carry radius-pill so that the focus highlight comes out
   as a pill — so the "line" was painted along a 999px curve and rendered
   as a short curved stub floating near the top of the segment. It read as
   a rendering fault, which is roughly what it was.

   Short and centred rather than full height, which is also what stops it
   colliding with the pill's own rounded ends. */
.search-bar-field + .search-bar-field::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 1px;
  height: 55%;
  background: var(--color-border);
  transition: opacity 0.15s ease;
}
/* A divider running alongside a highlighted segment cuts across it. Both
   the focused segment's own and the next one's go, so the highlight is
   bounded by nothing but itself. */
.search-bar-field:focus-within::before,
.search-bar-field:focus-within + .search-bar-field::before { opacity: 0; }
.search-bar-where { flex: 2 1 0; }
.search-bar-when { flex: 1.5 1 0; }
.search-bar-who { flex: 0.9 1 0; }

.search-bar-field label,
.search-bar-label {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--color-ink);
}
.search-bar-field input {
  width: 100%;
  height: auto;
  padding: 0;
  border: none;
  background: transparent;
  box-shadow: none;
  font-size: 0.9375rem;
  line-height: 1.4;
  color: var(--color-body);
}
.search-bar-field input::placeholder { color: var(--color-muted); }
/* The HTMX indicator is absolutely positioned by .gu-spinner-in-field, so
   it needs a positioned ancestor, and the input needs to stop short of it
   or a long destination name runs under the spinner. */
.search-bar-input { position: relative; }
.search-bar-input input { padding-inline-end: calc(18px + var(--space-2)); }
/* Each input's own ring is suppressed because it would draw a rectangle
   inside the pill. Suppressing it with no replacement would leave the
   site's primary control with no visible focus at all, so the ring moves
   to the segment — which also tells a sighted user *which* part of the bar
   they're in, something a ring around the whole pill can't. */
.search-bar-field input:focus { outline: none; box-shadow: none; }
.search-bar-field:focus-within {
  background: var(--color-paper);
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}

.search-bar-dates { display: flex; align-items: center; gap: var(--space-2); }
/* Sized to their content, not stretched to fill the segment. Stretched,
   each control was 151px holding about 50px of "calendar icon + From",
   so the leftover sat *between* the label and the dash: the icon and its
   own word ended up further apart on screen than the two dates were from
   each other, which is what made the group read as broken rather than
   merely roomy. They still grow when a real date replaces the
   placeholder. */
.search-bar-dates > .select-dropdown { min-width: 0; flex: 0 1 auto; }
.search-bar-dash { flex: none; color: var(--color-muted); }
/* The two date fields are includes/date_picker.html, whose trigger is the
   same bordered control used for the category and sort dropdowns. Inside
   the pill that border would draw a box within a box, so it is stripped
   back to plain text — the pill's own segment is already the field. */
.search-bar-dates .select-dropdown-trigger {
  padding: 0;
  border: none;
  background: transparent;
  font-size: 0.875rem;
  color: var(--color-body);
  justify-content: flex-start;
  gap: var(--space-1);
}
.search-bar-dates .select-dropdown-trigger:hover { border: none; }
/* The chevron earns its space on a standalone dropdown; here it competes
   with the dash between the two dates and the calendar icon already says
   what the control is. */
.search-bar-dates .select-dropdown-trigger svg:last-child { display: none; }
.search-bar-dates .select-dropdown-trigger span {
  /* The base trigger lets its label grow to fill; here that is the whole
     problem above, so the word sits next to its icon and stops. */
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The panel is anchored to one date field, which is roughly a third of the
   segment — narrower than a month grid. */
.search-bar-dates .availability-picker-panel { min-width: 17rem; }

.search-bar-submit {
  flex: none;
  align-self: center;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  margin-inline: var(--space-2) var(--space-1);
  border: none;
  border-radius: 50%;
  background: var(--color-accent-btn);
  color: #fff;
  cursor: pointer;
}
.search-bar-submit svg { width: 1.2rem; height: 1.2rem; }
.search-bar-submit:hover { background: var(--color-accent-btn-hover); }

/* The dropdown is anchored to the where-to segment, which is narrower than
   the suggestions it holds ("Geneva · Switzerland", plus a type label). */
.search-bar-where .autocomplete-panel { min-width: 20rem; }

/* Below the hero's two-column breakpoint the segments stack, because three
   of them plus a round button never fit a phone. The pill becomes a card
   at that point — a pill-shaped stack is just a rounded rectangle wearing
   the wrong radius. */
@media (max-width: 860px) {
  .search-bar {
    flex-direction: column;
    padding: var(--space-2);
    border-radius: var(--radius-lg);
  }
  .search-bar-field { border-radius: var(--radius-md); }
  /* The flex ratios above are left alone on purpose. They look like they
     would divide *height* once this is a column, but flex-grow only has
     an effect when there is free space to distribute and this container
     has no height of its own — measured, the segments come out the same
     with and without them. */
  /* Stacked, so the divider turns horizontal: a rule across the top of
     each segment but the first, inset from both edges so it reads as a
     separator rather than as a border on a box. */
  .search-bar-field + .search-bar-field::before {
    left: var(--space-4);
    right: var(--space-4);
    top: 0;
    transform: none;
    width: auto;
    height: 1px;
  }
  .search-bar-submit {
    width: 100%;
    height: 2.75rem;
    margin: var(--space-2) 0 0;
    border-radius: var(--radius-pill);
  }
  .search-bar-where .autocomplete-panel { min-width: 0; }
  /* "Until" opened off the right edge of the screen and took the whole
     page's horizontal scroll with it.

     The month grid can't shrink below 17rem (see the min-width above),
     and once the bar is a stacked card each of the two date triggers is
     only about half its width. The panel is `left: 0; right: 0` on its
     trigger, so min-width wins and it grows rightwards from the *left*
     edge of a trigger that starts halfway across the screen.

     The "From" panel is fine: its trigger starts at the card's left
     padding, so 17rem still lands inside a 320px viewport. Only the
     second one needs re-anchoring, to its own right edge, so it grows
     inwards instead. `right: 0` alone would not do it — `left: 0` is
     still set and would keep stretching it. */
  .search-bar-dates > .select-dropdown:last-child .availability-picker-panel {
    left: auto;
    right: 0;
  }
}

/* Filter bar and panel ---------------------------------------------------
   Everything that is not where/when/how-many lives behind one button. The
   count on it is what makes collapsing honest: a hidden filter that leaves
   no trace on the page is indistinguishable from a search that's quietly
   broken, which is exactly what the ten-field grid was there to avoid. */
.search-form { margin-bottom: var(--space-8); }

.filter-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}
.filter-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--color-ink);
  cursor: pointer;
}
.filter-toggle:hover { border-color: var(--color-border-interactive); }
.filter-toggle svg { width: 1.05rem; height: 1.05rem; }
.filter-toggle-chevron { display: inline-flex; transition: transform 0.15s ease; }
.filter-toggle.is-open .filter-toggle-chevron { transform: rotate(180deg); }
.filter-toggle-count {
  display: inline-grid;
  place-items: center;
  min-width: 1.35rem;
  height: 1.35rem;
  padding: 0 0.35rem;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
}
.filter-bar-sort { display: flex; align-items: center; gap: var(--space-2); }
.filter-bar-sort-label { font-size: 0.875rem; color: var(--color-muted); white-space: nowrap; }

.filter-panel {
  margin-top: var(--space-4);
  padding: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.filter-panel-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-4);
}
.filter-panel-grid .field { margin-bottom: 0; }
.filter-panel-toggles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  margin-top: var(--space-4);
}
.filter-panel-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

.hero-collage { position: relative; display: none; }
.collage-main {
  position: absolute;
  top: 0;
  right: 0;
  width: 62%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.collage-small {
  position: absolute;
  width: 36%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  border: 5px solid var(--color-paper);
}
.collage-small-1 { top: -6%; left: 0; }
.collage-small-2 { bottom: -6%; left: 8%; }

/* Below 900px the desktop collage's absolute-positioned overlap doesn't
   have a sane height to anchor to, so it stays hidden (.hero-collage's
   default display:none) and this simpler strip takes over instead — a
   real photo, visible in the first screen on a phone, not the desktop
   layout guessing at a small-screen height. Inspired by withlocals'
   mobile home screen (partial image peeking below the search bar,
   inviting a scroll) but in this site's own rounded-card, shadowed style
   rather than their edge-to-edge treatment. */
.hero-mobile-collage {
  display: flex;
  gap: var(--space-3);
  height: 180px;
  margin-top: var(--space-6);
}
.hero-mobile-collage img {
  border-radius: var(--radius-lg);
  object-fit: cover;
  box-shadow: var(--shadow-md);
  height: 100%;
  min-width: 0;
  width: 100%;
}
.hero-mobile-collage img:first-child { flex: 1.4; }
.hero-mobile-collage img:nth-child(2) { flex: 1; }

@media (min-width: 900px) {
  .hero-split { grid-template-columns: 1fr 1fr; }
  .hero-collage { display: block; height: 460px; }
  .hero-mobile-collage { display: none; }
}

/* Category cards --------------------------------------------------------- */
.category-card {
  position: relative;
  display: block;
  border-radius: var(--radius-lg);
  overflow: hidden;
  aspect-ratio: 4 / 3;
  box-shadow: var(--shadow-sm);
}
.category-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.35s ease;
}
.category-card:hover img { transform: scale(1.05); }
.category-card:hover { text-decoration: none; }
.category-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(14, 47, 41, 0.8), rgba(14, 47, 41, 0) 60%);
}
.category-card-label {
  position: absolute;
  left: var(--space-4);
  bottom: var(--space-4);
  z-index: 1;
  color: #fff;
  font-family: var(--font-serif);
  font-size: 1.25rem;
}

/* Editorial image + text split ------------------------------------------- */
.editorial-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  align-items: center;
}
.editorial-image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}
@media (min-width: 900px) {
  .editorial-split { grid-template-columns: 1fr 1fr; }
  /* Zigzag layout for a run of several editorial-split sections in a row
     (e.g. how_it_works_page.html's steps) — alternates which side the
     photo sits on instead of every section looking identical. */
  .editorial-split.is-reversed .editorial-image { order: 2; }
}

/* Centered copy column (how_it_works_page.html's steps) — the icon,
   "Step N" eyebrow and heading read as a centered unit within their half
   of the split, rather than pinned to the column's inner edge. */
.step-copy { text-align: center; }
.step-copy .icon-badge { margin: 0 auto var(--space-3); }

/* Scroll-reveal (static/js/scroll-reveal.js) — starts every
   [data-scroll-reveal] element lower and transparent, animates up into
   place the first time it scrolls into view. */
[data-scroll-reveal] {
  opacity: 0;
  transform: translateY(32px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
[data-scroll-reveal].is-visible { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  [data-scroll-reveal] { opacity: 1; transform: none; transition: none; }
}

/* CTA band ----------------------------------------------------------------*/
.cta-band {
  position: relative;
  background-size: cover;
  background-position: center;
}
.cta-band-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(14, 47, 41, 0.92) 10%, rgba(14, 47, 41, 0.55) 60%, rgba(14, 47, 41, 0.25) 100%);
}
.cta-band-content {
  position: relative;
  /* Longhand — see the note on .section above; this is paired with
     .container on its element too (templates/cms/home_page.html). */
  padding-top: var(--space-12);
  padding-bottom: var(--space-12);
  max-width: 520px;
}
.cta-band-content p { color: #dde7e2; }

/* Tabbed carousel ---------------------------------------------------------
   Our own lightweight replacement for a common "tabs switch a horizontal
   carousel" pattern — plain CSS scroll-snap + a few lines of Alpine
   (static/js/tabbed-carousel.js), no external carousel library. */
.tabbed-carousel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}
.tabs { display: flex; gap: var(--space-5); overflow-x: auto; }
.tab-button {
  background: none;
  border: none;
  padding: var(--space-2) 0;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--color-muted);
  border-bottom: 3px solid transparent;
  cursor: pointer;
  white-space: nowrap;
}
.tab-button.is-active, .tab-button:hover { color: var(--color-primary); border-bottom-color: var(--color-accent); }

.carousel-arrows { display: flex; gap: var(--space-2); flex: none; }
.carousel-arrow {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--color-border-interactive);
  background: var(--color-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--color-ink);
}
.carousel-arrow svg { width: 18px; height: 18px; }
.carousel-arrow:hover { border-color: var(--color-primary); color: var(--color-primary); }

.carousel-row {
  display: flex;
  gap: var(--space-5);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--space-2);
  scrollbar-width: none;
}
.carousel-row::-webkit-scrollbar { display: none; }
.carousel-row > * { flex: 0 0 280px; scroll-snap-align: start; }

/* Icon badges + step/point cards ------------------------------------------ */
.icon-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--color-sand);
  color: var(--color-primary);
  flex: none;
}
.icon-badge svg { width: 26px; height: 26px; }
.icon-badge-accent { background: #f7ecd9; color: var(--color-accent-dark); }

.icon-card {
  position: relative;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
}
.icon-card .icon-badge { margin-bottom: var(--space-4); }
.icon-card h4 { margin-bottom: var(--space-2); }
.icon-card p { margin-bottom: 0; }
.icon-card-number {
  position: absolute;
  top: var(--space-5);
  right: var(--space-6);
  font-family: var(--font-serif);
  font-size: 1.75rem;
  color: var(--color-border);
}

/* Quantity stepper — our replacement for browser-default number inputs */
.stepper-control {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  padding: var(--space-1);
}
.stepper-btn {
  width: 2.25rem;
  height: 2.25rem;
  flex: none;
  border: none;
  border-radius: 50%;
  background: var(--color-sand);
  color: var(--color-ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.stepper-btn svg { width: 16px; height: 16px; }
.stepper-btn:hover:not(:disabled) { background: var(--color-primary); color: #fff; }
.stepper-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.stepper-input {
  flex: 1;
  border: none;
  background: transparent;
  text-align: center;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 1.0625rem;
  color: var(--color-ink);
  padding: 0;
}
.stepper-input:focus { outline: none; box-shadow: none; }

/* Tour creation wizard ----------------------------------------------------
   Two-column layout: form content + a sidebar tracking step progress. */
.wizard-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 280px;
  gap: var(--space-8);
  align-items: start;
}
.wizard-sidebar { position: sticky; top: var(--space-6); }

/* Must come after the unconditional .wizard-sidebar rule above — same
   specificity, so with this block first (as it originally was), the
   later unconditional rule always won the cascade regardless of whether
   the media query matched, silently overriding position: static below. */
@media (max-width: 900px) {
  /* minmax(0, …) rather than a bare 1fr, to match the two-column rule
     above. `1fr` means `minmax(auto, 1fr)`, so the column's floor is its
     min-content width — one nowrap string anywhere inside it and the
     column grows past the viewport, taking the whole page sideways with
     it. The copy-prices picker found this: its trigger label truncates
     on screen but still contributes the untruncated title to intrinsic
     sizing, so choosing a long tour name grew this column to 648px on a
     360px phone. Desktop was always safe; only this rule was missing it. */
  .wizard-layout { grid-template-columns: minmax(0, 1fr); }
  /* The sticky position above is only correct in the two-column desktop
     layout, where the sidebar stays put in its own narrow column while
     content scrolls beside it. Collapsed to one column, the sidebar
     becomes a full-width block sitting above the content — sticky there
     just pins the whole 8-step list over the page as you scroll. */
  .wizard-sidebar { order: -1; position: static; }
}

.wizard-progress-track {
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-sand);
  overflow: hidden;
  margin-bottom: var(--space-2);
}
.wizard-progress-fill {
  height: 100%;
  background: var(--color-primary);
  border-radius: var(--radius-pill);
  transition: width 0.25s ease;
}
.wizard-progress-label { font-size: 0.8125rem; color: var(--color-muted); margin-bottom: var(--space-5); }

.wizard-steps { list-style: none; margin: 0; padding: 0; }
.wizard-step-item { position: relative; padding-left: var(--space-8); padding-bottom: var(--space-5); }
.wizard-step-item::before {
  /* connecting line down to the next step */
  content: "";
  position: absolute;
  left: 13px;
  top: 28px;
  bottom: 0;
  width: 1px;
  background: var(--color-border);
}
.wizard-step-item:last-child::before { display: none; }
.wizard-step-marker {
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8125rem;
  font-weight: 700;
  background: var(--color-surface);
  border: 1.5px solid var(--color-border);
  color: var(--color-muted);
}
.wizard-step-marker svg { width: 14px; height: 14px; }
.wizard-step-item.is-complete .wizard-step-marker { background: var(--color-success); border-color: var(--color-success); color: #fff; }
.wizard-step-item.is-current .wizard-step-marker { border-color: var(--color-primary); color: var(--color-primary); }
.wizard-step-link { font-weight: 500; color: var(--color-body); text-decoration: none; }
.wizard-step-item.is-current .wizard-step-link { color: var(--color-primary); font-weight: 700; }
.wizard-step-item.is-locked .wizard-step-link { color: var(--color-muted); pointer-events: none; cursor: default; }
.wizard-step-link:hover { color: var(--color-primary); text-decoration: none; }

.wizard-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-border);
}

/* Requirements checklist — shown before a guide starts creating a tour */
.checklist { list-style: none; margin: 0; padding: 0; }
.checklist-item { display: flex; gap: var(--space-4); padding: var(--space-3) 0; }
.checklist-number {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--color-sand);
  color: var(--color-primary);
  font-weight: 700;
  font-size: 0.8125rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.checklist-item h5 { margin: 0 0 0.15rem; font-size: 0.9375rem; }
.checklist-item p { margin: 0; font-size: 0.875rem; color: var(--color-muted); }

/* Radio cards — illustrated single-choice picker (category, booking mode,
   cancellation policy). Real radio inputs under the hood, visually hidden;
   the whole card is the label so it's keyboard- and screen-reader-friendly. */
.radio-card-group { display: grid; gap: var(--space-3); }
.radio-card-group.is-columns { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.radio-card { position: relative; }
.radio-card input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}
.radio-card label {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  margin: 0;
  padding: var(--space-4) var(--space-5);
  border: 1.5px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.radio-card label:hover { border-color: var(--color-primary-light); }
.radio-card input:focus-visible + label { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.radio-card input:checked + label { border-color: var(--color-primary); background: var(--color-success-bg); }
.radio-card-icon {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--color-sand);
  color: var(--color-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.radio-card-icon svg { width: 20px; height: 20px; }
.radio-card-body { flex: 1; }
.radio-card-title { font-weight: 600; color: var(--color-ink); margin-bottom: 0.15rem; }
.radio-card-desc { font-size: 0.8125rem; color: var(--color-muted); }
.radio-card-check {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1.5px solid var(--color-border);
  margin-top: 2px;
}
.radio-card input:checked + label .radio-card-check {
  border-color: var(--color-primary);
  background: var(--color-primary);
  position: relative;
}
.radio-card input:checked + label .radio-card-check::after {
  content: "";
  position: absolute;
  left: 6px;
  top: 2px;
  width: 5px;
  height: 9px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* City/country autocomplete — templates/includes/city_autocomplete.html */
.autocomplete { position: relative; }
/* What an .autocomplete-panel anchors itself to.
   The panel is `position: absolute; top: 100%`, so without this it
   measures against the whole .autocomplete field — label, control, help
   text and errors included — and opens below all of them. That is
   invisible on a field whose help text is one line and obvious on the
   guiding-cities picker, whose help text is three: the suggestions
   appeared a paragraph below the box they belong to.
   Wrap just the control and the panel in this, and `top: 100%` means
   the bottom of the control, with the panel overlaying whatever follows. */
.autocomplete-anchor { position: relative; }
.autocomplete-panel {
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--color-paper);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  max-height: 280px;
  overflow-y: auto;
}
.autocomplete-list { list-style: none; margin: 0; padding: var(--space-1); }
.autocomplete-option {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  border: none;
  background: transparent;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  color: var(--color-ink);
}
.autocomplete-option:hover, .autocomplete-option:focus-visible { background: var(--color-sand); outline: none; }
.autocomplete-option-secondary { font-size: 0.8125rem; color: var(--color-muted); flex: none; }
.autocomplete-empty { padding: var(--space-3); margin: 0; font-size: 0.8125rem; color: var(--color-muted); }

/* Language pill picker (includes/language_autocomplete.html) — the
   bordered box IS the "field", styled to look like one input even though
   it's a wrapping flex row of pills + a borderless text input. */
.language-pills {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.language-pills:focus-within { border-color: var(--color-primary); }
.language-pills input {
  flex: 1 1 120px;
  min-width: 120px;
  border: none;
  outline: none;
  padding: var(--space-1);
  font-family: inherit;
  font-size: 0.9375rem;
  background: transparent;
}
.language-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.3rem 0.4rem 0.3rem 0.75rem;
  border-radius: var(--radius-pill);
  background: var(--color-sand);
  color: var(--color-primary);
  font-size: 0.8125rem;
  font-weight: 600;
  white-space: nowrap;
}
.language-pill button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.language-pill button:hover { background: rgba(22, 68, 60, 0.15); }

/* Theme checkboxes — grouped, optional discovery tags (wizard_basics.html) */
.theme-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);
}
.theme-group h5 { margin: 0 0 var(--space-2); font-size: 0.8125rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-muted); }

/* Itinerary activity picker (includes/activity_picker.html) ---------------
   Heading for a group of checkboxes. Matches a <label>'s weight and size
   so nothing moves visually, but it is a <span>: a <label> with no `for`
   labels nothing, and this one sits above 17 checkboxes rather than one
   control. */
.field-group-heading {
  display: block;
  font-weight: 600;
  font-size: 0.875rem;
  margin-bottom: var(--space-1);
  color: var(--color-ink);
}
/* The search box used to sit flush against the bordered checkbox grid,
   reading as a stray bar welded to the bottom of it rather than as its
   own control. */
.activity-picker-more { margin-top: var(--space-4); }
.theme-checkboxes { display: flex; flex-direction: column; gap: var(--space-2); }

/* Drag-and-drop photo upload (wizard_photos.html) */
.dropzone {
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-sand);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.dropzone.is-dragging { border-color: var(--color-primary); background: var(--color-success-bg); }
.dropzone-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-5);
  cursor: pointer;
  text-align: center;
}
.dropzone-input { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.dropzone-icon { color: var(--color-primary); }
.dropzone-icon svg { width: 32px; height: 32px; }
.dropzone-text { font-size: 0.9375rem; color: var(--color-body); }
.dropzone-browse { color: var(--color-primary); font-weight: 600; text-decoration: underline; }
.dropzone-hint { font-size: 0.8125rem; color: var(--color-muted); }

.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: var(--space-3); }
.photo-thumb { position: relative; aspect-ratio: 1; border-radius: var(--radius-md); overflow: hidden; }
.photo-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-thumb-remove { position: absolute; top: var(--space-2); right: var(--space-2); margin: 0; }
.photo-thumb-remove button {
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(26, 36, 32, 0.65);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.photo-thumb-remove button:hover { background: var(--color-danger); }
.photo-thumb-remove svg { width: 14px; height: 14px; }

.photo-thumb-cover-badge {
  position: absolute;
  bottom: var(--space-2);
  left: var(--space-2);
  background: var(--color-primary);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-pill);
}
.photo-thumb-set-cover { position: absolute; bottom: var(--space-2); left: var(--space-2); margin: 0; }
.photo-thumb-set-cover button {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem 0.6rem;
  border: none;
  border-radius: var(--radius-pill);
  background: rgba(26, 36, 32, 0.65);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  cursor: pointer;
}
.photo-thumb-set-cover button:hover { background: var(--color-primary); }
.photo-thumb-set-cover svg { width: 12px; height: 12px; }

/* Pickup-area map picker (wizard_pickup.html) */
/* Leaflet's own panes and controls run to z-index 400-1000, and the sticky
   site header is 100 — so a map scrolled under the header painted straight
   over it. Making each map its own stacking context contains every one of
   those z-indexes inside the map, where they still order correctly relative
   to each other and no longer compete with the page. */
.map-picker {
  height: 320px;
  width: 100%;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--color-border);
  position: relative;
  z-index: 0;
}
/* Read-only meeting-point map on the public tour page. Shorter than the
   editor's picker — it's a "roughly here" beside an address, not
   something to work in — and non-interactive (see static/js/static-map.js
   for why), so the grab cursor Leaflet sets by default would lie. */
.map-static {
  height: 200px;
  width: 100%;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--color-border);
  margin-top: var(--space-2);
  /* Same stacking-context fix as .map-picker above. */
  position: relative;
  z-index: 0;
}
.map-static .leaflet-container { cursor: default; }
/* Room to breathe on both sides. The description ends in a paragraph and
   the meeting point starts with a bold label, so with only the paragraph's
   own margin between them the two ran together as one block of text with
   a map stuck in the middle of it. */
.meeting-point {
  margin-top: var(--space-8);
  margin-bottom: var(--space-8);
}
.meeting-point > p:first-child { margin-bottom: var(--space-3); }

/* Custom-styled native range input — used for pickup radius */
.range-slider {
  width: 100%;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-sand);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}
.range-slider::-webkit-slider-runnable-track { height: 6px; border-radius: var(--radius-pill); background: transparent; }
.range-slider::-moz-range-track { height: 6px; border-radius: var(--radius-pill); background: transparent; }
.range-slider::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  margin-top: -7px;
  border-radius: 50%;
  background: var(--color-primary);
  border: 3px solid #fff;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}
.range-slider::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--color-primary);
  border: 3px solid #fff;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}
.range-slider:focus { outline: none; }
.range-slider:focus-visible::-webkit-slider-thumb,
.range-slider:focus-visible::-moz-range-thumb { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* Itinerary timeline (wizard_itinerary.html) */
.itinerary-timeline { list-style: none; margin: 0; padding: 0; }
.itinerary-stop { position: relative; padding-left: var(--space-8); padding-bottom: var(--space-4); }
.itinerary-stop::before {
  content: "";
  position: absolute;
  left: 13px;
  top: 28px;
  bottom: 0;
  border-left: 2px dashed var(--color-border);
}
.itinerary-stop:last-child::before { display: none; }
.itinerary-stop-marker {
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--color-primary);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8125rem;
  font-weight: 700;
}
.itinerary-stop-body {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
/* "Copy prices from another tour", above the pricing form. A shortcut
   rather than part of the form, so it reads as an offer and not a
   required field — hence the muted surface and the outline button. */
.price-copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
  background: var(--color-sand);
}
.price-copy-title { margin: 0; font-size: 1rem; }
.price-copy-hint { margin: var(--space-1) 0 0; }
.price-copy-controls {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  min-width: 0;
}
/* The dropdown takes the room; the button keeps its own width. Without
   this the select collapses to its placeholder and the tour titles —
   the whole point of the picker — are cut off. */
.price-copy-controls .select-dropdown { flex: 1 1 18rem; min-width: 0; }

@media (max-width: 560px) {
  /* Button under the select rather than beside it. A 288px flex-basis
     plus a button does technically fit on one wrapped line at 360px, so
     they sat side by side with nothing left over — same breakpoint as
     .time-off-form, which stacks for the same reason. */
  .price-copy-controls { flex-direction: column; align-items: stretch; }
  .price-copy-controls .select-dropdown { flex: 0 0 auto; width: 100%; }
}

/* The ways in, on the second-step page. Every method gets the same card
   and the same weight because they are alternatives to each other — the
   page used to card the passkey and leave the code field loose below it,
   which reads as step two rather than "or". */
.auth-choices { margin: var(--space-5) 0 var(--space-4); }
.auth-choice { margin: 0; }
.auth-choice-title { margin: 0; font-size: 1rem; }
.auth-choice-help {
  margin: var(--space-1) 0 var(--space-3);
  font-size: 0.8125rem;
  color: var(--color-muted);
}
/* A rule through the middle with the word sitting on it — the separator
   has to read as "these are alternatives", which a bare word floating
   between two cards does not do on its own. */
.auth-or {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-4) 0;
  color: var(--color-muted);
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.auth-or::before,
.auth-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border);
}
.auth-trust { gap: var(--space-2); margin-top: var(--space-4); }
.auth-trust input { width: auto; margin: 0; }
.auth-resend { margin-top: var(--space-3); }
.auth-recovery { margin-top: var(--space-6); }

/* One row per bullet — highlights, what's included, what to bring. The
   remove button sits inside the row rather than under it, so a guide
   editing item three is not counting buttons to find the right one. */
.bullet-list-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.bullet-list-row .bullet-list-input { flex: 1; min-width: 0; }
.bullet-list-remove {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
}
.bullet-list-remove:hover { color: var(--color-danger); border-color: var(--color-border); }
.bullet-list-remove svg { width: 1rem; height: 1rem; }
.bullet-list-add { margin-top: var(--space-1); }

/* The hidden attribute means hidden.
   `[hidden]` gets `display: none` from the user-agent stylesheet only, so
   any author rule that sets `display` beats it — `.btn` is `inline-flex`,
   which is how `button.hidden = true` left a button on screen still
   reading "Asking…" after permission had been granted. One rule here
   rather than remembering it at each call site. */
[hidden] { display: none !important; }

/* A tour's scannable lists — highlights, what's included, what to bring.
   Adapted from what the big OTAs put on a listing; the value is that a
   traveller can answer "is this for me" without reading three paragraphs. */
.tour-bullets { margin: var(--space-6) 0; }
.tour-bullets h3 { margin: 0 0 var(--space-3); font-size: 1.05rem; }
.tour-bullets ul { margin: 0; padding: 0; list-style: none; }
.tour-bullets li {
  position: relative;
  padding-left: var(--space-6);
  margin-bottom: var(--space-2);
}
/* The marker is drawn per variant rather than with a list-style, so
   included and not-included can read as a tick and a cross without the
   traveller having to compare two headings to tell them apart. */
.tour-bullets li::before {
  position: absolute;
  left: 0;
  top: 0;
  font-weight: 700;
}
.tour-bullets-plain li::before,
.tour-bullets-highlights li::before { content: "•"; color: var(--color-primary); }
.tour-bullets-included li::before { content: "✓"; color: var(--color-primary); }
.tour-bullets-excluded li::before { content: "✕"; color: var(--color-muted); }
.tour-bullets-excluded li { color: var(--color-muted); }
.tour-bullets-notice li::before { content: "!"; color: var(--color-warning); }
.tour-bullets-highlights {
  padding: var(--space-4) var(--space-5);
  background: var(--color-sand);
  border-radius: var(--radius-md);
}
/* Side by side where there is room: the two lists answer one question
   between them, and reading them as a pair is the point. */
.tour-bullet-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0 var(--space-6);
}

/* "Runs in French, English" under the tour title. Its own line because
   the language a tour is run in decides whether a traveller can take it
   at all. */
/* The languages line under a tour's title. Vertical spacing for this and
   its neighbours lives with the rest of the header rhythm at the foot of
   this file, not here: it is a relationship between two elements rather
   than a property of either, and splitting it across both was how this
   row ended up with a negative top margin that pulled it under the
   private-tour note. */
.tour-languages {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.tour-languages svg { width: 1rem; height: 1rem; flex: none; }
.tour-languages-label { margin-right: calc(-1 * var(--space-1)); }
/* A language this tour runs in. Outlined rather than filled: the badges
   above the title say what kind of tour this is and earn their colour;
   these are quieter facts, and a second filled row would compete with
   the title sitting between them. */
.badge-language {
  background: transparent;
  border: 1px solid var(--color-border);
  color: var(--color-body);
  font-weight: 500;
}

/* An offered bookend — the pickup or drop-off the pickup step already
   describes, shown at the end of the timeline where it would land.
   Dashed and unnumbered so it reads as a slot rather than a stop: it is
   a suggestion, and it reappears if the guide deletes the stop it makes
   (see apps/tours/bookends.py), so it has to sit quietly. */
.itinerary-bookend-offer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-left: var(--space-8);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
}
.itinerary-bookend-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* min-width:0 so a long description wraps inside the row instead of
     forcing it wider than the column — the same fix the stop body needs. */
  min-width: 0;
  flex: 1 1 16rem;
}
.itinerary-bookend-title { font-weight: 600; }
.itinerary-bookend-desc { font-size: 0.8125rem; color: var(--color-muted); }
.itinerary-bookend-warning {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--color-warning);
  background: var(--color-warning-bg);
  border-radius: var(--radius-sm);
  font-size: 0.875rem;
}

/* The tick on the checkout success screen. Sized off the icon rather than
   a fixed pixel value so it holds if the icon set changes. */
.checkout-success-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: var(--color-primary);
  color: #fff;
}
.checkout-success-mark svg { width: 1.75rem; height: 1.75rem; }

/* A booking on the traveller's trips page. Grid rather than flex so the
   date block, the details and the status each hold their own column and a
   long tour title can't shove the badge off the edge. */
.trip-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-4);
  align-items: center;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.trip-card:hover { border-color: var(--color-primary-light); text-decoration: none; transform: translateY(-1px); }
.trip-card.is-past { opacity: 0.72; }

.trip-card-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 3.25rem;
  padding: var(--space-2);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  line-height: 1.1;
}
.trip-card-day { font-size: 1.375rem; font-weight: 700; color: var(--color-primary); }
.trip-card-month { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-muted); }
.trip-card-title { font-weight: 600; margin-bottom: 0.15rem; }
.trip-card-body { min-width: 0; }

/* Below this the three columns stop fitting: the status badge wraps under
   a two-line title and the row reads as two unrelated things. */
@media (max-width: 560px) {
  .trip-card { grid-template-columns: auto 1fr; }
  .trip-card-status { grid-column: 2; }
}

/* The traveller-facing timeline (includes/itinerary.html) has no action
   column, so its text has to grow into the space the buttons used to take.
   min-width:0 because a flex item won't shrink below its longest word
   otherwise, and a stop description is free text. */
.itinerary-stop-text { flex: 1; min-width: 0; }

.itinerary-stop-title { font-weight: 600; margin: 0 0 0.15rem; }
.itinerary-stop-meta { font-size: 0.8125rem; color: var(--color-muted); }
.itinerary-stop-desc { font-size: 0.875rem; color: var(--color-body); margin: var(--space-2) 0 0; }
.itinerary-stop-actions { display: flex; flex-direction: column; gap: var(--space-1); flex: none; }
.itinerary-stop-actions form { margin: 0; }
.itinerary-stop-actions button {
  background: transparent;
  border: none;
  color: var(--color-muted);
  cursor: pointer;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  display: inline-flex;
}
/* The inline edit form is a child of the flex .itinerary-stop-body, so it
   defaults to flex: 0 1 auto and collapses to its content width — about a
   third of the row. While editing it is the only visible child and should
   own the whole row, like the add form below the list does. */
.itinerary-stop-edit { flex: 1; min-width: 0; }

/* includes/icon.html emits a bare <svg viewBox> with no width/height —
   every consumer sizes it in CSS. Without this the trash icon collapsed to
   0x0 and the Remove button rendered as 8x8px of padding: present in the
   DOM, working on the server, and effectively impossible to hit. The two
   reorder buttons next to it looked fine because they hardcode their own
   inline <svg width="16">. */
.itinerary-stop-actions button svg { width: 16px; height: 16px; }
.itinerary-stop-actions button:hover:not(:disabled) { background: var(--color-sand); color: var(--color-ink); }
.itinerary-stop-actions button:disabled { opacity: 0.3; cursor: not-allowed; }

/* Weekday picker — availability recurrence rule (wizard_availability.html) */
.weekday-picker { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.weekday-toggle input[type="checkbox"] { position: absolute; opacity: 0; width: 1px; height: 1px; }
.weekday-toggle label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1.5px solid var(--color-border);
  background: var(--color-surface);
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
  color: var(--color-body);
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}
.weekday-toggle input:checked + label { background: var(--color-primary); border-color: var(--color-primary); color: #fff; }
.weekday-toggle input:focus-visible + label { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.rule-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-3);
}

/* Event Calendar theming (github.com/vkurko/calendar) — every custom
   property it exposes, mapped to our own tokens. includes/event_calendar.html */
.ec {
  --ec-bg-color: var(--color-surface);
  --ec-text-color: var(--color-ink);
  --ec-border-color: var(--color-border);
  --ec-button-bg-color: var(--color-surface);
  --ec-button-border-color: var(--color-border);
  --ec-button-text-color: var(--color-ink);
  --ec-button-active-bg-color: var(--color-sand);
  --ec-button-active-border-color: var(--color-primary);
  --ec-button-active-text-color: var(--color-primary);
  --ec-today-bg-color: var(--color-sand);
  --ec-highlight-color: var(--color-sand);
  --ec-event-bg-color: var(--color-primary);
  --ec-event-text-color: #fff;
  --ec-bg-event-color: var(--color-sand);
  --ec-now-indicator-color: var(--color-accent);
  --ec-popup-bg-color: var(--color-surface);
  font-family: var(--font-sans);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.ec-day {
  cursor: pointer;
  transition: background-color 0.12s ease, box-shadow 0.12s ease;
}
.ec-day:hover { background-color: var(--color-sand); box-shadow: inset 0 0 0 1px var(--color-border); }
.ec-day.ec-today:hover { box-shadow: inset 0 0 0 1px var(--color-primary-light); }
.ec-day-number { text-align: right; padding: var(--space-1) var(--space-2) 0; font-size: 0.875rem; }
/* Day badges: open slots, plus bookings when there are any.
   `flex-wrap` rather than a row that can overflow — two badges on a
   narrow cell stack instead of spilling past the cell border, which is
   what the single badge used to do with "9 slots" at ~48px wide. */
.ec-day-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem;
  margin: var(--space-1) var(--space-2) var(--space-2);
}
.ec-day-badge {
  display: inline-flex;
  align-items: baseline;
  gap: 0.25rem;
  padding: 0.15rem 0.5rem;
  background: var(--color-primary);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 600;
  border-radius: var(--radius-pill);
  /* Never wider than the cell, whatever the language does to the word. */
  max-width: 100%;
  overflow: hidden;
}
.ec-day-badge .ec-badge-word { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Bookings. Accent rather than danger red: a booking is the good news on
   this page, and --color-danger is what a cancellation or an error uses
   everywhere else on the site. Still unmistakably a different colour from
   the green, which is the point. */
.ec-day-badge.is-booked { background: var(--color-accent-btn); }
/* Below this the cell is too narrow for any word at all, so the number
   carries it and the colour says which number it is. The word stays in
   the badge's `title`, and the day panel spells it out on click. */
@media (max-width: 720px) {
  .ec-day-badges { margin-inline: var(--space-1); gap: 0.15rem; }
  .ec-day-badge { padding: 0.1rem 0.4rem; }
  .ec-day-badge .ec-badge-word { display: none; }
}
.event-calendar-day-panel {
  margin-top: var(--space-5);
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.event-calendar-day-panel h4 { margin: 0 0 var(--space-3); }

/* Location settings strip (dashboard/requests_inbox.html) — deliberately
   flat and sand-toned with a dashed border, unlike the elevated white
   .card-body request items below it: this is a filter/preference control,
   not a piece of content, and it needs to read that way at a glance. */
.location-settings {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-5);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-6);
  background: var(--color-sand);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
}
.location-settings-header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  flex: 0 0 240px;
}
.location-settings-icon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-primary);
}
.location-settings-icon svg { width: 16px; height: 16px; }
.location-settings-body {
  flex: 1 1 280px;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

/* Location subscription chips — white pills read as tags against the
   settings strip's sand background (they'd disappear against the old
   all-white card). */
.location-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.3rem 0.4rem 0.3rem 0.75rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  font-size: 0.8125rem;
  color: var(--color-ink);
}
.location-chip form { margin: 0; }
.location-chip button {
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.location-chip button:hover { background: var(--color-sand); color: var(--color-ink); }

/* Proposal negotiation thread ------------------------------------------ */
.proposal-thread-timeline { padding-left: var(--space-4); border-left: 2px solid var(--color-border); }
.proposal-round { padding: var(--space-2) 0; }
.proposal-round + .proposal-round { border-top: 1px dashed var(--color-border); }

/* Site-wide dismissible banner (SiteBanner) ------------------------------
   Sits in normal document flow above the nav — not fixed/sticky, so it
   scrolls away like any other content rather than permanently eating
   viewport height on small screens. */
.site-banner { background: var(--color-primary); color: var(--color-paper); }
.site-banner-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  text-align: center;
}
.site-banner-message { margin: 0; font-size: 0.9375rem; }
.site-banner-cta { color: var(--color-accent); font-weight: 600; text-decoration: underline; margin-left: var(--space-2); }
.site-banner-cta:hover { color: var(--color-paper); }
.site-banner-dismiss {
  flex: none;
  background: none;
  border: none;
  padding: var(--space-1);
  margin: 0;
  color: var(--color-paper);
  opacity: 0.8;
  cursor: pointer;
  line-height: 0;
}
.site-banner-dismiss:hover { opacity: 1; }
.site-banner-dismiss svg { width: 18px; height: 18px; }
@media (max-width: 600px) {
  .site-banner-inner { flex-wrap: wrap; padding: var(--space-3) var(--space-4); }
  .site-banner-message { font-size: 0.875rem; }
}

/* Loading spinner (includes/spinner.html) --------------------------------
   The logo mark filling up like an hourglass: a faint copy of the pin sits
   underneath, and a full-colour copy on top is revealed bottom-up by an
   animated clip-path, holds for a beat, then drains away from the bottom.
   Deliberately not a rotating arc — the mark is the one shape that is ours,
   and a fill reads at 18px inside a button as well as at 56px on a card. */
.gu-spinner {
  --gu-spinner-size: 28px;
  --gu-spinner-cycle: 1700ms;
  position: relative;
  display: inline-block;
  flex: none;
  width: var(--gu-spinner-size);
  height: var(--gu-spinner-size);
  vertical-align: -0.25em;
  line-height: 0;
}
.gu-spinner-sm { --gu-spinner-size: 18px; }
.gu-spinner-lg { --gu-spinner-size: 56px; }
.gu-spinner > span { position: absolute; inset: 0; }
.gu-spinner svg { display: block; width: 100%; height: 100%; }
.gu-spinner-ghost { opacity: 0.18; }

.gu-spinner-half-a { fill: var(--color-primary); }
.gu-spinner-half-b { fill: var(--color-accent); }
/* On a filled button or a dark band the teal half disappears into the
   background, so this variant drops the two-tone brand colours for two
   weights of the surrounding text colour instead. */
.gu-spinner-mono .gu-spinner-half-a { fill: currentColor; }
.gu-spinner-mono .gu-spinner-half-b { fill: currentColor; opacity: 0.55; }

.gu-spinner-fill { animation: gu-spinner-fill var(--gu-spinner-cycle) cubic-bezier(0.55, 0.1, 0.35, 0.9) infinite; }
/* Rises from the bottom, holds full (42%→58%), then empties from the bottom
   again. The jump from a top-clipped to a bottom-clipped box at the loop
   point is invisible because the mark is fully hidden at both ends. */
@keyframes gu-spinner-fill {
  0% { clip-path: inset(100% 0 0 0); }
  42% { clip-path: inset(0 0 0 0); }
  58% { clip-path: inset(0 0 0 0); }
  100% { clip-path: inset(0 0 100% 0); }
}

/* A gold comet running the pin's outline, so there is still movement during
   the fill's hold beat. Only rendered at lg (see includes/spinner.html) —
   at button size it is a flicker rather than a detail. */
.gu-spinner-trace path {
  fill: none;
  stroke: var(--color-accent);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 20 80;
  animation: gu-spinner-trace var(--gu-spinner-cycle) linear infinite;
}
.gu-spinner-mono .gu-spinner-trace path { stroke: currentColor; }
@keyframes gu-spinner-trace { to { stroke-dashoffset: -100; } }

/* A spinner and the label next to it, as one block — the standing pattern
   for "this region is busy" (AI drafting, slow submits). */
.gu-loading {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) 0;
}
.gu-loading-text { margin: 0; font-weight: 600; color: var(--color-ink); }
.gu-loading-text + .gu-loading-hint { margin: var(--space-1) 0 0; }
.gu-loading-hint { font-size: 0.875rem; color: var(--color-muted); }

/* Sits at the trailing edge of a text input while its autocomplete request
   is in flight. RTL-safe (inset-inline-end) and translated off the vertical
   centre by half its own height. */
.gu-spinner-in-field {
  position: absolute;
  inset-inline-end: var(--space-3);
  top: 50%;
  margin-top: calc(var(--gu-spinner-size) / -2);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  /* Keep something moving — a still spinner reads as a hung page — but drop
     the travelling fill for a slow, low-amplitude pulse of the whole mark. */
  .gu-spinner-fill { animation: gu-spinner-pulse 1800ms ease-in-out infinite; clip-path: none; }
  .gu-spinner-trace path { animation: none; stroke-dasharray: none; opacity: 0.35; }
  @keyframes gu-spinner-pulse {
    0%, 100% { opacity: 0.35; }
    50% { opacity: 1; }
  }
}

/* Autocomplete input + in-field spinner wrapper. The pickers that use pills
   (languages, themes) put the spinner inside .language-pills instead, which
   is the same idea — the bordered box the user perceives as "the input". */
.autocomplete-input { position: relative; }
.autocomplete-input input { padding-inline-end: calc(18px + var(--space-3) * 2); }
.language-pills { position: relative; }

/* Live price recalculation on the tour page: the stale figures fade back
   while the new ones are fetched, with the mark filling over them. Dimming
   rather than blanking keeps the card's height stable, so the Book button
   below it never jumps. */
.price-slot-wrap { position: relative; }
.price-slot { transition: opacity 150ms ease; }
.price-slot-wrap.htmx-request .price-slot { opacity: 0.4; }
.price-slot-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.gu-loading-centered { justify-content: center; }

/* Busy state for a plain (non-htmx, non-Alpine) button — the Stripe payment
   button toggles .is-busy by hand. Reserving no space when idle is fine
   here: the click that reveals it is also the last one the button takes. */
.btn-busy { display: none; }
.btn.is-busy .btn-busy { display: inline-flex; }

/* Skip link ------------------------------------------------------------- */
/* Off-screen rather than display:none — a hidden element can't take focus,
   which is the one thing this has to do. It reappears on :focus, not
   :focus-visible: it is only ever reached by keyboard, so focus-visible
   would be the same set of users with an extra way to fail. */
.skip-link {
  position: absolute;
  left: var(--space-4);
  top: -100%;
  z-index: 200;
  padding: var(--space-3) var(--space-5);
  background: var(--color-primary);
  color: #fff;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  font-weight: 600;
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-link:focus {
  top: 0;
  color: #fff;
  text-decoration: none;
}
/* main takes tabindex="-1" so the browser actually moves focus there on
   activation instead of only scrolling; that would otherwise leave the
   next Tab back at the top of the nav. The outline is suppressed because
   it's a programmatic target, never a user-chosen one. */
main:focus { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}

/* Form error summary ---------------------------------------------------- */
/* Sits at the top of a long form listing every failure — see
   includes/form_error_summary.html for why. Danger-tinted rather than
   neutral: this is the one block on the page the user must not skim past. */
.form-error-summary {
  border: 1px solid var(--color-danger);
  border-left-width: 4px;
  background: var(--color-danger-bg);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}
.form-error-summary:focus { outline: 2px solid var(--color-danger); outline-offset: 2px; }
.form-error-summary-title {
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-danger);
  margin: 0 0 var(--space-2);
}
.form-error-summary ul { margin: 0; padding-left: 1.15rem; }
.form-error-summary li { margin-bottom: var(--space-1); font-size: 0.9375rem; }
.form-error-summary li:last-child { margin-bottom: 0; }
.form-error-summary a { color: var(--color-danger); text-decoration: underline; }
.form-error-summary a:hover { text-decoration: none; }

/* <picture> ------------------------------------------------------------- */
/* Every image rule on this site targets `img` and sizes it against the
   card or collage container around it. A <picture> wrapper would sit
   between the two as an inline box, and `width: 100%` on the img would
   then resolve against a shrink-to-fit inline parent instead of the
   container — the hero collage collapses. `display: contents` makes the
   wrapper generate no box at all, so the img stays a layout child of the
   thing that sizes it and none of those rules need to know <picture> is
   there. */
picture { display: contents; }
/* And <source> must not become one. `display: contents` promotes ALL of
   picture's children to layout items, and <source> is one of them — in a
   two-column grid that makes an invisible first item, pushes the image
   into column two and wraps the text onto row two. That is exactly what
   it did to the "Real guides, not a call center" section: measured
   `children=2, cols=552px 552px` with the text landing at y=2430 instead
   of beside the photo. No UA stylesheet reliably hides it here, so this
   rule does. */
picture > source { display: none; }

/* Screen-reader-only text ------------------------------------------------ */
/* Visually gone, still announced. Not `display: none` or
   `visibility: hidden` — both remove it from the accessibility tree too,
   which defeats the purpose. The 1px-clip technique is the one that
   survives every screen reader worth supporting. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Active search filters -------------------------------------------------- */
.search-summary { gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.25rem 0.7rem;
  background: var(--color-sand);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  font-size: 0.8125rem;
  color: var(--color-ink);
  text-decoration: none;
}
.filter-chip:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
  text-decoration: none;
}
.filter-chip-x { font-size: 1rem; line-height: 1; color: var(--color-muted); }
.filter-chip:hover .filter-chip-x { color: var(--color-primary); }

/* Mobile booking bar ----------------------------------------------------- */
/* Below 900px the tour layout collapses and the booking card falls below
   the description and reviews. This keeps the price and one action
   reachable from anywhere on the page. */
.booking-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  /* env() keeps the bar clear of the iOS home indicator; the fallback in
     the first value is what non-iOS browsers use. */
  padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  box-shadow: 0 -4px 16px rgba(26, 36, 32, 0.08);
  transform: translateY(0);
  transition: transform 0.2s ease;
}
.booking-bar.is-hidden { transform: translateY(110%); }
.booking-bar-price { display: flex; align-items: baseline; gap: var(--space-1); min-width: 0; }
.booking-bar-amount { font-family: var(--font-serif); font-size: 1.25rem; color: var(--color-ink); }
.booking-bar-unit { font-size: 0.8125rem; color: var(--color-muted); }
.booking-bar-cta { flex: none; }

/* The bar overlays the end of the page, so the last thing on it needs
   room to clear — otherwise the footer's final row sits under the bar and
   can't be read or tapped. */
@media (max-width: 899px) {
  body:has(.booking-bar) { padding-bottom: 5.5rem; }
}
@media (min-width: 900px) {
  .booking-bar { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .booking-bar { transition: none; }
}

/* Wizard: what's still outstanding ---------------------------------------- */
.wizard-outstanding {
  margin-top: var(--space-5);
  padding: var(--space-3) var(--space-4);
  background: var(--color-warning-bg);
  border-left: 3px solid var(--color-warning);
  border-radius: var(--radius-sm);
  font-size: 0.8125rem;
  color: var(--color-body);
}
.wizard-outstanding-title { margin: 0 0 var(--space-2); font-weight: 600; color: var(--color-warning); }
.wizard-outstanding ul { margin: 0; padding-left: 1.1rem; }
.wizard-outstanding li { margin-bottom: 2px; }
/* Ready to submit is good news, not a warning — same block, green. */
.wizard-outstanding.is-ready {
  background: var(--color-success-bg);
  border-left-color: var(--color-success);
  color: var(--color-success);
  font-weight: 600;
}

/* Itinerary reorder animation -------------------------------------------- */
/* Each row carries a unique view-transition-name (see
   dashboard/_itinerary_list.html), so when htmx swaps the list with
   `transition:true` the browser tweens each row from its old position to
   its new one instead of the order simply changing under the cursor.

   Nothing here is required for the feature to work — a browser without
   View Transitions swaps the list instantly, which is still better than
   the full page reload this replaced. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(*) { animation-duration: 0.22s; }
}
/* A reordered row is the same element in a new place, so the default
   cross-fade of its contents is wrong — it should just move. */
::view-transition-old(*),
::view-transition-new(*) { animation: none; mix-blend-mode: normal; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*) { animation: none; }
}

/* Two-step delete on an itinerary stop ----------------------------------- */
/* Armed state: the control widens to fit the word and turns the danger
   colour, so it reads as "this one is about to go" rather than as a
   generic button that happens to say Delete. It reverts on its own after
   4s and on a click anywhere else, so an accidental arm doesn't leave a
   live destructive control sitting in the page. */
.itinerary-stop-delete { transition: background-color 0.15s ease, color 0.15s ease; }
.itinerary-stop-delete.is-confirming {
  background: var(--color-danger-bg);
  color: var(--color-danger);
  font-size: 0.75rem;
  font-weight: 700;
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}
.itinerary-stop-delete.is-confirming:hover { background: var(--color-danger); color: #fff; }
.itinerary-stop-delete-confirm { animation: gu-delete-arm 0.18s ease-out; }
@keyframes gu-delete-arm {
  from { opacity: 0; transform: scale(0.85); }
  to   { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .itinerary-stop-delete { transition: none; }
  .itinerary-stop-delete-confirm { animation: none; }
}

/* Utility: hide via a bound class, for the cases where x-show misbehaves
   (see the note in dashboard/wizard_pricing.html). !important because it
   has to beat whatever display the component's own rule sets. */
.u-hidden { display: none !important; }

/* Price tier rows --------------------------------------------------------- */
/* Each row carries a view-transition-name keyed on its id, so removing one
   lets the rows below travel up rather than jumping. The editor swaps as a
   whole (see dashboard/_tier_editor.html), which without this would make
   every row appear to change at once. */
/* The wizard's "add a row" forms — optional extras, price tiers. A row on a
   wide screen, stacked on a narrow one.

   flex:2 / flex:1 alone cannot do that. It's shorthand for flex:N 1 0%, and a
   zero basis lets a field shrink below its own content while the button next
   to it keeps its full intrinsic width. At 360px that left the extras name
   field about 90px wide with its help text wrapping down five lines, and the
   tier form is worse — five fields sharing the same crush. Giving each field
   a real basis makes the row wrap instead.

   Overrides .cluster's align-items (both are one class deep, and this file
   loads after grid.css). flex-end is what puts the checkbox and the button on
   the same baseline as the inputs, and it only holds while every field in the
   row is the same height — which is why no field in here carries help text
   under its input. The example goes in the placeholder instead. */
/* While photo-upload.js is resizing a selection. Shrinking four phone photos
   takes a moment on a mid-range phone, and the dropzone would otherwise look
   like it ignored the tap. */
.dropzone.is-preparing { opacity: 0.6; pointer-events: none; }
.dropzone.is-preparing .dropzone-text::after { content: " \2026"; }

.add-row-form { align-items: flex-end; }
.add-row-form .field { flex: 1 1 100%; margin-bottom: 0; }
.add-row-form > label.checkbox-field { margin: 0; }
.add-row-form > button { margin-bottom: 0; }

@media (min-width: 640px) {
  .add-row-form .field { flex: 1 1 7rem; }
  /* after .field, same specificity — the wider field wins the cascade */
  .add-row-form .field-grow { flex: 2 1 12rem; }
}

.tier-row {
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
}
.tier-row:last-of-type { border-bottom: none; }

/* The editor fades rather than snapping when it's swapped. htmx adds
   .htmx-swapping to the outgoing element for the duration below. */
#tier-editor.htmx-swapping { opacity: 0.55; transition: opacity 0.12s ease-out; }

@media (prefers-reduced-motion: reduce) {
  #tier-editor.htmx-swapping { transition: none; opacity: 1; }
}

/* Wizard step rail -------------------------------------------------------- */
/* The eight steps as a horizontal strip under the title. The sidebar keeps
   the same information on desktop; this is what a phone sees first, where
   the sidebar sits below the form. */
.wizard-rail {
  margin-top: var(--space-5);
  border-bottom: 1px solid var(--color-border);
  /* Scrolls sideways rather than wrapping: eight labels on three rows push
     the form off the first screen, which is the problem this exists to
     fix. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.wizard-rail::-webkit-scrollbar { display: none; }
/* Same visual language as the home page's carousel tabs (.tabs /
   .tab-button above): flat text, muted until it matters, a 3px accent
   underline on the one you're on. The rail used to be its own thing —
   bordered pill links wrapping circled number chips — so the site had two
   unrelated answers to "these are the sections, here's where you are".
   The rules below deliberately mirror .tab-button's weight, size, padding
   and underline rather than inventing a third.

   What the rail keeps beyond a plain tab strip is completion state, which
   a wizard genuinely has and a carousel doesn't: a check for done, a
   number for not. Both render as inline text at the tab's own size, not
   as bordered circles, so they read as part of the label. */
.wizard-rail ol {
  display: flex;
  gap: var(--space-5);
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: max-content;
}
.wizard-rail-link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  white-space: nowrap;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--color-muted);
  text-decoration: none;
  border-bottom: 3px solid transparent;
  transition: color 0.15s ease, border-color 0.15s ease;
}
a.wizard-rail-link:hover {
  color: var(--color-primary);
  border-bottom-color: var(--color-accent);
  text-decoration: none;
}
.wizard-rail-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
}
.wizard-rail-marker svg { width: 14px; height: 14px; }
/* Completed steps get the check in the brand green — the one place the
   rail says something a tab strip never has to. */
.wizard-rail-item.is-complete .wizard-rail-marker { color: var(--color-success); }
.wizard-rail-item.is-complete .wizard-rail-link { color: var(--color-ink); }
.wizard-rail-item.is-current .wizard-rail-link {
  color: var(--color-primary);
  border-bottom-color: var(--color-accent);
}
.wizard-rail-item.is-current .wizard-rail-marker { color: var(--color-primary); }
.wizard-rail-item.is-locked .wizard-rail-link { opacity: 0.45; cursor: default; }

/* No desktop size shrink any more. It used to drop the label to 0.8125rem
   on wide screens so the rail wouldn't compete with the sidebar, but the
   whole point of matching .tab-button is that a step strip looks the same
   wherever it appears — and a rail that changes size at 901px is a third
   variant, not a quieter one. The sidebar and the rail are distinguished
   by position and shape, which is enough. */
@media (prefers-reduced-motion: reduce) {
  .wizard-rail-link { transition: none; }
}

/* Two related fields side by side, stacking on narrow screens ------------- */
/* Unlike .field-grid-row this keeps each label with its own field, so it
   can collapse to one column without the labels detaching from their
   inputs. Used for the trip date range, where two date pickers do not fit
   side by side on a phone. */
.field-pair {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
@media (min-width: 640px) {
  .field-pair { grid-template-columns: 1fr 1fr; gap: var(--space-3); }
}

/* Guide profile -----------------------------------------------------------*/
.guide-hero {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}
.guide-hero h1 { margin-bottom: var(--space-1); }
.guide-hero-meta { margin: 0; }
.guide-hero-body { min-width: 0; }

/* Auto-fit rather than a fixed count: a guide with two languages and no
   response rate shouldn't leave two empty columns. */
.guide-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-6);
  padding: var(--space-5);
  background: var(--color-sand);
  border-radius: var(--radius-lg);
}
.guide-fact { min-width: 0; }
.guide-fact-label {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: var(--space-1);
}
.guide-fact-value { color: var(--color-ink); }

.guide-section { margin-top: var(--space-8); }

@media (max-width: 560px) {
  /* Stacked and centred below 560px. Side by side, an 80px avatar leaves
     too little for a long display name, which then wraps mid-word. */
  .guide-hero { flex-direction: column; align-items: center; text-align: center; }
  .guide-hero .cluster { justify-content: center; }
}

/* Language suggestion ------------------------------------------------------*/
/* A strip directly under the header, not a modal or a corner toast. It is a
   suggestion *about this page*, so it belongs attached to the page — and it
   must never cover content the reader came for. Sand rather than the accent
   colour: an offer, not an alert. */
.lang-suggestion {
  background: var(--color-sand);
  border-bottom: 1px solid var(--color-border);
}
.lang-suggestion-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
}
.lang-suggestion-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
/* The offer in the reader's own language leads and carries the weight —
   it is the line they can actually read. */
.lang-suggestion-primary {
  font-weight: 600;
  color: var(--color-primary);
}
/* The same sentence in the language on screen, quieter: it is there so the
   banner is never a mystery to whoever is already reading comfortably. */
.lang-suggestion-secondary {
  font-size: 0.8125rem;
  color: var(--color-muted);
}
.lang-suggestion-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}
.lang-suggestion-actions form { margin: 0; }

@media (max-width: 640px) {
  /* Stacked rather than squeezed: at this width the two sentences and two
     buttons on one row leave the primary line one word wide. */
  .lang-suggestion-inner {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }
  .lang-suggestion-actions { justify-content: flex-start; }
}

/* Impersonation banner (includes/impersonation_banner.html) --------------
   Deliberately does not look like the rest of the site. Everything else
   here is built to feel like one product; this is the one state where
   blending in is the bug, because the whole job of the banner is to keep
   a superuser from forgetting that "you" is currently somebody else.

   Danger colouring and no dismiss button, unlike .site-banner — that one
   is a campaign and dismissing it costs nothing. */
.impersonation-banner {
  background: var(--color-danger);
  color: #fff;
  position: relative;
  z-index: 60;
}
.impersonation-banner-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-5);
}
.impersonation-banner-message {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: 0.9375rem;
}
.impersonation-banner-message svg { flex: none; width: 18px; height: 18px; }
.impersonation-banner-who { opacity: 0.85; margin-left: var(--space-2); }
.impersonation-banner-exit {
  flex: none;
  padding: var(--space-1) var(--space-4);
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: var(--radius-pill);
  background: transparent;
  color: #fff;
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
}
.impersonation-banner-exit:hover { background: rgba(255, 255, 255, 0.15); }
@media (max-width: 600px) {
  .impersonation-banner-inner { padding: var(--space-2) var(--space-4); }
  .impersonation-banner-message { font-size: 0.875rem; }
  .impersonation-banner-who { display: block; margin-left: 0; }
}

/* Moderator tour preview (tours/_preview_banner.html, _preview_panel.html)
   The page under it is the real tour page, unchanged — that is the point,
   and it is also the hazard: nothing else on screen says whether this tour
   is live or has never been seen by anyone. So the banner is loud, in the
   same spirit as .impersonation-banner, while the side panel is quiet and
   sits where the booking widget would. */
.preview-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-warning);
  border-left-width: 4px;
  border-radius: var(--radius-md);
  background: var(--color-warning-bg);
  color: var(--color-warning);
}
.preview-banner-message {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 500;
}
.preview-banner-message svg { flex: none; width: 18px; height: 18px; }
.preview-banner-status { opacity: 0.85; margin-left: var(--space-2); font-weight: 400; }
.preview-banner-exit {
  flex: none;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-warning);
  text-decoration: underline;
}

.preview-panel-title { font-size: 1.05rem; margin: 0 0 var(--space-4); }
/* Two columns so a fact and its value read as one line, rather than as a
   label stack the eye has to pair up. */
.preview-panel-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-4);
  margin: 0 0 var(--space-4);
  font-size: 0.875rem;
}
.preview-panel-facts dt { color: var(--color-muted); }
.preview-panel-facts dd { margin: 0; color: var(--color-ink); }
.preview-panel-changes {
  margin-bottom: var(--space-4);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-sand);
}
.preview-panel-changes h3 { font-size: 0.8125rem; margin: 0 0 var(--space-2); color: var(--color-muted); }
.preview-panel-changes ul { margin: 0; padding-left: var(--space-4); font-size: 0.875rem; }

/* The rate card. Separated by a rule rather than a second card, because
   it is part of the same review and a moderator's eye should run down one
   column, not hop between boxes. */
.preview-panel-pricing {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}
.preview-panel-pricing h3 { font-size: 0.9375rem; margin: 0; }
.preview-panel-mode {
  margin: 0 0 var(--space-3);
  font-size: 0.8125rem;
  color: var(--color-muted);
}
.preview-panel-subhead {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-muted);
  margin: var(--space-4) 0 var(--space-2);
}
.preview-panel-extras {
  margin: 0;
  padding-left: var(--space-4);
  font-size: 0.875rem;
}
.preview-panel-extras li { margin-bottom: var(--space-1); }
.preview-panel-warn { color: var(--color-warning); }
/* Warnings from apps.tours.pricing. Loud on purpose: an uncovered group
   size means a traveller can reach a tour with no price at all, and this
   is the last screen before it goes live. */
.preview-panel-warnings {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-6);
  border-radius: var(--radius-sm);
  background: var(--color-warning-bg);
  border-left: 3px solid var(--color-warning);
  font-size: 0.8125rem;
}
.preview-panel-warnings li + li { margin-top: var(--space-2); }
@media (max-width: 600px) {
  .preview-banner-status { display: block; margin-left: 0; }
}

/* Error pages (404 / 403 / 400, via includes/_error_body.html) ------------
   500 is deliberately not styled from here — it inlines its own CSS so it
   can render when whatever broke has taken this file's page with it. */
.error-page { max-width: 40rem; text-align: center; }
.error-page-code {
  margin: 0 0 var(--space-2);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.error-page h1 { margin-bottom: var(--space-3); }
/* The search box, not just a "go home" button: a dead end on a
   marketplace is a lost session, and the reader wanted something
   specific. Centred and given room, because it is the point of the page
   rather than a footnote to the apology. */
.error-page-search { margin: var(--space-6) auto var(--space-5); max-width: 30rem; }
.error-page-links { justify-content: center; gap: var(--space-2); }

/* Moderator review panel (tours/_review_panel.html) ------------------------
   A drawer pinned to the corner rather than a sidebar: at review time the
   page itself is the thing being judged, so the checklist must be
   reachable without ever covering the photos, the itinerary or the
   meeting point. Fixed rather than absolute so it survives the long
   scroll a real review involves. */
.review-panel {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: 120;
  width: min(24rem, calc(100vw - var(--space-6)));
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2);
}
.review-panel-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--color-primary);
  color: #fff;
  border: 0;
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-lg);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
}
.review-panel-toggle svg { width: 16px; height: 16px; }
@media (max-width: 899px) {
  .review-panel-body { max-height: 55vh; }
}
/* The count of things the machine already doubts. Accent, not danger:
   these are hints for a human, not errors. */
.review-panel-count {
  display: inline-grid;
  place-items: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--color-accent-light);
  color: var(--color-ink);
  font-size: 0.75rem;
}
.review-panel-body {
  width: 100%;
  /* Shorter on a phone, where the drawer is the whole width: 70vh of
     checklist over a 100vh page leaves nothing of the tour visible, and
     the tour is the thing being judged. */
  max-height: min(70vh, 40rem);
  overflow-y: auto;
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.review-panel-status { margin: 0 0 var(--space-3); font-size: 0.8125rem; color: var(--color-muted); }
.review-panel-group {
  margin: var(--space-3) 0 var(--space-1);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.review-check {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2);
  align-items: start;
  padding: var(--space-1) 0;
  font-size: 0.875rem;
  cursor: pointer;
}
.review-check input { margin-top: 0.2rem; }
.review-check.is-flagged { color: var(--color-accent-dark); }
.review-check-flag {
  display: block;
  font-size: 0.75rem;
  color: var(--color-accent-dark);
}
.review-panel-note { display: block; margin-top: var(--space-4); font-size: 0.8125rem; }
.review-panel-note textarea { width: 100%; margin-top: var(--space-1); }
.review-panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.review-panel-actions .btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.review-panel-hint { margin: var(--space-2) 0 0; font-size: 0.75rem; color: var(--color-muted); }

@media (max-width: 700px) {
  /* Full width at the bottom on a phone, where a floating card would
     cover most of the page it is meant to be judging. */
  .review-panel { left: var(--space-3); right: var(--space-3); width: auto; }
}

/* Moderator notes dialog (dashboard/_moderator_notes.html) -----------------
   The history of what was asked for, on the page where a guide is fixing
   it. Same <dialog> reasoning as the photo lightbox: focus trap, Escape,
   inert background and a stylable ::backdrop, none of which a div with a
   z-index gets for free. */
.notes-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
}
.notes-trigger svg { width: 16px; height: 16px; flex-shrink: 0; }

/* A guide arriving here has been sent back and does not yet know why.
   The one thing on the page that answers that is this button, so it
   asks for their eye twice and then stops -- long enough to be noticed
   on arrival, short enough not to be a thing that flashes at you while
   you work.

   The colour change is not part of the animation and does not go away
   with it, which is what someone who has turned motion off is left
   with. */
.notes-trigger.is-urgent {
  border-color: var(--color-accent);
  color: var(--color-accent);
  animation: notes-pulse 0.5s ease-in-out 0.35s 2;
}
@keyframes notes-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(192, 138, 46, 0); transform: scale(1); }
  /* The ring alone is too polite to notice against a page the guide has
     just landed on. A 4% breath with it is unmistakable and still well
     short of a thing that demands to be clicked. */
  50% { box-shadow: 0 0 0 7px rgba(192, 138, 46, 0.32); transform: scale(1.04); }
}
@media (prefers-reduced-motion: reduce) {
  .notes-trigger.is-urgent { animation: none; }
}
.notes-dialog {
  padding: 0;
  border: 0;
  background: none;
  max-width: min(40rem, calc(100vw - var(--space-6)));
  width: 100%;
}
.notes-dialog::backdrop { background: rgba(16, 22, 20, 0.5); }
.notes-dialog-inner {
  padding: var(--space-5);
  background: var(--color-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  max-height: 80vh;
  overflow-y: auto;
}
.notes-dialog-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.notes-dialog-close {
  background: none;
  border: 0;
  color: var(--color-muted);
  cursor: pointer;
  padding: var(--space-2);
  line-height: 0;
  border-radius: var(--radius-sm);
}
.notes-dialog-close svg { width: 20px; height: 20px; }
.notes-dialog-close:hover { color: var(--color-ink); background: var(--color-sand); }
.notes-revision + .notes-revision {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}
.notes-revision-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-muted);
}
.notes-revision-points { margin: 0; padding-left: var(--space-5); }
.notes-revision-points li { margin-bottom: var(--space-2); }
.notes-revision-note {
  margin: var(--space-3) 0 0;
  padding: var(--space-3);
  background: var(--color-sand);
  border-radius: var(--radius-sm);
  font-style: italic;
}

/* The route map (templates/tours/_route_map.html) ---------------------------
   A baked basemap image with the route and the stops as live DOM over it.
   Everything is positioned in percentages so one 2x render scales to any
   width with the markers still landing where they should. */
.route-map {
  position: relative;
  margin: var(--space-6) 0 0;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--color-border);
  /* How far along the route to draw, 0 to 100. Set by
     static/js/route-map.js as the traveller reads the itinerary. */
  --route-progress: 0;
}
.route-map img {
  display: block;
  width: 100%;
  height: auto;
}
/* Pinned while the itinerary scrolls past it, so the map is still there
   when you reach step 6. Only on a screen tall enough that a pinned map
   leaves room to read: on a phone it would take the whole viewport and
   the list would scroll behind a picture of itself.

   The z-index keeps it above the list scrolling underneath while staying
   far below the site header's 100 — see the stacking note on .map-picker.
   The sticky ends with .route-and-itinerary, which is why that wrapper
   exists. */
@media (min-width: 1000px) {
  /* Beside the steps, not above them, and pinned: the map is the same
     numbered list seen from above, so having to scroll away from it to
     read step 6 defeats the point of pairing them at all. */
  .route-and-itinerary {
    display: grid;
    /* Half and half. This section is outside the content column and gets
       the whole grid width, so each side is around 550px rather than the
       340px it had while nested. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-8);
    align-items: start;
  }
  .route-and-itinerary > h3 { grid-column: 1; grid-row: 1; }
  .route-and-itinerary > .itinerary-timeline { grid-column: 1; grid-row: 2; }
  /* Row 2, not row 1: the map lines up with the first step's card rather
     than with the "What you'll do" heading above it. It belongs to the
     list, so it should start where the list starts. */
  .route-and-itinerary > .route-map {
    grid-column: 2;
    grid-row: 2;
    position: sticky;
    top: 5.5rem;
    margin-top: 0;
    z-index: 1;
  }
  /* Slightly smaller than in a full-width map: a marker's size is fixed
     while the ground under it shrinks, and five stops in one old town are
     already close together at full width. */
  .route-and-itinerary > .route-map .route-map-marker {
    width: 24px;
    height: 24px;
    font-size: 0.75rem;
  }
}

.route-map-line {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.route-map-line path {
  fill: none;
  stroke: var(--color-primary);
  /* stroke-width is set on the element, in the image's own pixels, so it
     scales with the map rather than being fixed. */
  stroke-linecap: round;
  stroke-linejoin: round;
  /* pathLength="100" in the markup means these are percentages of the
     route, not user units, so the offset is just "how much is left". */
  stroke-dasharray: 100;
  stroke-dashoffset: calc(100 - var(--route-progress));
  transition: stroke-dashoffset 700ms cubic-bezier(0.22, 0.61, 0.36, 1);
  /* A soft halo, because a dark line over dark map detail (a river, a
     railway) is otherwise hard to follow. */
  filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.9));
}

.route-map-markers {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}
.route-map-markers li {
  position: absolute;
  /* left/top are the stop's position, so the marker is centred on it
     rather than hanging below and to the right of it. */
  transform: translate(-50%, -50%);
}
/* Deliberately the same shape as .itinerary-stop-marker: the map and the
   list are meant to read as one component, which is the whole argument
   for building this rather than a cloud of identical pins. */
.route-map-marker {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid #fff;
  background: var(--color-muted);
  color: #fff;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  box-shadow: var(--shadow-sm);
  transition: transform 200ms ease, background-color 400ms ease;
}
/* Grey until the route reaches it, so the numbers read as a sequence
   being walked rather than as nine things that are all equally here. */
.route-map-marker.is-reached { background: var(--color-primary); }
.route-map-marker.is-active,
.route-map-marker:hover,
.route-map-marker:focus-visible {
  transform: scale(1.25);
  background: var(--color-accent);
  z-index: 2;
}
/* The other direction: hovering a marker highlights its itinerary entry. */
.itinerary-stop.is-active .itinerary-stop-marker { background: var(--color-accent); }

@media (prefers-reduced-motion: reduce) {
  .route-map-line path { transition: none; }
  .route-map-marker { transition: none; }
}

/* Placing the itinerary's stops (dashboard/_itinerary_map.html) ------------- */
.itinerary-map-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.itinerary-map-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  font-size: 0.8125rem;
  cursor: pointer;
  max-width: 22ch;
}
.itinerary-map-chip > span:nth-child(2) {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.itinerary-map-chip-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex: none;
  border-radius: 50%;
  background: var(--color-sand);
  color: var(--color-muted);
  font-weight: 600;
  font-size: 0.75rem;
}
/* Placed is the settled state, selected is the one being worked on. Both
   can be true, and the selected ring has to win. */
.itinerary-map-chip.is-placed .itinerary-map-chip-number {
  background: var(--color-primary);
  color: #fff;
}
.itinerary-map-chip.is-selected {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px rgba(22, 68, 60, 0.18);
}
.itinerary-map-chip-clear {
  color: var(--color-muted);
  font-size: 1rem;
  line-height: 1;
  padding: 0 0.15rem;
}
.itinerary-map-chip-clear:hover { color: var(--color-danger); }

/* The numbered pin, shaped like .itinerary-stop-marker and like the one
   the traveller ends up seeing, so all three read as the same thing. */
.itinerary-map-pin {
  display: flex !important;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--color-primary);
  color: #fff;
  border: 2px solid #fff;
  font-size: 0.8125rem;
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}

/* A message carrying a link or a phone number, seen by its recipient.
   Warning, not blocking — see apps.messaging.limits.flag_risky. */
.chat-bubble-warning {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-warning-bg);
  border-left: 3px solid var(--color-warning);
  color: var(--color-warning);
  font-size: 0.75rem;
  line-height: 1.4;
}

/* A traveller's own photos under their review (includes/review_card.html).
   Square thumbnails so a row of them reads as a set whatever shape the
   originals are, and object-fit: cover rather than letterboxing, because
   a review photo is a glimpse rather than a document. */
.review-photos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}
.review-photos img {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  display: block;
  transition: transform 150ms ease;
}
.review-photos button {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  display: block;
  border-radius: var(--radius-sm);
}
.review-photos button:hover img,
.review-photos button:focus-visible img { transform: scale(1.04); }

/* The chevron on a type-ahead that is really a picker (currency). A
   bordered box holding "EUR" reads as somewhere to type a code; the
   chevron says there is a list behind it, which there is. */
.autocomplete-input { position: relative; }
.autocomplete-chevron {
  position: absolute;
  right: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--color-muted);
  line-height: 0;
}
.autocomplete-chevron svg { width: 16px; height: 16px; }
.autocomplete-input input { padding-right: var(--space-8); }

/* ---------------------------------------------------------------------- */
/* Social studio (superuser only) — templates/core/social_studio.html      */
/* ---------------------------------------------------------------------- */

/* Two panels that scroll independently, because the form is long and the
   preview is the whole point of looking at the screen — scrolling to a
   field must not scroll the image out of view. */
.studio {
  display: grid;
  grid-template-columns: minmax(320px, 400px) 1fr;
  gap: var(--space-6);
  align-items: start;
  max-width: 1240px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-4);
}
.studio-panel { min-width: 0; }
.studio-edit { display: flex; flex-direction: column; gap: var(--space-4); }
.studio-head h1 { margin: 0 0 var(--space-1); font-size: 1.5rem; }
.studio-row { display: flex; gap: var(--space-3); align-items: flex-start; flex-wrap: wrap; }

/* The preview sticks while the form scrolls past it. */
.studio-view {
  position: sticky;
  top: var(--space-6);
  background: var(--color-sand);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  display: flex;
  justify-content: center;
}
.studio-figure { margin: 0; text-align: center; }
.studio-image {
  /* Height-capped, not width-capped: a 9:16 story at full width would run
     off the bottom of the screen, which is the format most worth seeing
     whole. */
  max-width: 100%;
  max-height: 74vh;
  width: auto;
  height: auto;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  background: var(--color-surface);
}
.studio-caption {
  margin-top: var(--space-3);
  font-size: 0.8125rem;
  color: var(--color-muted);
}
.studio-safe-key { display: block; margin-top: var(--space-1); }
.studio-hint {
  margin-top: var(--space-3);
  font-size: 0.875rem;
  color: var(--color-muted);
  text-align: center;
}

/* Radio pills — the same shape as a chip elsewhere, but each wraps its own
   input so the whole pill is the hit target. */
.studio-formats { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.studio-chip { position: relative; cursor: pointer; }
.studio-chip input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.studio-chip span {
  display: inline-block;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  font-size: 0.8125rem;
  color: var(--color-body);
  white-space: nowrap;
}
.studio-chip input:checked + span {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
}
.studio-chip input:focus-visible + span { outline: 2px solid var(--color-accent); outline-offset: 2px; }

.studio-range { width: 100%; }
.studio-check { display: flex; align-items: center; gap: var(--space-2); cursor: pointer; }
.studio-download { align-self: flex-start; }

@media (max-width: 900px) {
  /* Preview first: on a phone the form is taller than the screen, and
     burying the image under it defeats the tool. */
  .studio { grid-template-columns: 1fr; }
  .studio-view { position: static; order: -1; }
  .studio-image { max-height: 52vh; }
}

/* ---------------------------------------------------------------------- */
/* Two-factor authentication — templates/users/security.html and friends   */
/* ---------------------------------------------------------------------- */

.mfa-qr svg { width: 220px; height: 220px; }
/* Monospace and spaced: this gets read off a screen and typed into a
   phone, and in a proportional face "l1" and "O0" are a support email. */
.mfa-secret {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 1rem;
  letter-spacing: 0.14em;
  word-break: break-all;
  margin: var(--space-2) 0 0;
  color: var(--color-ink);
}
/* Wide and tracked, so six digits are checkable at a glance against the
   phone they were copied from. */
.mfa-code-input {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 1.5rem;
  letter-spacing: 0.35em;
  text-align: center;
}
.mfa-codes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2) var(--space-4);
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 1.0625rem;
  letter-spacing: 0.06em;
}
@media (max-width: 480px) {
  .mfa-codes { grid-template-columns: 1fr; }
}
/* Recovery codes exist to be kept on paper, so printing them should not
   also print the navigation. */
@media print {
  .mfa-codes { font-size: 1.25rem; }
  header, footer, nav, .alert, .btn { display: none !important; }
}

/* ---------------------------------------------------------------------- */
/* Ticket + check-in — templates/bookings/ticket.html and friends          */
/* ---------------------------------------------------------------------- */

/* In the flow under the nav, never over the content. The whole strip is
   the link: this gets tapped on a phone, outdoors, one-handed, while
   looking for a guide in a crowd. */
.ticket-strip {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-primary);
  color: #fff;
  text-decoration: none;
  font-size: 0.9375rem;
}
.ticket-strip:hover,
.ticket-strip:focus-visible { background: var(--color-primary-dark); text-decoration: none; color: #fff; }
.ticket-strip-icon { display: flex; flex: 0 0 auto; }
.ticket-strip-icon svg { width: 22px; height: 22px; }
.ticket-strip-text { display: flex; flex-direction: column; min-width: 0; flex: 1; line-height: 1.3; }
.ticket-strip-detail {
  font-size: 0.8125rem;
  opacity: 0.82;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ticket-strip-action {
  flex: 0 0 auto;
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 999px;
  padding: var(--space-1) var(--space-3);
  font-weight: 600;
  font-size: 0.8125rem;
}

/* The ticket itself is one thing held up to another person, so it is
   centred, uncluttered, and everything else is off the page. */
.ticket-page { max-width: 420px; text-align: center; }
.ticket-title { font-size: 1.375rem; margin-bottom: var(--space-1); }
.ticket-when { margin-top: 0; }
.ticket-qr {
  background: #fff;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  display: inline-block;
  margin: var(--space-5) 0 var(--space-3);
}
.ticket-qr svg { display: block; width: 240px; height: 240px; }
.ticket-instruction { font-weight: 600; margin: 0 0 var(--space-5); }
.ticket-code-block {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-4);
}
.ticket-code-label {
  font-size: 0.8125rem;
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
/* Big, monospaced and spaced out: this gets read aloud across a pavement
   and typed into someone else's phone. */
.ticket-code {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 2rem;
  letter-spacing: 0.18em;
  margin: var(--space-2) 0 0;
  color: var(--color-primary);
}
.ticket-foot { margin-top: var(--space-6); text-align: left; }
.ticket-back { margin-top: var(--space-6); }
.ticket-done { padding: var(--space-8) 0; }
.ticket-done svg { width: 56px; height: 56px; color: var(--color-primary); }

.checkin-code-input {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 1.375rem;
  letter-spacing: 0.16em;
  text-align: center;
  text-transform: uppercase;
}

/* A ticket is the one page somebody might genuinely want on paper. */
@media print {
  .ticket-strip, header, footer, nav, .ticket-back { display: none !important; }
}

/* The check-in confirmation. Its own page, so a guide glancing at it
   mid-greeting knows it worked without reading anything. */
.checkin-done { max-width: 420px; text-align: center; padding-top: var(--space-8); }
.checkin-done h1 { font-size: 1.5rem; margin: var(--space-5) 0 var(--space-2); }
.checkin-done-detail { margin-top: 0; }
.checkin-done-actions { margin-top: var(--space-7); }

.checkin-tick { width: 96px; height: 96px; }
.checkin-tick-ring {
  stroke: var(--color-primary);
  stroke-width: 2.5;
  /* 2πr for r=24, so the dash is exactly one circumference and the ring
     draws itself from nothing rather than sliding into place. */
  stroke-dasharray: 151;
  stroke-dashoffset: 151;
  animation: checkin-draw-ring 0.45s ease-out forwards;
}
.checkin-tick-mark {
  stroke: var(--color-primary);
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 40;
  stroke-dashoffset: 40;
  /* Starts as the ring finishes: the tick lands, rather than the two
     racing each other. */
  animation: checkin-draw-mark 0.3s 0.35s ease-out forwards;
}
@keyframes checkin-draw-ring { to { stroke-dashoffset: 0; } }
@keyframes checkin-draw-mark { to { stroke-dashoffset: 0; } }

/* Drawn already, no motion. The tick still has to be there — it is the
   whole message — so this sets the end state rather than hiding it. */
@media (prefers-reduced-motion: reduce) {
  .checkin-tick-ring,
  .checkin-tick-mark {
    animation: none;
    stroke-dashoffset: 0;
  }
}

/* ---------------------------------------------------------------------- */
/* Text somebody typed                                                     */
/* ---------------------------------------------------------------------- */

/* Anything a user wrote can contain a run no browser wants to break — a
   URL, a long German compound, or (the case that found this) ninety-six
   hyphens a guide used as a divider line.
 *
 * `anywhere` rather than `break-word`, and the difference is the whole
   point: both stop the text spilling visually, but only `anywhere`
   reduces the element's *intrinsic minimum*. These blocks sit in grid
   tracks sized from their contents, so with `break-word` the track still
   grew to 512px inside a 360px phone and the entire page scrolled
   sideways. Firefox only: Chromium happens to break a hyphen run, which
   is why it looked fine on one browser and not the other. */
.user-text {
  overflow-wrap: anywhere;
}

/* The keyboard cursor in a type-ahead. Same treatment as hover on
   purpose: arrowing down should look like pointing at the row, not like a
   second, competing kind of selection. */
.autocomplete-option.is-active { background: var(--color-sand); }

/* The scheduling block on a guide's profile. Set apart because it is a
   different kind of decision from the rest of the form — everything else
   describes the guide, this one changes what travellers can book. */
.scheduling-block {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5) var(--space-5);
  margin: var(--space-6) 0;
}
.scheduling-block legend {
  padding: 0 var(--space-2);
  margin: 0;
}
.scheduling-example {
  /* Bottom margin so the next field's label doesn't sit flush against the
     quote rule and read as part of the example. */
  margin: var(--space-3) 0 var(--space-5);
  padding-left: var(--space-3);
  border-left: 2px solid var(--color-accent);
  font-size: 0.875rem;
  color: var(--color-muted);
}
.scheduling-block input[type="number"] { max-width: 8rem; }

/* Days a guide is away — templates/dashboard/calendar.html. Sits under
   the calendar because it is about the same thing the calendar shows,
   and reads as a short list rather than a form with a list attached. */
.time-off { margin-top: var(--space-8); max-width: 620px; }
.time-off-list { list-style: none; padding: 0; margin: var(--space-4) 0; }
.time-off-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
}
.time-off-dates { font-weight: 600; }
/* The guide dashboard's tab row. Was inline styles on the <nav>; it moved
   here when the current tab needed a state of its own.

   The current tab takes the ghost button's own hover fill plus a weight
   bump and a 2px rule along its bottom edge. The rule is an inset shadow
   rather than an absolutely positioned bar sitting on the nav's border,
   because this row wraps onto two lines on a phone and only the wrapped
   item's own edge is in a predictable place. */
.dashboard-nav {
  margin-bottom: var(--space-6);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
/* The current tab wears the same gold underline as .tab-button.is-active
   on the home page, because it is the same idea and the site should only
   have one of those. The pill it used to wear was the ghost button's own
   hover fill, which made "where you are" and "where the pointer is" two
   shades of the same thing.

   ::after rather than a border-bottom, inset by the button's own
   horizontal padding: it lines the rule up with the label instead of the
   hit area, and it survives this row wrapping onto two lines on a phone,
   which a rule positioned against the nav's own edge would not. */
.dashboard-nav .is-current {
  position: relative;
  font-weight: 600;
  color: var(--color-primary);
}
.dashboard-nav .is-current::after {
  content: "";
  position: absolute;
  left: 0.9rem;
  right: 0.9rem;
  bottom: 0;
  height: 3px;
  border-radius: 2px;
  background: var(--color-accent);
}
.dashboard-nav .is-current:hover { background: var(--color-sand); }

/* Two dates side by side on a desktop, stacked on a phone — a date input
   is unusably narrow if you squeeze three onto one row. */
.time-off-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3) var(--space-4);
  /* start, not end: only one of the two date fields carries help text,
     and aligning on the bottom edge lifted the other input above its
     neighbour by exactly the height of that line. */
  align-items: start;
  margin-top: var(--space-4);
}
.time-off-form .time-off-note { grid-column: 1 / -1; }
.time-off-form button { justify-self: start; }
@media (max-width: 560px) {
  .time-off-form { grid-template-columns: 1fr; }
}

/* A day the guide has blocked. Muted rather than hidden — the slots still
   exist and come back when the absence is removed, and a day that simply
   vanished would read as data loss. */
.ec-day-badge.is-away {
  background: var(--color-sand);
  color: var(--color-muted);
  border: 1px solid var(--color-border);
}
.ec-day-badges.is-away .ec-day-badge:not(.is-away) {
  opacity: 0.45;
  text-decoration: line-through;
}

/* --- AI provenance badge (templates/includes/ai_badge.html) -------------
   Beside the label of any field the drafter wrote. Guide-facing only:
   this never appears on the public tour page, where a traveller reading
   "drafted by AI" beside a description learns nothing they can act on
   and trusts the listing less for it. The guide is the one who can go
   and check it, so the guide is who gets told.

   Deliberately quiet. It sits next to a label on a form the guide is
   already working through, and a badge that competes with the field it
   describes is noise on every step. */
.ai-badge {
  position: relative;
  display: inline-block;
  margin-left: var(--space-2);
  /* Labels are the baseline here; the badge should ride with the text
     rather than push the row taller. */
  vertical-align: baseline;
}

.ai-badge-mark {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  padding: 0.1em 0.5em;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: none;
  font: inherit;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.6;
  cursor: help;
  /* A real button, so it is reachable by keyboard and tappable on a
     phone — `title` is neither. The cursor says "explanation", not
     "action", because pressing it changes nothing. */
}

.ai-badge-mark svg {
  width: 0.95em;
  height: 0.95em;
  flex: none;
}

.ai-badge-ai .ai-badge-mark {
  color: var(--color-accent-dark);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}

.ai-badge-edited .ai-badge-mark {
  color: var(--color-muted);
  background: color-mix(in srgb, var(--color-muted) 10%, transparent);
}

.ai-badge-mark:hover,
.ai-badge-mark:focus-visible {
  border-color: currentColor;
}

.ai-badge-help {
  position: absolute;
  z-index: 20;
  top: calc(100% + var(--space-1));
  left: 0;
  /* Never wider than the viewport on a phone, where this badge sits at
     the right-hand end of a label and would otherwise open off-screen. */
  width: max-content;
  max-width: min(30ch, calc(100vw - 2 * var(--space-4)));
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-md, 0 6px 20px rgb(0 0 0 / 12%));
  color: var(--color-text);
  font-size: 0.8125rem;
  font-weight: 400;
  line-height: 1.45;
  white-space: normal;
}

/* On a narrow screen the badge can sit far enough right that a
   left-anchored panel still overflows. Anchoring both edges lets it use
   the width it has and wrap instead. */
@media (max-width: 560px) {
  .ai-badge {
    position: static;
  }

  .ai-badge-help {
    left: var(--space-4);
    right: var(--space-4);
    width: auto;
    max-width: none;
  }

  .field {
    position: relative;
  }
}

/* --- Breadcrumbs (templates/includes/breadcrumbs.html) ------------------
   Quiet. It is orientation, not navigation anybody came for, and it sits
   above the title on every tour page. */
.breadcrumbs {
  margin-bottom: var(--space-2);
  font-size: 0.8125rem;
  color: var(--color-muted);
}

.breadcrumbs ol {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.breadcrumbs li + li::before {
  content: "›";
  margin-right: var(--space-2);
  /* Decorative: the list structure already says these are steps, and a
     screen reader reading "single right-pointing angle quotation mark"
     between every crumb is noise. */
  speak: never;
}

.breadcrumbs a { color: inherit; }
.breadcrumbs a:hover { color: var(--color-text); }
.breadcrumbs [aria-current="page"] {
  /* The current page is not a link and should not look like one, but it
     is still the crumb a reader scans for. */
  color: var(--color-text);
}

/* --- Destination pages (templates/cms/destination_page.html) ----------- */
.destination-header { margin-bottom: var(--space-5); }

.destination-stats {
  margin: var(--space-2) 0 0;
  color: var(--color-muted);
}

.destination-intro {
  max-width: 68ch;
  margin-bottom: var(--space-8);
}

.destination-intro p { margin-bottom: var(--space-4); }

.destination-tours-heading { margin-bottom: var(--space-5); }

.destination-nearby { margin-top: var(--space-10); }

.destination-nearby-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}

.destination-group + .destination-group { margin-top: var(--space-8); }

/* --- Search-snippet counter (templates/includes/snippet_counter.html) ---
   Under short_description on the wizard's Basics step. */
.snippet-counter { margin-top: var(--space-2); }

.snippet-counter-bar {
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--color-border);
  overflow: hidden;
}

.snippet-counter-bar span {
  display: block;
  height: 100%;
  border-radius: inherit;
  transition: width 120ms linear, background-color 120ms linear;
}

/* Three states, and the colours are the message: too short is the
   default (most listings), good is the target, over is a note rather
   than a fault — Google truncates, it does not penalise. */
.snippet-counter-bar .is-short { background: var(--color-muted); }
.snippet-counter-bar .is-good { background: var(--color-success, #2e7d5b); }
.snippet-counter-bar .is-over { background: var(--color-accent); }

.snippet-counter-text {
  margin: var(--space-1) 0 0;
  font-size: 0.8125rem;
  color: var(--color-muted);
}

/* --- Per-photo alt text (templates/dashboard/wizard_photos.html) -------- */
.photo-thumb-alt { margin-top: var(--space-1); }

.photo-thumb-alt input {
  width: 100%;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  font-size: 0.8125rem;
}

.photo-thumb-alt input::placeholder { color: var(--color-muted); }

/* --- Request prompt on a destination page ------------------------------
   Below the tours, not above: somebody who can book what they want
   should book it. This is for the reader who got through the intro,
   didn't find their thing in the list, and would otherwise close the
   tab. */
.destination-request {
  margin-top: var(--space-10);
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface-muted, var(--color-surface));
}

.destination-request h2 { margin: 0 0 var(--space-2); }

.destination-request p {
  max-width: 60ch;
  margin: 0 0 var(--space-4);
  color: var(--color-muted);
}

/* Countries on the destinations index and cities on a country page use
   the same list as "also in this country" — one shape for one kind of
   thing, at three levels of the tree. */
.destination-cities { margin-bottom: var(--space-8); }
.destination-country-list li { font-size: 1.0625rem; }

/* --- Request form: who's coming, and budget ---------------------------- */
.request-what-happens {
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.9375rem;
  color: var(--color-muted);
}

.request-what-happens li {
  position: relative;
  padding-left: var(--space-5);
  margin-bottom: var(--space-1);
}

.request-what-happens li::before {
  content: "→";
  position: absolute;
  left: 0;
  color: var(--color-accent-dark);
}

.request-party,
.request-budget {
  border: 0;
  padding: 0;
  margin-bottom: var(--space-5);
}

.request-party legend,
.request-budget legend {
  padding: 0;
  margin-bottom: var(--space-2);
  font-weight: 600;
}

.request-budget-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.request-budget-option {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /* The base `label` rule adds a bottom margin, for a label sitting above
     its field. These sit in a wrapping row, where it prints as an uneven
     gap between the two lines of pills. */
  margin-bottom: 0;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border-interactive, var(--color-border));
  border-radius: var(--radius-pill);
  cursor: pointer;
}

/* The base `input` rule is sized for a full-width text field: width 100%,
   plus its own padding and border. On a radio inside a flex pill that
   stretches the control across the whole pill and squeezes the text down
   to min-content, so "Money isn't the constraint" wrapped one word per
   line inside a pill wide enough for all four. Same override
   .checkbox-field needs, for the same reason. */
.request-budget-option input {
  width: auto;
  flex: none;
  margin: 0;
  padding: 0;
}

/* The radio stays in the DOM and focusable — it is what a keyboard and a
   screen reader use. :has() moves the visual state onto the label around
   it so the whole pill is the target. */
.request-budget-option:has(input:checked) {
  border-color: var(--color-accent-dark);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  font-weight: 500;
}

.request-budget-option:has(input:focus-visible) {
  outline: 2px solid var(--color-accent-dark);
  outline-offset: 2px;
}

.request-budget-exact {
  margin-top: var(--space-4);
}

.request-budget-exact summary {
  cursor: pointer;
  font-size: 0.9375rem;
  color: var(--color-muted);
}

/* The call to action under /guide-with-us/. Its own rule rather than
   `.card .stack` plus three inline styles: `.stack` applies one uniform
   margin between boxes, and these boxes have very different line-heights
   (a 1.25rem serif lede, a button, a 0.875rem note), so equal margins
   read as unequal gaps and the button looked stuck to the note beneath
   it. Two nested flex columns instead, each with the gap its own pair
   needs. */
.guide-cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
  margin-top: var(--space-8);
  padding: var(--space-6) var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  text-align: center;
}
.guide-cta .lede { margin: 0; max-width: 34ch; }
.guide-cta-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}
.guide-cta-note { margin: 0; font-size: 0.875rem; color: var(--color-muted); }

/* Tier coverage counter, beside the "Group size tiers" heading. Muted
   while there is work left and green once every group size has a price:
   the one moment in this editor where a guide is actually finished, and
   nothing else on the step says so. */
.tier-coverage {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  white-space: nowrap;
}
.tier-coverage.is-complete { color: var(--color-success); font-weight: 600; }

/* Onboarding screencast card. Copy beside the player on a wide screen,
   above it on a narrow one — a 16:9 video and a paragraph side by side
   below ~700px leaves both too narrow to be worth having. */
.walkthrough-card {
  display: grid;
  gap: var(--space-5);
  align-items: center;
  margin-bottom: var(--space-6);
}
@media (min-width: 800px) {
  .walkthrough-card { grid-template-columns: 1fr 1.2fr; }
}
.walkthrough-video {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm);
  background: var(--color-ink);
  display: block;
}

/* The quiet variant: one row until somebody asks for more. No side-by-side
   split, because there is no copy to sit beside the player — the whole
   point is that it takes a line on a page that is not about it. */
.walkthrough-quiet {
  display: block;
  margin-top: var(--space-8);
  margin-bottom: 0;
}
.walkthrough-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.9375rem;
  color: var(--color-body);
  text-align: left;
  cursor: pointer;
}
.walkthrough-toggle:hover { color: var(--color-primary); }
.walkthrough-chevron {
  transition: transform 0.15s ease;
  color: var(--color-muted);
  font-size: 1.25rem;
  line-height: 1;
}
.walkthrough-chevron.is-open { transform: rotate(90deg); }
.walkthrough-quiet .walkthrough-player { margin-top: var(--space-4); }
.walkthrough-quiet .walkthrough-video { max-width: 720px; }

/* Chapters. A numbered list because the recording is a sequence and the
   order carries meaning: this is the path through the product, and a
   guide reading it learns the shape of the job before pressing play. */
.walkthrough-chapters {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: grid;
  gap: 1px;
  max-width: 720px;
}

/* Side by side once there is room for it. Thirteen chapters stacked under
   a 16:9 player is a card twice the height of the screen, and the list is
   what makes the recording usable — it should be readable while the video
   is playing rather than after scrolling past it.

   The list scrolls inside its own column rather than setting the card's
   height: it is the taller of the two and would otherwise leave the video
   floating in white space. */
@media (min-width: 900px) {
  .walkthrough-player {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(220px, 1fr);
    gap: var(--space-5);
    align-items: start;
  }
  .walkthrough-player .walkthrough-video,
  .walkthrough-quiet .walkthrough-player .walkthrough-video { max-width: none; }
  .walkthrough-chapters {
    margin-top: 0;
    max-width: none;
    max-height: min(60vh, 420px);
    overflow-y: auto;
    /* Scrollable but not obviously so until it is: the gap tells the eye
       the list continues, and a permanent scrollbar in a card reads as a
       mistake. */
    scrollbar-width: thin;
  }
}
.walkthrough-chapters button {
  display: flex;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  font: inherit;
  font-size: 0.875rem;
  color: var(--color-body);
  text-align: left;
  cursor: pointer;
}
.walkthrough-chapters button:hover { background: var(--color-sand); color: var(--color-primary); }
.walkthrough-chapter-time {
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  min-width: 3.25rem;
}

/* Founding story ----------------------------------------------------------
   The one page on the site that is a piece of writing rather than an
   interface. Everything here exists to make two thousand words readable:
   a measure that does not run past the eye's comfortable line, generous
   air between sections, and photographs used as pauses rather than
   decoration. See templates/cms/flat_content_page.html.
   ------------------------------------------------------------------------- */
.story-hero {
  padding: var(--space-10) 0 var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.story-hero .eyebrow { display: block; margin-bottom: var(--space-3); }
.story-hero h1 {
  max-width: 16ch;
  font-size: clamp(2.25rem, 5.5vw, 3.5rem);
  line-height: 1.05;
  margin: 0;
  text-wrap: balance;
}

/* 21:9 rather than the 4:3 the editorial-split images use. A hero that
   tall pushes the first line of the letter off a laptop screen, and the
   first line is the one that decides whether the rest gets read. */
.story-hero-image {
  width: 100%;
  aspect-ratio: 21 / 9;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

/* 62 characters. Wider than this and the eye loses the start of the next
   line on the way back; the site's other prose page (.container at 720px)
   is close to the same thing arrived at by hand. */
.story-measure {
  max-width: 62ch;
}

.story-band { padding: var(--space-8) 0; }
/* The opening band sits directly under the hero photo, which is already a
   large gap — a second one reads as a missing paragraph. */
.story-hero + .story-band { padding-top: var(--space-6); }

.story-prose { font-size: 1.0625rem; }
.story-prose p { margin: 0 0 var(--space-4); }
.story-prose p:last-child { margin-bottom: 0; }
.story-prose ul,
.story-prose ol { margin: 0 0 var(--space-4); padding-left: var(--space-5); }
.story-prose li { margin-bottom: var(--space-2); }

/* A paragraph that is nothing but bold text is the author making a point
   land on its own line — the letter does this four times, always at the
   turn of an argument. Rendering it as a pull quote is reading the
   structure that is already in the writing rather than adding a widget
   for it.

   :only-child is doing the work: bold used inside a sentence is emphasis
   and must stay inline. Only a <strong> that is the entire paragraph gets
   promoted. */
.story-prose p > strong:only-child {
  display: block;
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: 1.5rem;
  line-height: 1.35;
  color: var(--color-primary);
  border-left: 3px solid var(--color-accent);
  padding-left: var(--space-5);
  margin: var(--space-6) 0;
}

.story-photo {
  margin: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-md);
}
.story-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

@media (max-width: 700px) {
  .story-hero { padding-top: var(--space-6); }
  /* 21:9 on a phone is a letterbox strip a hundred pixels tall. */
  .story-hero-image { aspect-ratio: 3 / 2; }
  .story-photo img { aspect-ratio: 4 / 3; }
  .story-prose p > strong:only-child { font-size: 1.25rem; padding-left: var(--space-4); }
}

/* The private-tour line on a tour page. Sits between the facts row and
   the price, and is deliberately not an alert: it is a property of what
   is being sold, not a warning about it. */
.tour-private-note {
  /* Vertical margins with the header rhythm at the foot of this file. */
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--color-primary-light);
  background: var(--color-sand);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: 0.9375rem;
  color: var(--color-body);
}

/* Moving between wizard steps -------------------------------------------
   No animation. A slide was tried and abandoned: it drew attention to the
   navigation rather than to the step, and the real complaint was never
   that the change was abrupt — it was that the page jumped to the top and
   left the guide scrolling back to the part they were editing.

   What matters is where the viewport lands, which is the `show:` modifier
   on the swap in wizard_base.html. This rule is the other half of it: the
   rail must not end up flush against the top edge, or under the sticky
   header on the way past. */
#wizard-rail-anchor {
  scroll-margin-top: var(--space-4);
}

/* An approximate price beside a real one. Muted and lighter than the
   figure it follows, because it is a reading aid: the number that will
   be charged is the one in the guide's own currency, and this must never
   compete with it for attention. */
.price-approx {
  color: var(--color-muted);
  font-weight: 400;
  white-space: nowrap;
}

/* The same idea under a total: quieter than the total, louder than the
   footnote below it, and it names the currency that will actually be
   charged rather than leaving the reader to infer it. */
.price-approx-note {
  margin: var(--space-2) 0 0;
  font-size: 0.8125rem;
  color: var(--color-muted);
}

/* Help centre ------------------------------------------------------------
   A list of questions, set as a list of questions: no cards, no icons, no
   accordion. Somebody arriving here is scanning for their own sentence,
   and every ornament between the titles is one more thing between them
   and it. */
.help-group { margin-top: var(--space-8); }
.help-group h2 { font-size: 1.125rem; margin-bottom: var(--space-3); }
.help-list { list-style: none; margin: 0; padding: 0; }
.help-list li + li { border-top: 1px solid var(--color-border); }
.help-list a {
  display: block;
  padding: var(--space-3) 0;
  color: var(--color-ink);
  text-decoration: none;
}
.help-list a:hover, .help-list a:focus-visible { color: var(--color-primary); text-decoration: underline; }
.help-back { margin: 0 0 var(--space-4); font-size: 0.875rem; }
.help-back a { color: var(--color-muted); text-decoration: none; }
.help-back a:hover { color: var(--color-primary); }
.help-nothing-here {
  margin-top: var(--space-8);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
  font-size: 0.9375rem;
  color: var(--color-muted);
}

/* The two things the availability step now says out loud: that dates are
   held across every tour, and that time off is set once. Quiet, because
   it is reassurance rather than an instruction — a guide who already knew
   should be able to skim past it. */
.availability-assurance {
  margin-bottom: var(--space-6);
  padding: var(--space-4) var(--space-5);
  background: var(--color-sand);
  border-radius: var(--radius-md);
  font-size: 0.9375rem;
  color: var(--color-body);
}
.availability-assurance p { margin: 0; }
.availability-assurance p + p { margin-top: var(--space-2); color: var(--color-muted); }

/* Tour page layout and rhythm -------------------------------------------
   Was three inline `style` attributes and a <style> block at the foot of
   tour_detail.html carrying an !important to beat them. Nothing here is
   new behaviour; it is the same layout said once, where it can be read.

   The rhythm is the part worth explaining. The header had seven gaps in
   two sizes -- 8, 8, 16, 8, 8, 12, 16 -- which is close enough to uniform
   that nothing grouped, and the whole block read as one flat list. More
   space everywhere would only have made a taller list.

   So: two sizes, chosen by relationship. Tight inside a group, generous
   between them. The badges sit close to the title because they label it;
   the meta line and the languages sit close to each other because they
   are both facts; and the gaps that separate one idea from the next are
   four times bigger than the gaps inside one. That contrast is what does
   the work — the page reads as five things rather than eleven.

   Stated as a flow rule with exceptions rather than as a margin on each
   piece, so the exceptions are visible as exceptions and the default is
   in one place. */
.tour-page {
  display: grid;
  gap: var(--space-8);
  grid-template-columns: 1fr;
}
.tour-layout {
  display: grid;
  gap: var(--space-8);
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .tour-layout {
    grid-template-columns: 2fr 1fr;
    align-items: start;
  }
}

/* The default: one section to the next. */
.tour-main > * { margin-bottom: 0; }
.tour-main > * + * { margin-top: var(--space-6); }
/* A block's own last paragraph or list must not add to the gap the rule
   above already set — the description would otherwise sit 48px from what
   follows it while everything else sits 32. */
.tour-main > * > p:last-child,
.tour-main > * > ul:last-child { margin-bottom: 0; }

/* The exceptions, in the order they appear. Each one is a pair that
   belongs together. */
.tour-main > .breadcrumbs + .tour-badges { margin-top: var(--space-5); }
/* And when there is no breadcrumb above them — the embedded page, where
   navigation on our site means nothing — the badges must not end up
   against whatever is: they were sitting on the gallery. */
.tour-main > .tour-badges:first-child { margin-top: var(--space-2); }
.tour-main > .tour-badges + h1 { margin-top: var(--space-2); }
.tour-main > h1 + .tour-meta { margin-top: var(--space-3); }
.tour-main > .tour-meta + .tour-languages { margin-top: var(--space-2); }
/* The private-tour note is a tinted box. It needs clearance on both
   sides or it looks wedged between the facts and the guide, which is
   exactly how it looked when it carried 12px above and 8 below. */
.tour-main > .tour-private-note { margin-top: var(--space-5); }

.tour-guide-name { font-weight: 600; }
/* Pushed to the far edge of the guide row: it answers the guide, not the
   rating beside it. */
.tour-guide-ask { margin-left: auto; }

/* Embeddable widgets: the page where a guide builds their snippet -------
   One column for everything, so the form, the list and the notices under
   them share a left edge and an outer edge. The alert used to run to the
   full container width while the section above it stopped at 720, which
   read as two pages stacked. */
.widget-page { max-width: 760px; }
.widget-page > * + * { margin-top: var(--space-6); }
.widget-page > .alert { margin-bottom: 0; }
.widget-sites { margin-top: var(--space-6); }
.widget-site-form { margin-top: var(--space-4); }
/* The input and its button on one line, the label above and the help
   below — both spanning the row rather than only the input. Aligning the
   button to the bottom of a label-input-help stack is what hung it a
   line lower than the box it belongs to. */
.widget-site-row {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--space-3);
  margin-top: var(--space-1);
}
.widget-site-row input {
  flex: 1 1 260px;
  min-width: 0;
  margin: 0;
}
.widget-site-row .btn { flex: none; }
.widget-site-form .field-help { margin-top: var(--space-2); }
.widget-builder { margin-top: var(--space-8); }
.widget-builder-grid {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
  margin-top: var(--space-4);
}
@media (min-width: 900px) {
  .widget-builder-grid { grid-template-columns: 1fr 1fr; align-items: start; }
}
/* A neutral stage rather than the page background: a widget a guide is
   about to paste onto their own site should be judged against something
   that is not ours, and a card on white looks different on grey. */
.widget-preview-stage {
  padding: var(--space-5);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: repeating-conic-gradient(var(--color-sand) 0% 25%, transparent 0% 50%) 0 0 / 16px 16px;
  min-height: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.widget-preview-stage > * { max-width: 340px; width: 100%; }
/* The whole-tour widget is a page, not a tile: previewing it at card
   width would show a column nobody will ever see it in. */
.widget-preview-stage.is-page { align-items: flex-start; max-height: 520px; overflow-y: auto; }
.widget-preview-stage.is-page > * { max-width: none; }
.widget-snippet {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8125rem;
  width: 100%;
  white-space: pre;
  overflow-x: auto;
}

/* What the guide asked, and what the traveller answered, on a booking. */
.booking-answers {
  margin: 0 0 var(--space-4);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-1) var(--space-4);
}
.booking-answers dt { font-weight: 600; }
.booking-answers dd { margin: 0; }
@media (max-width: 560px) {
  .booking-answers { grid-template-columns: 1fr; }
  .booking-answers dd { margin-bottom: var(--space-2); }
}

/* Booking questions, on the details step of the wizard. */
.question-row {
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.question-form { margin-top: var(--space-4); }
/* The checkbox sits beside a select, so it lines up on the input's
   baseline rather than the label's — the field above it is a head taller
   than a bare checkbox. */
.question-required { align-self: end; padding-bottom: 0.6rem; }

/* Help centre answers ---------------------------------------------------
   <details>, styled as a row. The marker goes because Safari and Firefox
   draw it differently and neither matches the chevron; the chevron is
   ours and rotates with the state. */
.help-item { border-bottom: 1px solid var(--color-border); }
.help-question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  cursor: pointer;
  font-weight: 600;
  color: var(--color-ink);
  list-style: none;
}
.help-question::-webkit-details-marker { display: none; }
.help-question:hover { color: var(--color-primary); }
/* The row is the click target, so it needs the focus ring rather than
   some inner element that happens to be focusable. */
.help-item:focus-within .help-question { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.help-question svg {
  width: 1rem;
  height: 1rem;
  flex: none;
  color: var(--color-muted);
  transition: transform 0.15s ease;
}
.help-item[open] .help-question svg { transform: rotate(180deg); }
.help-answer { padding: 0 0 var(--space-5); max-width: 62ch; }
.help-answer > :first-child { margin-top: 0; }
.help-permalink { margin: var(--space-4) 0 0; font-size: 0.8125rem; }
.help-permalink a { color: var(--color-muted); }
.help-permalink a:hover { color: var(--color-primary); }
@media (prefers-reduced-motion: reduce) {
  .help-question svg { transition: none; }
}

/* Guest checkout: the two fields somebody fills in instead of making an
   account. Grouped and set slightly apart from the steppers above, so it
   reads as "who you are" rather than as two more booking options. */
.guest-checkout {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}
.guest-checkout .field:last-child { margin-bottom: 0; }
.guest-checkout-signin { margin: var(--space-3) 0 0; text-align: center; }

/* The tour page as it appears inside a frame on a guide's own website.
   Everything else on that page is our ordinary markup and our ordinary
   stylesheet — see embeds/tour_page.html. Only what surrounds it differs. */
/* The frame's own column. .container caps it at the site width; this
   caps it again at the width our tour page actually gives its prose,
   because there is no booking panel beside it here to take the other
   third — without it the same description sets a third wider inside the
   frame than it does on our own page. */
/* .container's own width, exactly as on our pages — the frame is as wide
   as the host makes it, and the layout inside must not follow. The
   two-column grid below does the rest: .tour-layout gives the tour two
   thirds and the booking panel one, which is where the panel belongs and
   why it no longer sits under everything a traveller came to read. */
.embed-page {
  padding-top: var(--space-5);
  padding-bottom: var(--space-8);
}
.embed-page .tour-layout { margin-top: var(--space-6); }
.embed-booking { position: sticky; top: var(--space-4); }
/* The price breakdown ends in a box; without this the button sits on its
   edge, which reads as part of the table rather than as the thing to do
   next. */
.embed-book { margin-top: var(--space-4); }
/* Refused until there is a date to hand over. Not [disabled], because
   this is an anchor: without JavaScript it stays a working link to our
   own page, which asks for the date itself. */
.embed-book.is-disabled { opacity: 0.55; }
.embed-book-hint { margin: var(--space-2) 0 0; }
.embed-book-note { margin: var(--space-2) 0 0; }
/* Ours, quietly. It is somebody else's page and the badge is a courtesy
   to the reader — whose booking is this — not a banner. */
.embed-powered { margin: var(--space-8) 0 0; text-align: center; }
.embed-powered a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
  color: var(--color-muted);
  font-size: 0.8125rem;
  opacity: 0.85;
}
.embed-powered a:hover { opacity: 1; }
.embed-powered .logo-word { font-size: 1rem; }
.embed-dates { margin-top: var(--space-6); }
.embed-date-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.embed-date {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--color-ink);
  font-size: 0.875rem;
  min-width: 6rem;
}
.embed-date:hover { border-color: var(--color-primary); }
.embed-date-day { font-weight: 600; }
.embed-date-time { color: var(--color-muted); }


/* Hints: the corner tip, and a hint opened against its control ----------
   Both are apps.core.hints. The tip suggests; the tooltip points. */
.hint-tip {
  position: fixed;
  right: var(--space-5);
  bottom: var(--space-5);
  z-index: 120;
  width: min(22rem, calc(100vw - var(--space-6)));
  padding: var(--space-4) var(--space-5) var(--space-4);
  background: var(--color-paper);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.hint-tip-eyebrow {
  margin: 0;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.hint-tip-title { margin: var(--space-1) 0 var(--space-2); font-size: 1rem; font-weight: 600; }
.hint-tip-body { margin: 0 0 var(--space-3); font-size: 0.875rem; color: var(--color-body); }
.hint-tip-actions { display: flex; align-items: center; gap: var(--space-2); }
.hint-tip-actions form { margin: 0; }
/* Findable, not shouted: the way out of a helpful feature should be in
   the feature rather than three pages away in settings. */
.hint-tip-off { margin: var(--space-3) 0 0; }
.hint-tip-off button {
  background: none;
  border: 0;
  padding: 0;
  font-size: 0.75rem;
  color: var(--color-muted);
  text-decoration: underline;
  cursor: pointer;
}
.hint-tip-off button:hover { color: var(--color-ink); }
@media (max-width: 640px) {
  .hint-tip { left: var(--space-4); right: var(--space-4); bottom: var(--space-4); width: auto; }
}

/* The tooltip. Absolutely positioned by static/js/hints.js, which also
   hides it when its anchor is not on the page. */
.hint-pop {
  position: absolute;
  z-index: 130;
  width: min(20rem, calc(100vw - var(--space-6)));
}
.hint-pop-inner {
  padding: var(--space-4);
  background: var(--color-paper);
  border: 1px solid var(--color-primary-light);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.hint-pop-step { margin: 0 0 var(--space-1); font-size: 0.75rem; color: var(--color-muted); }
.hint-pop-title { margin: 0 0 var(--space-2); font-size: 1rem; font-weight: 600; }
.hint-pop-body { margin: 0 0 var(--space-3); font-size: 0.875rem; }
.hint-pop-more { margin: 0 0 var(--space-3); font-size: 0.8125rem; }
.hint-pop-actions { display: flex; align-items: center; gap: var(--space-2); }
.hint-pop-actions form { margin: 0; }
/* A sheet on a narrow screen: a tooltip pinned to a control on a 360px
   display covers the control it is pointing at, which makes it a modal
   whatever we call it. */
.hint-pop.is-sheet {
  position: fixed;
  left: var(--space-3);
  right: var(--space-3);
  bottom: var(--space-3);
  width: auto;
}
/* The control being talked about, so the eye has somewhere to go. */
.is-hinted {
  outline: 2px solid var(--color-primary-light);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

/* The walkthrough offer on the dashboard. Beside the screencast, not
   instead of it: the recording is better at why and the tour is for
   where, and nobody should be asked to sit through both. */
.walkthrough-offer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-6);
}
.walkthrough-offer h2 { margin: 0 0 var(--space-1); }
.walkthrough-offer p { margin: 0; max-width: 52ch; }
.walkthrough-offer form { margin: 0; }

/* "Help with this page" on a wizard step. Closed until asked for: a guide
   on step five is working, not reading. */
.step-help { position: fixed; right: var(--space-5); bottom: var(--space-5); z-index: 115; }
.step-help-bubble {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.5rem 0.9rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border);
  background: var(--color-paper);
  box-shadow: var(--shadow-lg);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-primary);
  cursor: pointer;
}
.step-help-bubble:hover { background: var(--color-sand); }
.step-help-bubble svg { width: 1rem; height: 1rem; }
.step-help-menu {
  position: absolute;
  right: 0;
  bottom: calc(100% + var(--space-2));
  width: min(20rem, calc(100vw - var(--space-6)));
  background: var(--color-paper);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.step-help-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-3) var(--space-4);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--color-border);
  cursor: pointer;
  font-family: var(--font-sans);
}
.step-help-option:last-child { border-bottom: 0; }
.step-help-option:hover { background: var(--color-sand); }
.step-help-option strong { display: block; color: var(--color-ink); font-size: 0.9375rem; }
/* The clip, over the page rather than in it: the step underneath is a
   form somebody is in the middle of, and pushing it down to make room
   would lose their place. */
.step-help-video {
  position: fixed;
  inset: 0;
  z-index: 140;
  background: rgba(16, 22, 20, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
}
.step-help-video-inner { position: relative; width: min(900px, 100%); }
.step-help-video-inner video { width: 100%; border-radius: var(--radius-md); background: #000; }
.step-help-video-inner p { color: var(--color-paper); margin: var(--space-2) 0 0; }
.step-help-video-close {
  position: absolute;
  top: calc(-1 * var(--space-6));
  right: 0;
  background: none;
  border: 0;
  color: var(--color-paper);
  font-size: 1.75rem;
  line-height: 1;
  cursor: pointer;
}
@media (max-width: 640px) {
  .step-help { right: var(--space-3); bottom: var(--space-3); }
}

/* The list of mail no switch can stop, under the email preferences. Set
   apart rather than styled down: it is the honest half of the page. */
.always-sent {
  margin-top: var(--space-8);
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-sand);
}
.always-sent h2 { margin: 0 0 var(--space-2); }
.always-sent ul { margin: var(--space-3) 0; }
.always-sent li { margin-bottom: var(--space-1); }

/* The platform-cost table when a content page asks for it. Same width as
   the prose above it, so the page reads as one column rather than as a
   document with a widget in it. */
.flat-fee-breakdown { max-width: 640px; margin-top: var(--space-8); }
.flat-fee-breakdown h2 { margin-bottom: var(--space-4); }

/* The partner wall on the call-for-partners page.

   A list rather than a bare grid of images, because each row carries a
   name, a region and a line about what they do — the logo alone is
   decoration, and a wall of decoration is what most partner pages are.

   The logo sits in a fixed-height box and is contained inside it, never
   cropped: a partner's mark is the one image on this site where filling
   the box produces the wrong image rather than a tighter one. The box is
   white regardless of the sand band behind the section, since a logo
   drawn for a white background loses its edges on anything else. */
.partner-wall {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--space-5);
}
.partner-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--color-paper);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
/* The mark, and the card's heading: a fixed-height box so a row of cards
   lines its text up whether or not every partner sent a logo. The
   centring goes on this box rather than on the image, since object-fit
   alone cannot place a short wide logo inside a taller slot. */
.partner-name {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 72px;
  margin: 0;
  font-size: 1.0625rem;
  text-align: center;
}
.partner-name a { display: flex; align-items: center; justify-content: center; height: 100%; max-width: 100%; }
.partner-logo { max-height: 72px; max-width: 100%; width: auto; object-fit: contain; }
/* No logo supplied. The name set in the display face beats a grey
   placeholder rectangle, which reads as a broken image. */
.partner-wordmark {
  font-family: var(--font-serif);
  font-size: 1.25rem;
  line-height: 1.25;
  color: var(--color-primary);
}
.partner-meta { display: flex; flex-direction: column; gap: var(--space-2); }
.partner-region { margin: 0; font-size: 0.8125rem; color: var(--color-muted); }
.partner-meta .badge { align-self: flex-start; }


/* The couple of lines about a place, above its search results.

   Quiet on purpose: no card, no border box, no background. It is context
   for the results underneath it, and anything with edges would read as a
   second result competing with the tours.

   Three lines, clamped in CSS rather than by cutting the text to a
   character count, because three lines is three lines on a phone and on
   a desktop while a character count is neither. The view still caps what
   it sends, so the clamp is hiding a sentence at most, not a page. */
.destination-note {
  max-width: 62ch;
  margin: calc(var(--space-5) * -1) 0 var(--space-6);
}
.destination-note-text {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0 0 var(--space-2);
  color: var(--color-muted);
}
.destination-note-link {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-primary);
}
.destination-note-link:hover { text-decoration: underline; }
