/* Booking content styling. The page chrome (fonts, preloader, bar/menu,
   brand lockup, footer, guide chat, grain, ::selection) comes from the site
   master shell — index.php builds on site/site_shared/master.php and links
   site/css/master.css before this file. Only booking-app rules live here. */

:root {
  --booking-available: var(--sbv-success);
  --booking-unavailable: var(--sbv-danger);
  --booking-selected: var(--sbv-brand-primary);
  /* warm-ink palette, aligned with the site's 5.1 chrome (master.css tokens) */
  --ink:        #0f0e0b;
  --ink-2:      #161410;
  --cream:      #f4efe3;
  --line-dark:  rgba(244, 239, 227, 0.14);
  --booking-panel: var(--ink-2);
  --booking-panel-soft: #1d1a14;
  --booking-border: var(--line-dark);
  /* Panel elevation. The panel FILL stays warm-ink: lifting it to a lighter
     flat tone is what turns this palette into generic dark grey. Separation
     comes from a lit top edge + short falloff instead. Measured before this:
     panel #161410 on page #0f0e0b = 1.05:1, ΔL* 2.4 — invisible, the hairline
     border was carrying the whole card. Keep these three in step with
     belevingen.css, which mirrors them so the funnel reads as one system. */
  --booking-border-panel: rgba(244, 239, 227, 0.2);
  --booking-panel-lit:  rgba(244, 239, 227, 0.055); /* top-of-panel light */
  --booking-panel-edge: rgba(244, 239, 227, 0.09);  /* 1px lit hairline */
  --booking-border-strong: rgba(255, 217, 0, 0.45);
  --booking-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  --booking-neutral-dark: #100f0c;
  --booking-neutral-panel: #1d1a14;
  --booking-font: 'AvenirNextCn', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
}

/* ── HUISLOOK IN WARME INKT ───────────────────────────────────────────────
   branding/surfaces.css levert het materiaal: glas, ambient licht, hoeken,
   hoogtes en gloed. De WAARDEN daarin zijn neutraalgrijs (#141414, rgba(40,40,
   40,…)), en die horen bij het beheer en bij wbs/. Hier niet: deze funnel deelt
   zijn warme inkt met de marketingsite (site/site_shared/master.css), en de
   opmerking hieronder waarschuwt er zelf voor — een lichter vlak tone maakt van
   dit palet "generiek donkergrijs".

   Daarom nemen we de STRUCTUUR over en houden we de KLEUR. Dat is precies hoe
   surfaces.css bedoeld is ("apps opt in one declaration at a time"); het bestand
   levert zelf ook al een lichte variant van dezelfde tokens.

   Gespiegeld in belevingen.css — de vier detailpagina's laden dit bestand niet.
   Houd ze in stap, net als de drie paneelvariabelen hieronder. */
body.booking-page {
  --sbv-surface-0: var(--ink);                        /* #0f0e0b, paginacanvas */
  --sbv-surface-1: rgba(29, 26, 20, 0.62);            /* panelen, zones        */
  --sbv-surface-2: rgba(38, 34, 26, 0.66);            /* kaarten, verhoogd     */
  --sbv-surface-3: rgba(22, 20, 16, 0.62);            /* rijen, verdiept       */
  --sbv-surface-strong: rgba(15, 14, 11, 0.82);       /* kop, modal            */
  --sbv-surface: var(--sbv-surface-1);
  --sbv-surface-edge: var(--booking-panel-edge);
  --sbv-surface-1-solid: #1d1a14;
  --sbv-surface-2-solid: #262218;
  --sbv-surface-3-solid: #161410;

  /* Ambient in warme tinten: goud waar het huislook goud heeft, en amber/koper
     waar het blauw en groen heeft — blauw over warme inkt leest als vuil. */
  --sbv-ambient:
    radial-gradient(1000px 560px at 12% -8%, rgba(255, 217, 0, 0.10), transparent 62%),
    radial-gradient(820px 460px at 96% 4%, rgba(184, 115, 51, 0.09), transparent 60%),
    radial-gradient(900px 620px at 55% 108%, rgba(212, 165, 116, 0.06), transparent 62%);
}

/* Ambient licht achter alles — wat het glas laat wérken. */
body.booking-page::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--sbv-ambient);
}

/* Warm the shared sbv text tokens for this page: colors.css dark mode uses cool
   neutral greys (#e5e5e5/#cccccc/#999999) that clash with the site's warm-ink
   chrome — the site renders secondary copy in cream-alpha (master.css). */
body.booking-page {
  --sbv-text-primary: var(--cream);
  --sbv-text-secondary: rgba(244, 239, 227, 0.75);
  --sbv-text-tertiary: rgba(244, 239, 227, 0.55);
  --sbv-text-muted: rgba(244, 239, 227, 0.4);
  /* Site hover vocabulary never hue-shifts the gold (colors.css dark mode uses
     Tailwind #eab308); hover affordance comes from lift/glow, not a color swap. */
  --sbv-brand-primary-hover: var(--sbv-brand-primary);
}

/* Brand-lockup wordmark must render in Bungee (like site/), not fall back to
   the body sans. Cause: master.css sets `.wm { font-family: var(--brand) }`
   expecting --brand = the Bungee stack, but branding/colors.css redefines
   --brand to the yellow COLOR (var(--sbv-brand-primary)) under .sbv-theme-*,
   which this page carries — so var(--brand) becomes an invalid font-family and
   .wm inherits the body sans. Pin it back to Bungee. (Same fix as
   belevingen.css's body.booking-page .brand-lockup .wm.) */
body.booking-page .brand-lockup .wm {
  font-family: 'Bungee', 'AvenirNextCn', system-ui, sans-serif;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background:
    linear-gradient(180deg, rgba(255, 217, 0, 0.06) 0, rgba(15, 14, 11, 0) 18rem),
    var(--ink);
  color: var(--cream);
  font-family: var(--booking-font);
  line-height: 1.55;
  font-size: var(--fs-body-sm); /* 18px — site's card/step/tile size (master.css) */
}

button,
input,
select,
textarea { font: inherit; }

.container {
  width: min(1180px, 100%);
  margin: 0 auto;
  /* top padding clears the master shell's absolute brand lockup + menu bar */
  padding: clamp(92px, 11vh, 118px) clamp(1rem, 3vw, 2rem) clamp(1rem, 3vw, 2rem);
}

.main {
  display: grid;
  gap: 1rem;
}

h1,
h2,
p { margin-top: 0; }

h1 {
  margin-bottom: 0.75rem;
  /* weight 400: AvenirNextCn ships one weight; the site's 5.1 display headings
     are regular — browser-default bold would render a synthesized fake bold. */
  font-weight: 400;
  font-size: var(--fs-display); /* site type scale (master.css) — was a local 6rem clamp */
  line-height: 0.9;
  max-width: 9ch;
  color: var(--cream);
  text-transform: uppercase;
  letter-spacing: -0.02em;
}

.hero-title {
  display: grid;
  gap: 0.12em;
}

.hero-title-prefix,
.hero-title-dynamic,
.hero-title-current {
  display: block;
}

.hero-title-dynamic {
  position: relative;
  min-height: 1em;
  color: var(--sbv-brand-primary);
}

.hero-title-current {
  opacity: 1;
  min-width: 4ch;
}

h2 {
  margin-bottom: 0;
  font-weight: 400;
  font-size: var(--fs-h2); /* site type scale — was a local 2rem clamp */
  line-height: 1.12;
  color: var(--cream);
  text-transform: uppercase;
  letter-spacing: -0.01em;
}

.section-kicker {
  margin-bottom: 0.5rem;
  color: var(--sbv-brand-primary);
  font-size: var(--fs-label); /* site kicker size — was 0.82rem */
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
}

.hero-panel,
.card {
  border: 1px solid var(--booking-border-panel);
  border-radius: var(--sbv-radius-lg);
  /* Lit from above — see the token block for why the fill isn't lightened.
     The falloff is a fixed 200px, not a percentage: light behaves the same
     whether the panel is 300px or 1200px tall, and a percentage would make
     short cards read brighter than tall ones.

     De vulling is nu glas (--sbv-surface-1, in warme inkt): de lichte gradiënt
     erboven blijft doen wat hij deed, en het ambient licht erachter komt er
     doorheen. Zie branding/surfaces.css. */
  background:
    linear-gradient(180deg, var(--booking-panel-lit), rgba(244, 239, 227, 0) 200px),
    var(--sbv-surface-1);
  backdrop-filter: var(--sbv-blur);
  -webkit-backdrop-filter: var(--sbv-blur);
  box-shadow:
    var(--sbv-surface-sheen, inset 0 1px 0 var(--booking-panel-edge)),
    var(--sbv-elev-3);
}

.hero-panel {
  min-height: min(680px, calc(100vh - 110px));
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(360px, 0.98fr);
  align-items: stretch;
  overflow: hidden;
}

.hero-content {
  padding: clamp(1.35rem, 5vw, 4.25rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
  isolation: isolate;
}

/* The ambient ember layer (.ambient-embers) deliberately has NO rules here any
   more. That layer is created on every public page by site/js/preloader.js and
   styles itself inline there; the same four declarations sat here a second time.
   The reason for each of them lives with that code. */

.hero-copy {
  max-width: 46rem;
  margin-bottom: 1.35rem;
  color: var(--sbv-text-secondary);
  font-size: var(--fs-body); /* site running-text size — was a local 1.2rem clamp */
}

.hero-visual {
  position: relative;
  min-height: 460px;
  background: var(--booking-neutral-dark);
  overflow: hidden;
}

/* The seam that blends the photo into the copy panel. It MUST stay above every
   photo layer, including .is-entering (z-index 2): at z-index 1 the incoming
   layer painted over it, so the dark left edge washed out across the 1800ms
   crossfade and snapped back the moment the class was removed — read as the
   divider flickering/rebuilding on every rotation. */
.hero-visual::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(15, 14, 11, 0.92), rgba(40, 40, 40, 0.12) 36%, rgba(15, 14, 11, 0.12));
  pointer-events: none;
  z-index: 3;
}

.hero-photo {
  position: absolute;
  object-fit: cover;
  display: block;
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.42);
}

.hero-photo-main {
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  will-change: opacity;
  transition: opacity 1800ms ease-in-out;
}

.hero-photo-layer {
  z-index: 0;
  opacity: 0;
}

.hero-photo-layer.is-active {
  z-index: 1;
  opacity: 1;
}

.hero-photo-layer.is-entering {
  z-index: 2;
}

.hero-photo-layer.is-leaving {
  z-index: 1;
}

.booking-highlights {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.7rem;
  max-width: 30rem;
}

.booking-highlight {
  min-height: 54px;
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  padding: 0.85rem;
  border: 1px solid var(--booking-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--sbv-text-secondary);
}

.booking-highlight .sbv-icon {
  flex: 0 0 auto;
  margin-top: 0.1rem;
}

/* The price pill quotes the experience the headline is showing, so its text is
   swapped on every hero rotation. Fade rather than hard-cut, opacity only so the
   pill never reflows. Duration is mirrored by HERO_PRICE_FADE_MS in app.js —
   change both together. The copy is kept short enough to hold one line at the
   hero's width; only a phone genuinely runs out of room, hence pretty. */
.hero-price-highlight {
  transition: opacity 220ms ease;
  text-wrap: pretty;
}

/* Phone: the three hero pills are LABELS, so they hold one line rather than
   breaking mid-phrase (.github/copilot-instructions.md, wrap rule). At 18px the
   longest of them ("Vrijblijvende boeking — je betaalt nu niets", 263px) needed
   263px against the 260px a 390px-wide phone leaves inside the pill, and at
   360px all three overflowed. The copy is admin-editable, so it cannot be
   shortened from here; the box gives the room back instead — tighter gutters
   plus one step down in size buys 40px, measured to hold one line from 360px up.
   Below that a phone genuinely runs out of room, which is what text-wrap
   handles. */
@media (max-width: 430px) {
  .booking-highlight {
    gap: 0.5rem;
    padding: 0.75rem 0.65rem;
    font-size: 1rem;
    text-wrap: pretty;
  }
}

.card { padding: clamp(1rem, 2.5vw, 1.5rem); }

.section-heading {
  display: grid;
  gap: 0.35rem;
  margin-bottom: 1rem;
}

.section-heading p:last-child {
  margin-bottom: 0;
  color: var(--sbv-text-secondary);
}

.section-heading.compact { margin-bottom: 0.75rem; }

/* Heading receives programmatic focus only (step hand-off); no visible ring needed. */
#slot-heading { outline: none; }

.help-text {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0.85rem 0 0;
  color: var(--sbv-text-tertiary);
  font-size: 0.95rem;
}

.help-text .sbv-icon {
  flex: 0 0 auto;
  margin-top: 0.15rem;
}

.experience-grid-wrap {
  position: relative; /* anchors .combo-badge over the card seam */
}

.experience-grid {
  display: grid;
  /* auto-fit: 4 kaarten passen naast elkaar op desktop en wrappen netjes
     op smallere schermen (mobiel valt terug op 1 kolom via de media query) */
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 0.85rem;
}

/* ── combo affordance: workshop + plancha ──────────────────────────────
   Desktop: a round gold "+" pinned over the seam between card 1 (workshop)
   and card 2 (plancha), with a tooltip on hover/focus.
   Mobile (<=900px, cards stack): becomes a full-width chip under the grid,
   label visible and tooltip inline — hover doesn't exist on touch.
   Card order is pinned by DEFAULT_EXPERIENCES (workshop, plancha, …), so
   the seam is always after the first column. */
.combo-badge {
  position: absolute;
  /* Seam after column 1. auto-fit(minmax(230px,1fr)) gives 4 cols >=1024px
     and 3 cols from 821-1023px, so the seam moves 25% -> 33.33% (measured).
     Half the 0.85rem gap re-centres the badge on the gutter itself. */
  left: calc(25% - 0.425rem);
  /* vertical middle of the 191px media strip */
  top: 95px;
  transform: translate(-50%, -50%);
  z-index: 3;

  /* A pill, not a bare circle: the offer has to be legible without hovering. */
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: 2.5rem;
  padding: 0 0.85rem 0 0.6rem;
  border: 2px solid var(--booking-panel-soft);
  border-radius: 999px;
  background: var(--sbv-brand-primary);
  color: var(--sbv-brand-primary-contrast);
  cursor: pointer;
  transition: transform 0.25s var(--ease, ease), box-shadow 0.25s var(--ease, ease);
}

.combo-badge:hover,
.combo-badge:focus-visible {
  transform: translate(-50%, -50%) scale(1.08);
  box-shadow: 0 6px 24px rgba(255, 217, 0, 0.32);
}

.combo-badge:focus-visible {
  outline: 3px solid rgba(255, 217, 0, 0.48);
  outline-offset: 3px;
}

/* Combo intent active: the badge stays visibly "pressed" so the hub confirms
   the combination is what's being booked. */
.combo-badge.is-combo-selected {
  border-color: var(--cream);
  box-shadow: 0 0 0 3px rgba(255, 217, 0, 0.35), 0 6px 24px rgba(255, 217, 0, 0.32);
}

.combo-badge-mark {
  display: grid;
  place-items: center;
}

/* A label rides along on desktop too. The badge used to be an unlabelled "+"
   circle above 820px, so the single best upsell on the page only revealed
   "10% korting" on hover — invisible to anyone who never hovered it.
   Two lengths because the space differs by an order of magnitude: a short pill
   squeezed onto the card seam vs. a full-width chip between stacked cards. The
   button's own aria-label carries the complete offer either way. */
.combo-badge-label {
  font-size: 0.78rem;
  font-weight: 800;
  white-space: nowrap;
}

.combo-badge-label-full { display: none; }

.combo-badge-tip {
  position: absolute;
  /* opens DOWNWARD over the card media strip: opening upward collides with
     the "Kies je beleving" section heading */
  top: calc(100% + 0.6rem);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 17rem;
  /* above the badge's own gold glow, which otherwise washes over the panel */
  z-index: 1;
  /* block, not grid: a grid container would turn the inline <strong> into a
     block-level grid item and break the sentence onto its own line */
  display: block;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--booking-border);
  border-radius: 12px;
  background: var(--booking-panel-soft);
  color: var(--sbv-text-secondary);
  box-shadow: var(--booking-shadow);
  font-size: 0.95rem;
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
  text-transform: none;
  letter-spacing: normal;

  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0.2s ease;
  pointer-events: none;
}

.combo-badge-tip strong { color: var(--sbv-text-primary); }

/* only the leading title is its own line */
.combo-badge-tip .combo-badge-tip-title {
  display: block;
  margin-bottom: 0.2rem;
}

.combo-badge:hover .combo-badge-tip,
.combo-badge:focus-visible .combo-badge-tip,
.combo-badge.is-open .combo-badge-tip {
  opacity: 1;
  visibility: visible;
}

@media (prefers-reduced-motion: reduce) {
  /* !important is hier geen bijgeloof meer maar ook geen kwaad: de page-wide
     `*`-blanket-regel die het ooit nodig maakte is weg (zie het blok onderaan).
     Blijft staan omdat deze twee regels de basisduur van de badge zelf moeten
     overrulen, niet omdat er nog iets van buitenaf tegenin duwt. */
  .combo-badge,
  .combo-badge-tip { transition-duration: 0.01ms !important; }
  .combo-badge:hover,
  .combo-badge:focus-visible { transform: translate(-50%, -50%); }
}

/* 3-column regime: the workshop|plancha seam sits at 1/3 of the grid. */
@media (max-width: 1023px) {
  .combo-badge { left: calc(33.333% - 0.425rem); }
}

/* Cards stack (measured at <=820px): no seam exists, so the badge becomes a
   full-width chip under the grid. Touch has no hover, so the copy is always
   visible instead of hidden behind a tooltip. */
@media (max-width: 820px) {
  .combo-badge {
    position: static;
    transform: none;
    width: 100%;
    height: auto;
    margin-top: 0.85rem;
    padding: 0.8rem 0.95rem;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    border: 1px dashed rgba(255, 217, 0, 0.55);
    border-radius: 12px;
    background: rgba(255, 217, 0, 0.08);
    color: var(--sbv-text-primary);
    text-align: left;
  }

  .combo-badge:hover,
  .combo-badge:focus-visible {
    transform: none;
    box-shadow: none;
    background: rgba(255, 217, 0, 0.14);
  }

  .combo-badge.is-combo-selected {
    border-style: solid;
    border-color: var(--sbv-brand-primary);
    background: rgba(255, 217, 0, 0.16);
    box-shadow: none;
  }

  .combo-badge-mark {
    flex: 0 0 auto;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 999px;
    background: var(--sbv-brand-primary);
    color: var(--sbv-brand-primary-contrast);
  }

  /* Full-width chip: room for the complete offer, so swap the short pill copy
     for the long one. */
  .combo-badge-label {
    font-size: 0.95rem;
    font-weight: 600;
  }

  .combo-badge-label-short { display: none; }
  .combo-badge-label-full { display: block; }

  /* Tooltip has no hover to hang off: fold it away entirely, the chip label
     already carries the offer. Keeps it reachable for screen readers. */
  .combo-badge-tip {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
    opacity: 1;
    visibility: visible;
  }
}

.experience-btn {
  width: 100%;
  min-height: 100%;
  text-align: left;
  position: relative;
  overflow: hidden;
  background: var(--booking-panel-soft);
  color: var(--sbv-text-primary);
  border: 1px solid var(--booking-border);
  border-radius: 12px;
  padding: 0;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22);
  transition: transform 140ms ease, box-shadow 140ms ease, border-color 140ms ease, background 140ms ease;
}

.experience-btn:disabled {
  cursor: wait;
}

.experience-btn:focus-visible,
.btn:focus-visible,
.day:focus-visible {
  outline: 3px solid rgba(255, 217, 0, 0.48);
  outline-offset: 3px;
}

.experience-btn:hover {
  border-color: rgba(255, 217, 0, 0.34);
  box-shadow: 0 18px 36px rgba(0, 0, 0, 0.32);
  transform: translateY(-2px);
}

.experience-btn[aria-pressed="true"] {
  border-color: var(--booking-border-strong);
  box-shadow: 0 0 0 2px rgba(255, 217, 0, 0.2), 0 18px 36px rgba(0, 0, 0, 0.34);
}

.experience-btn.is-loading {
  border-color: var(--booking-border-strong);
  box-shadow: 0 0 0 2px rgba(255, 217, 0, 0.2), 0 18px 36px rgba(0, 0, 0, 0.34);
}

.experience-media {
  position: relative;
  aspect-ratio: 4 / 3;
  width: 100%;
  background: var(--booking-neutral-dark);
  overflow: hidden;
  margin-bottom: -1px;
  z-index: 1;
}

.experience-media img {
  position: absolute;
  inset: -1px;
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  object-fit: cover;
  display: block;
}

.experience-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(15, 14, 11, 0.02), rgba(15, 14, 11, 0.68));
}

.experience-label {
  position: absolute;
  left: 0.75rem;
  bottom: 0.75rem;
  z-index: 2;
  display: inline-flex;
  max-width: calc(100% - 1.5rem);
  padding: 0.38rem 0.6rem;
  border-radius: 999px;
  background: rgba(255, 217, 0, 0.94);
  color: var(--booking-neutral-dark);
  font-size: 0.8rem;
  font-weight: 800;
}

/* Single anchor pill across the four cards (config/experiences.php `badge`).
   Sits opposite .experience-label so the two never collide. */
.experience-badge {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 2;
  display: inline-flex;
  padding: 0.34rem 0.6rem;
  border-radius: 999px;
  background: rgba(15, 14, 11, 0.82);
  border: 1px solid rgba(255, 217, 0, 0.55);
  color: var(--sbv-brand-primary);
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.experience-body {
  position: relative;
  /* flex-grow, not just min-height: the grid stretches all four cards to the
     same height, but a body sized only by min-height stops at its content, so
     a card with a shorter description left slack at the bottom and its action
     strip sat a few px higher than its neighbours'. Filling the card makes
     every body identical in height, and .experience-highlights' margin-top:auto
     then lands all four "Bekijk datums" buttons on exactly the same line. */
  flex: 1 1 auto;
  min-height: 360px;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  padding: 1rem;
}

.experience-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.experience-icon {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  background: rgba(255, 217, 0, 0.12);
  border: 1px solid rgba(255, 217, 0, 0.22);
}

.experience-title {
  font-weight: 900;
  line-height: 1.12;
  color: var(--cream);
}

.experience-meta {
  margin-top: 0.1rem;
  color: var(--sbv-text-tertiary);
  font-size: 0.88rem;
}

.experience-desc {
  margin: 0;
  color: var(--sbv-text-secondary);
  line-height: 1.45;
}

.experience-highlights {
  display: grid;
  gap: 0.45rem;
  margin-top: auto;
}

.experience-highlight {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  color: var(--sbv-text-tertiary);
  line-height: 1.35;
  font-size: 0.92rem;
}

.experience-highlight .sbv-icon {
  flex: 0 0 auto;
  margin-top: 0.1rem;
}

.btn {
  min-height: 44px;
  border: 1px solid var(--booking-border);
  background: rgba(255, 255, 255, 0.06);
  color: var(--sbv-text-primary);
  padding: 0.62rem 0.8rem;
  border-radius: 12px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-weight: 700;
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}

.btn:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.22);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-primary {
  width: 100%;
  background: var(--sbv-brand-primary);
  border-color: var(--sbv-brand-primary);
  color: var(--sbv-brand-primary-contrast);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  transition: transform 0.25s var(--ease, ease), box-shadow 0.25s var(--ease, ease);
}

.btn-primary:hover {
  /* Site CTA hover (master.css .cbtn:hover / index.css .hc-cta:hover): the
     button never changes color on hover, only lifts + glows. Pin background
     and border here — otherwise the generic .btn:hover translucency wash
     (meant for the dark secondary button) applies on top of the solid gold
     rest state and muddies the dark text past readable contrast. */
  background: var(--sbv-brand-primary);
  border-color: var(--sbv-brand-primary);
  transform: translateY(-2px);
  box-shadow: 0 6px 24px rgba(255, 217, 0, 0.32);
}

.btn-secondary { background: rgba(255, 255, 255, 0.04); }

/* Hero CTA — overrides full-width default of .btn-primary; pill shape +
   uppercase tracked label matches the site's CTA vocabulary (.cbtn/.ask). */
.hero-cta {
  width: fit-content;
  margin-top: 1.5rem;
  font-size: 0.95rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 1rem 1.9rem;
  text-decoration: none;
  border-radius: 999px;
}
.hero-cta:hover {
  /* Pin background/border for the same reason as .btn-primary:hover above —
     without it the generic .btn:hover translucency wash applies on top. */
  background: var(--sbv-brand-primary);
  border-color: var(--sbv-brand-primary);
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(255, 217, 0, 0.3);
}
@media (max-width: 640px) {
  .hero-cta { width: 100%; justify-content: center; }
}

/* ── Reviews section ─────────────────────────────────────────── */
.reviews-section {
  padding: clamp(1.75rem, 4vw, 3rem) 0;
  opacity: 0;
  transition: opacity 0.4s ease;
}
.reviews-section.is-loaded { opacity: 1; }

.reviews-heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: 1.75rem;
  flex-wrap: wrap;
}

.reviews-title {
  font-size: var(--fs-h2); /* site type scale — was a local 2rem clamp */
  font-weight: 400;
  color: var(--cream);
  margin-top: 0.2rem;
}

/* Stars, score line and link share one centre axis. Right-aligning the boxes
   still left the link's own text ragged inside its full-width block, so the
   three lines each started somewhere else; centring makes the stack read as one
   badge. text-align carries the centring into the meta text and the block link. */
.reviews-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.2rem;
}

.reviews-badge-stars {
  font-size: 1.3rem;
  color: var(--sbv-brand-primary);
  letter-spacing: 0.05em;
  line-height: 1;
}

.reviews-badge-meta {
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.55);
  white-space: nowrap;
}
.reviews-badge-meta strong { color: var(--cream); }

.reviews-carousel-wrap {
  position: relative;
  padding: 0 3rem;
}

.reviews-viewport {
  overflow: hidden;
  border-radius: 12px;
  height: 15rem;
}

.reviews-track {
  display: flex;
  gap: 1rem;
  height: 100%;
}


/* Static card, deliberately: these used to be links to Google, which made the
   page's strongest trust element its easiest exit. The one outbound link now
   lives on the badge (.reviews-badge-link). No cursor/hover affordance either —
   nothing here is clickable any more, so nothing should look like it is. */
.review-card {
  flex: 1;
  min-width: 0;
  /* Same lit-edge treatment as .card, shorter falloff for the shorter box. */
  background:
    linear-gradient(180deg, rgba(244, 239, 227, 0.05), rgba(244, 239, 227, 0) 120px),
    var(--booking-panel);
  border: 1px solid var(--booking-border-panel);
  border-radius: 12px;
  box-shadow:
    inset 0 1px 0 rgba(244, 239, 227, 0.08),
    0 18px 40px -22px #000;
  padding: 1.35rem 1.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  overflow: hidden;
}

/* Carousel nav arrows */
.reviews-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: var(--booking-panel);
  border: 1px solid var(--booking-border);
  border-radius: 50%;
  width: 2.4rem;
  height: 2.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 2;
  color: var(--cream);
  opacity: 0.6;
  transition: opacity 0.2s, background 0.2s;
}
.reviews-nav:hover,
.reviews-nav:focus-visible { opacity: 1; background: rgba(255,255,255,0.08); outline: none; }
.reviews-nav--prev { left: 0.3rem; }
.reviews-nav--next { right: 0.3rem; }

/* Dot indicators */
.reviews-dots {
  display: flex;
  gap: 0.5rem;
  justify-content: center;
  margin-top: 1.25rem;
}
.reviews-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.2);
  border: none;
  cursor: pointer;
  /* Het TIKDOEL is 24x24 (WCAG 2.5.8), het bolletje blijft 8px: padding op een
     content-box vergroot alleen het raakvlak en background-clip houdt de vulling
     bij de stip. Op een telefoon was dit een doel van 8 bij 8 pixels — kleiner
     dan de punt van een vinger, en 80% boekt op de telefoon. */
  box-sizing: content-box;
  padding: 0.5rem;
  background-clip: content-box;
  transition: background 0.25s, transform 0.25s;
}
/* The dots are real controls (they jump the carousel) but looked like decoration
   — no hover at all. Brighten and grow slightly so they invite the click. */
.reviews-dot:hover,
.reviews-dot:focus-visible {
  background: rgba(255, 255, 255, 0.55);
  transform: scale(1.35);
}

.reviews-dot.is-active {
  background: var(--sbv-brand-primary);
  transform: scale(1.45);
}

.reviews-dot.is-active:hover,
.reviews-dot.is-active:focus-visible {
  background: var(--sbv-brand-primary);
  transform: scale(1.6);
}

.review-stars {
  color: var(--sbv-brand-primary);
  font-size: 1rem;
  letter-spacing: 0.04em;
  line-height: 1;
}

.review-text {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.92rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.88);
  font-style: italic;
  margin: 0;
}

.review-footer {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--booking-border);
  margin-top: auto;
}

.review-name {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--cream);
}

/* The single link out to Google, on the rating badge. */
.reviews-badge-link {
  /* inline-block + verticale padding: als <block> was het raakvlak 19px hoog,
     onder het minimum van 24px voor een tikdoel. */
  display: inline-block;
  margin-top: 0.15rem;
  padding: 0.25rem 0;
  color: rgba(255, 217, 0, 0.75);
  font-size: 0.78rem;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.reviews-badge-link:hover { color: var(--sbv-brand-primary); }

.review-date {
  font-size: 0.75rem;
  color: rgba(255, 255, 255, 0.4);
  white-space: nowrap;
}

@media (max-width: 700px) {
  .reviews-heading { align-items: flex-start; flex-direction: column; }
  /* Phone: the heading stacks left-aligned, so the badge follows the page edge
     instead of centring on itself. Text alignment must follow the boxes. */
  .reviews-badge { align-items: flex-start; text-align: left; }
  .reviews-badge-meta { white-space: normal; }
  .reviews-nav { display: none; }
}

#experience-section { scroll-margin-top: 5.5rem; }

#calendar-card { scroll-margin-top: 6.25rem; }

.calendar-header {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: 1rem;
  margin-bottom: 1rem;
}

.booking-subtitle {
  margin: 0;
  color: var(--sbv-text-secondary);
  font-weight: 700;
}

.calendar-nav {
  min-width: min(100%, 390px);
  display: grid;
  /* 1fr on both outer tracks, not auto: with auto they were sized by their own
     label, so "Vorige" rendered visibly narrower than "Volgende". Equal fr
     tracks make the pair symmetrical whatever the words are, and the month
     label in the middle stays exactly as wide as its text. */
  grid-template-columns: 1fr auto 1fr;
  /* stretch, not center: the buttons resolve to 50px (padding + line-height)
     while .month-label's min-height is 44px, so centring left the middle pill
     visibly shorter than the two beside it. */
  align-items: stretch;
  gap: 0.6rem;
}

.month-label {
  min-height: 44px;
  display: grid;
  place-items: center;
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--booking-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  font-weight: 800;
  color: var(--cream);
  text-align: center;
}

.calendar {
  border-top: 1px solid var(--booking-border);
  padding-top: 0.85rem;
}

.weekday-row,
.day-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.45rem;
}

.weekday-row { margin-bottom: 0.45rem; }

.weekday {
  text-align: center;
  color: var(--sbv-text-tertiary);
  font-size: 0.85rem;
  font-weight: 700;
  padding: 0.25rem 0;
}

.day {
  /* Reset so in-month <button> days render identically to the decorative divs. */
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  text-align: left;
  color: inherit;
  width: 100%;
  margin: 0;
  position: relative; /* anchors the hover arrow on available days */
  border: 1px solid var(--booking-border);
  background: rgba(255, 255, 255, 0.04);
  /* padding-box, not the default border-box (iPad report, 15-09-2026: "some Optie dates are
     missing the bottom border"). Every border here is TRANSLUCENT, and a border-box background
     paints the cell's own lighter fill underneath it — so the line is the border colour blended
     over the fill instead of over the dark card, which is most of its contrast gone. WebKit then
     splits that already-weak 1px line over two device rows on a fractional edge and it reads as
     absent. With padding-box the fill stops at the border, whatever the subpixel position. */
  background-clip: padding-box;
  border-radius: 12px;
  min-height: 78px;
  padding: 0.55rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  cursor: pointer;
  /* Transform gets a slight overshoot so an available day SPRINGS up instead of
     sliding — that snap is most of what makes the grid feel alive. Everything
     else stays linear-ish so colour changes don't wobble. */
  transition: background 140ms ease, border-color 140ms ease,
              transform 240ms cubic-bezier(0.34, 1.45, 0.64, 1),
              box-shadow 200ms ease, opacity 140ms ease;
  /* We draw our own press state; kill the grey/blue tap flash on Android+iOS. */
  -webkit-tap-highlight-color: transparent;
}

.day:hover {
  background: rgba(255, 255, 255, 0.08);
  transform: translateY(-1px);
}

.day.outside {
  opacity: 0.3;
  cursor: default;
}

.day.outside:hover {
  background: rgba(255, 255, 255, 0.04);
  transform: none;
}

.day-number {
  font-weight: 900;
  font-size: 0.98rem;
  color: var(--cream);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  /* reset master.css's .badge pill (padding/border/radius) — here it is the
     small availability label inside a calendar day, not a tag pill */
  padding: 0;
  border: 0;
  border-radius: 0;
  white-space: normal;
  color: var(--sbv-text-tertiary);
  font-size: 0.78rem;
  line-height: 1.2;
}

/* Only bookable days get colour. A normal month is mostly taken, so painting
   every taken day red turned the grid into a wall of refusals — the guest read
   "we're full" instead of "these dates are yours". Same information, carried by
   presence of green rather than absence of red. */
/* 0.85, was 0.54 (iPad report, 15-09-2026). A half-covered hairline keeps its colour at this
   alpha; at 0.54 the half that survives anti-aliasing is too faint to read as a line.
   AND THE RING IS AN INSET SHADOW, NOT ONLY A BORDER. Raising the alpha was not enough: the
   report came back with the bottom edge still missing on some dates. A 1px BORDER on a box whose
   height and origin are both fractional (79.09px here) is a hairline WebKit may drop on one side;
   an inset shadow is painted inside the padding box against the rounded path and survives it.
   1.5px so it always covers a whole device pixel at 2x. The border stays for the layout and as
   the fallback. The hover and press rules below repeat the ring — a box-shadow does not cascade,
   so leaving it out of those lists would make the outline vanish under the finger. */
.day.available {
  box-shadow: inset 0 0 0 1.5px rgba(34, 197, 94, 0.85);
  border-color: rgba(34, 197, 94, 0.85);
  background: color-mix(in srgb, var(--sbv-success) 13%, rgba(255, 255, 255, 0.04));
}

/* Available days are the only thing on this grid worth clicking, so they are the
   only thing that reacts: a real lift, a green glow and an arrow that says
   "pick me". Taken days deliberately stay inert beyond a small opacity change —
   rewarding a hover that leads nowhere is what made the old grid feel busy. */
.day.available:hover,
.day.available:focus-visible {
  background: color-mix(in srgb, var(--sbv-success) 26%, rgba(255, 255, 255, 0.06));
  border-color: rgba(34, 197, 94, 1);
  /* Lift AND grow: scale is what reads at a glance across a 7-column grid. The
     raised day also needs to sit above its neighbours or the glow gets clipped. */
  transform: translateY(-4px) scale(1.045);
  z-index: 2;
  box-shadow: inset 0 0 0 1.5px rgba(34, 197, 94, 1),
              0 14px 28px rgba(0, 0, 0, 0.42),
              0 0 0 1px rgba(34, 197, 94, 0.45),
              0 8px 26px color-mix(in srgb, var(--sbv-success) 38%, transparent);
}

.day.available:hover .day-number,
.day.available:focus-visible .day-number {
  color: #fff;
}

.day.available:hover .badge,
.day.available:focus-visible .badge {
  color: var(--sbv-success);
}

/* Nudge arrow, hover-capable pointers only — touch has no hover, and on a
   64px-tall phone cell there is no room for it either. */
@media (hover: hover) {
  .day.available::after {
    content: "→";
    position: absolute;
    right: 0.5rem;
    bottom: 0.45rem;
    font-size: 0.95rem;
    font-weight: 900;
    line-height: 1;
    color: var(--sbv-success);
    opacity: 0;
    transform: translateX(-4px);
    transition: opacity 140ms ease, transform 140ms ease;
    pointer-events: none;
  }

  .day.available:hover::after,
  .day.available:focus-visible::after {
    opacity: 1;
    transform: translateX(0);
  }
}

/* Press feedback. This is the ONLY pre-selection signal a touch guest ever gets
   — no hover exists there — so the day must visibly compress under the finger.
   Declared after :hover so it wins at equal specificity, and fast enough to
   land within the press itself. */
.day.available:active {
  transform: translateY(-1px) scale(0.955);
  transition-duration: 90ms;
  box-shadow: inset 0 0 0 1.5px rgba(34, 197, 94, 1),
              0 4px 12px rgba(0, 0, 0, 0.35),
              0 0 0 1px rgba(34, 197, 94, 0.55);
}

/* Confirmation that the date landed: the cell springs out of the press and a
   gold ring radiates once. renderCalendarGrid() rebuilds the cells on every
   pick, so the newly-selected day is a fresh node and this replays each time —
   on touch it is the whole feedback loop, since the press state is already gone
   by the time the finger lifts. */
@keyframes day-pick {
  0%   { transform: scale(0.93); box-shadow: 0 0 0 0 rgba(255, 217, 0, 0.5),  0 0 0 2px rgba(255, 217, 0, 0.24); }
  55%  { transform: scale(1.055); }
  100% { transform: scale(1);     box-shadow: 0 0 0 18px rgba(255, 217, 0, 0), 0 0 0 2px rgba(255, 217, 0, 0.24); }
}

.day.selected {
  animation: day-pick 540ms cubic-bezier(0.34, 1.45, 0.64, 1);
  z-index: 3; /* the ring radiates over its neighbours */
}

/* Once picked, the day stops inviting: no lift, no arrow — it is settled. */
.day.available.selected:hover,
.day.available.selected:focus-visible {
  transform: none;
}

.day.selected::after { opacity: 0 !important; }

/* Touch: :hover latches after a tap and would leave a day looking permanently
   half-picked, so the hover treatment is undone where no real pointer exists.
   The press state and the pick animation carry the interaction instead. */
@media (hover: none) {
  .day:hover,
  .day.available:hover {
    transform: none;
    box-shadow: none;
    z-index: 0;
  }

  /* THE RING SURVIVES THE UNDO. `box-shadow: none` above kills the lift AND the inset ring, and
     iPad Safari keeps :hover on the last tapped cell — so without this the green outline
     disappeared from a date the moment a guest touched it. Same alpha as the resting state. */
  .day.available:hover {
    background: color-mix(in srgb, var(--sbv-success) 13%, rgba(255, 255, 255, 0.04));
    border-color: rgba(34, 197, 94, 0.85);
    box-shadow: inset 0 0 0 1.5px rgba(34, 197, 94, 0.85);
  }

  /* …but never suppress the press or the pick — those ARE the touch feedback. */
  .day.available:active {
    transform: scale(0.955);
    box-shadow: inset 0 0 0 1.5px rgba(34, 197, 94, 1),
                0 4px 12px rgba(0, 0, 0, 0.35), 0 0 0 1px rgba(34, 197, 94, 0.55);
  }

  .day.selected { animation: day-pick 540ms cubic-bezier(0.34, 1.45, 0.64, 1); }
}

/* Taken and too-soon days read the same: dimmed, still clickable (the status
   card underneath is where the way forward lives). */
.day.unavailable,
.day.not-option {
  border-color: var(--booking-border);
  background: rgba(255, 255, 255, 0.025);
  opacity: 0.62;
}

.day.unavailable .day-number,
.day.not-option .day-number { color: var(--sbv-text-secondary); }

.day.unavailable:hover,
.day.not-option:hover { opacity: 0.85; }

/* Positive count above the grid: "Nog 7 datums beschikbaar in augustus". */
.availability-count {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid rgba(34, 197, 94, 0.32);
  border-radius: 12px;
  background: color-mix(in srgb, var(--sbv-success) 8%, transparent);
  color: var(--sbv-text-secondary);
  font-size: 0.95rem;
}

.availability-count strong { color: var(--cream); }

/* Fully booked month: no scarcity to celebrate, so this becomes the way out. */
.availability-count.is-empty {
  border-color: var(--booking-border);
  background: rgba(255, 255, 255, 0.04);
}

/* Sits right after the sentence, not flushed to the card edge — the shell's
   fixed "Vraag het ons" pill lives in that corner. */
.availability-count .btn { margin-left: 0.25rem; }

.day.selected {
  border-color: var(--sbv-brand-primary);
  box-shadow: 0 0 0 2px rgba(255, 217, 0, 0.24);
}

.loading {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 0;
  color: var(--sbv-text-secondary);
}

.loading-title { font-weight: 800; }

.loading-subtitle {
  color: var(--sbv-text-tertiary);
  font-size: 0.95rem;
}

.spin { animation: spin 1s linear infinite; }

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

/* --- Failed state (WI-21) -------------------------------------------------
   The calendar's error box is not a banner, it is the state the guest is left
   in when the spinner gives up — so it carries an icon, a sentence and one
   action, laid out on a single row while there is room for it. */
.error {
  margin-top: 0.85rem;
  padding: 0.85rem;
  border: 1px solid rgba(239, 68, 68, 0.58);
  border-radius: 12px;
  /* Measured against the COMPOSITED background (rgb(39,24,20)) — the red tint
     is 8% over warm ink, so it renders near-black, not red: 11.83:1. */
  color: #fecaca;
  background: rgba(239, 68, 68, 0.08);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.error-body {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  min-width: 0;
}

.error-text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
  /* Only the sentence may ever wrap, and when it does the break lands sensibly
     instead of leaving one orphan word on the second line. */
  text-wrap: pretty;
}

/* 14.01:1 composited. */
.error-title {
  font-weight: 800;
  color: #fee2e2;
}

/* Buttons never wrap: the label is short enough to hold one line at any width
   the funnel supports, and a two-line button reads as broken. */
.error-retry {
  white-space: nowrap;
  flex: 0 0 auto;
}

/* --- In-flight state (WI-21) ----------------------------------------------
   The month arrows stay clickable while a month is loading — see
   setMonthNavBusy() — so this says "working", not "unavailable". Deliberately
   NOT opacity below 0.6: the label has to keep passing AA while it is busy. */
.calendar-nav .btn.is-busy {
  cursor: progress;
  opacity: 0.72;
  border-color: var(--booking-border-strong);
}

/* The handoff button is a one-way door and IS locked after the first click. */
#continue-btn.is-busy {
  cursor: progress;
  opacity: 0.72;
  pointer-events: none;
}

.status-row { margin-top: 0.85rem; }

.status-card {
  border: 1px solid var(--booking-border);
  border-radius: 12px;
  padding: 0.9rem;
  background: rgba(255, 255, 255, 0.04);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
}

.status-left {
  display: flex;
  align-items: center;
  gap: 0.65rem;
}

.status-title {
  font-weight: 800;
  color: var(--cream);
}

.status-subtitle {
  color: var(--sbv-text-tertiary);
  font-size: 0.95rem;
}

/* Every taken date now offers a route onward instead of ending the funnel.
   margin-right keeps the buttons off the far right edge, where the shell's
   fixed "Vraag het ons" pill floats and would clip the last one. */
.status-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-right: auto;
}

/* Compact button for in-card escape hatches, so they never outweigh the real
   step CTA further down the page. */
.btn-sm {
  padding: 0.45rem 0.7rem;
  font-size: 0.85rem;
  min-height: 0;
}

.slot-summary {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.65rem;
}

/* Total and its breakdown sit together on one baseline. The price used to be a
   full-width bar with the figures pushed to the far right, which read as a
   fifth summary row and put ~900px between the label and the number. */
.slot-price-figures {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.15rem 0.5rem;
}

.slot-price-total {
  font-weight: 900;
  font-size: 1.45rem;
  line-height: 1.1;
  color: var(--cream);
}

.slot-price-note {
  color: var(--sbv-text-tertiary);
  font-size: 0.9rem;
}

/* Risk reversal, directly above the step CTA. */
.reassurance {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0.85rem 0 0;
  padding: 0.7rem 0.85rem;
  border: 1px solid rgba(255, 217, 0, 0.28);
  border-radius: 12px;
  background: rgba(255, 217, 0, 0.06);
  color: var(--sbv-text-secondary);
  font-size: 0.95rem;
  line-height: 1.45;
}

.reassurance .sbv-icon {
  flex: 0 0 auto;
  margin-top: 0.15rem;
}

.reassurance strong { color: var(--cream); }

/* Inline text button — used for the group-size questions, which should read as
   a sentence, not compete with the step CTA. */
.link-btn {
  appearance: none;
  -webkit-appearance: none;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--sbv-brand-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.link-btn:hover { color: var(--cream); }

.hint-sep { color: var(--sbv-text-tertiary); }

.slot-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.15rem;
  min-height: 76px;
  padding: 0.7rem;
  border: 1px solid var(--booking-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
}

.slot-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--sbv-text-tertiary);
  font-size: 0.85rem;
  font-weight: 700;
}

.slot-label .sbv-icon { flex: 0 0 auto; }

/* The two things still being DECIDED (what it costs, how many people), side by
   side under the four fixed facts. Price gets the narrower column — it is a
   readout; the stepper needs room for its controls. */
.slot-decisions {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: 0.65rem;
  margin-top: 0.65rem;
  align-items: stretch;
}

/* Geen prijs om te tonen (renderSlotPrice zet deze klasse): één kolom, zodat de
   deelnemerkiezer de hele kaart krijgt in plaats van in de smalle kolom te
   blijven staan met een lege helft ernaast. */
.slot-decisions.is-priceless {
  grid-template-columns: minmax(0, 1fr);
}

@media (max-width: 820px) {
  .slot-decisions { grid-template-columns: 1fr; }
}

/* Price: a card in the same family as .slot-row, tinted gold because it is the
   number the guest is actually weighing. */
.slot-price {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.3rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid rgba(255, 217, 0, 0.3);
  border-radius: 12px;
  background: rgba(255, 217, 0, 0.07);
}

.actions {
  margin-top: 0.85rem;
  display: grid;
  gap: 0.6rem;
}

/* No top margin: it is a grid cell in .slot-decisions now, so the grid gap
   owns the spacing. */
.participant-picker {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--booking-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
}

.participant-picker-text {
  display: grid;
  gap: 0.15rem;
}

.participant-picker-title {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 800;
  color: var(--cream);
}

.participant-picker-title .sbv-icon { flex: 0 0 auto; }

.participant-picker-hint {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.1rem 0.4rem;
  color: var(--sbv-text-tertiary);
  font-size: 0.9rem;
}

/* One indivisible unit: the pair moves to the next line together rather than
   splitting and orphaning the second link. */
.hint-links {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  white-space: nowrap;
}

.stepper {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.stepper-btn {
  width: 44px;
  height: 44px;
  min-height: 44px;
  display: grid;
  place-items: center;
  border: 1px solid var(--booking-border);
  background: rgba(255, 255, 255, 0.06);
  color: var(--sbv-text-primary);
  border-radius: 12px;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}

.stepper-btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.22);
}

.stepper-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.stepper-btn:focus-visible {
  outline: 3px solid rgba(255, 217, 0, 0.48);
  outline-offset: 3px;
}

/* ── Deelnemers-schuif ─────────────────────────────────────────────────────
   Tick-slider volgens apps/local/toolkit/toolkit-tick-slider.md. Die notitie
   bestaat omdat dit patroon hier al meermaals scheef is gegaan; de regels die
   hieronder gecodeerd staan zijn genummerd zoals daar.

   Stijl overgezet uit belevingen.css — die laadt deze pagina niet. Bewust een
   kopie: de twee sliders staan in verschillende layouts en zijn los getuned;
   samenvoegen is een aparte opruimactie. */

/* Tekst bóven de schuif, schuif over de VOLLE breedte. Naast elkaar (space-between)
   kreeg de schuif maar de halve kaart. */
.participant-picker {
  display: block;
}
.participant-picker .participant-picker-text {
  margin-bottom: 0.75rem;
}

.slider-container {
  display: flex;
  align-items: center;
  gap: 1rem;
  position: relative;
  min-height: 44px;
  width: 100%;
}

/* Regel 11 — eigen GPU-laag. De input hertekent bij elke pointer-move van een
   sleep; zonder aparte lagen kunnen die damage rects de rail ernaast blanken. */
.slider-faux-track,
.slider-ticks,
.slider-container input[type="range"] {
  transform: translateZ(0);
}

/* Regel 2 — de rail loopt van de BUITENRAND van de eerste stip tot die van de
   laatste. left/width worden door JS gezet; hier alleen de vorm. */
.slider-faux-track {
  position: absolute;
  height: 8px;
  top: calc(50% - 4px);
  background: rgba(255, 255, 255, 0.22);
  border-radius: 5px;
  pointer-events: none;
  z-index: 1;
}

.slider-ticks {
  position: absolute;
  left: 0;
  right: 0;
  height: 8px;
  top: calc(50% - 4px);
  pointer-events: none;
  z-index: 2;
}

.slider-ticks .tick-dot {
  position: absolute;
  top: 50%;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid #000;
  transform: translateY(-50%);
}

/* Regel 10 — de stip onder de duim verbergen, anders zie je concentrische ringen. */
.slider-ticks .tick-dot.active { visibility: hidden; }

.slider-container input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  flex: 1 1 auto;
  height: 8px;
  margin: 0;
  background: transparent;
  position: relative;
  z-index: 3;
  cursor: pointer;
}

/* Regel 9 — elke door de engine getekende extra uit. ::-moz-range-progress is de
   sluipmoordenaar: Firefox vult links van de duim in de accentkleur en de
   track-regels dekken dat niet af. */
.slider-container input[type="range"]::-webkit-slider-runnable-track { height: 8px; background: transparent; }
.slider-container input[type="range"]::-moz-range-track { height: 8px; background: transparent; }
.slider-container input[type="range"]::-moz-range-progress { background: transparent; }

.slider-container input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 24px;
  height: 24px;
  margin-top: -8px;
  border-radius: 50%;
  background: var(--sbv-brand-primary);
  border: 3px solid var(--booking-bg);
  cursor: ew-resize;
}
.slider-container input[type="range"]::-moz-range-thumb {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--sbv-brand-primary);
  border: 3px solid var(--booking-bg);
  cursor: ew-resize;
}

/* Regel 8 — focusring op de DUIM. Een generieke outline hugt de 8px-hoge input
   en tekent een TWEEDE rail waarvan de uiteinden een halve duim buiten de
   eindstippen steken; die verschijnt na elke sleep en leest als scheefstand. */
.slider-container input[type="range"]:focus { outline: none; box-shadow: none; }
.slider-container input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 3px rgba(255, 217, 0, 0.45);
}
.slider-container input[type="range"]:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 3px rgba(255, 217, 0, 0.45);
}

/* Het getal naast de schuif — vaste breedte, zodat 8 en 16 de baan niet laten
   verspringen (regel 4 gaat over precies dat effect). */
.slider-value {
  flex: 0 0 auto;
  width: 3.5rem;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 900;
  font-size: 1.1rem;
  color: var(--cream);
  border: 1px solid var(--booking-border);
  border-radius: 12px;
  background: var(--booking-panel-soft);
}

.stepper-value {
  width: 3.5rem;
  height: 44px;
  text-align: center;
  font-weight: 900;
  font-size: 1.1rem;
  color: var(--cream);
  border: 1px solid var(--booking-border);
  border-radius: 12px;
  background: var(--booking-panel-soft);
  caret-color: transparent;
}

@media (max-width: 540px) {
  .participant-picker {
    flex-direction: column;
    align-items: stretch;
  }

  .stepper {
    justify-content: space-between;
  }
}

@media (max-width: 820px) {
  .hero-panel {
    grid-template-columns: 1fr;
    min-height: 0;
  }

  .hero-visual {
    min-height: 320px;
    order: -1;
  }

  .hero-visual::after {
    background: linear-gradient(180deg, rgba(15, 14, 11, 0.06), rgba(15, 14, 11, 0.72));
  }

  .experience-grid,
  .slot-summary {
    grid-template-columns: 1fr;
  }

  .experience-body { min-height: 0; }

  .calendar-header { grid-template-columns: 1fr; }
}

@media (max-width: 700px) {
  .container { padding: 84px 0.85rem 0.85rem; }

  .hero-content,
  .card { padding: 1rem; }

  .hero-visual { min-height: 260px; }

  .calendar-nav {
    min-width: 0;
    grid-template-columns: 44px 1fr 44px;
  }

  /* ARROWS ONLY ON A PHONE. This used to be `font-size: 0`, which site/css/booking.css
     overrides with `.booking-hub .calendar-nav .btn { font-size: 16px }` — that stylesheet loads
     after this one and wins on specificity, so on the public page both words came back and
     spilled out of their 44px buttons. Hiding the LABEL element is not a font trick and no
     later font-size rule can undo it. The button keeps its aria-label. */
  .calendar-nav .btn {
    padding: 0;
  }

  .calendar-nav .calendar-nav-label { display: none; }

  .calendar-nav .btn .sbv-icon { margin: 0; }

  .weekday-row,
  .day-grid { gap: 0.25rem; }

  .day {
    min-height: 64px;
    padding: 0.4rem;
  }

  .badge { font-size: 0; }
}

/* --- Reduced motion: scrollen wel, een generieke `*`-regel NOOIT -------------
   HIER STOND EEN BLANKET-REGEL, EN DIE IS TWEE KEER ALS BUG TERUGGEKOMEN.
   Ze luidde `*, *::before, *::after { animation-duration: 2.0s !important;
   animation-iteration-count: 1 !important; transition-duration: 2.0s !important }`
   en kwam op 30-08-2026 mee in een bulkdeploy. Het is het bekende boilerplate-
   fragment, alleen met 2.0s waar 0.01ms hoort — en dat is het slechtste van twee
   werelden: decoratie wordt stroperig, en alles wat MOET blijven draaien valt na
   één trage slag stil.

   Wat het kostte, allebei gemeld als "hij stopt halverwege":
     1. De spinner in #loading deed één slag van 2s terwijl de maandaanvraag
        (~2s of meer) nog liep — een bevroren icoon boven "Beschikbaarheid
        ophalen...", dus een pagina die vastgelopen lijkt.
     2. De ring van het laadgordijn (.pl-ring, uit de gedeelde chrome) idem:
        pl-spin werd 2s/1 keer in plaats van 1,4s/oneindig, en het gordijn staat
        er 5 seconden. Gemeten: na ~2s stond de ring stil op de rest van het
        gordijn.

   Punt 2 is de reden dat hier geen uitzonderingslijstje staat maar niets meer.
   De chrome (site/css/master.css) neemt zijn eigen, expliciet vastgelegde besluit
   over rustige stand: decoratieve effecten — gordijnspinner, logo-puls, chatpuntjes
   — blijven draaien. Deze pagina laadde master.css en overschreef dat besluit
   daarna ongevraagd met een `*`. Een lokale blanket-regel kan per definitie niet
   weten wat de chrome bewust aan laat staan; er is geen lijstje dat dat repareert.

   Rustige stand hoort hier gericht te zijn. Zie de takken bij .combo-badge en
   .day.attention, en in belevingen.css bij .card.is-selected: die dempen
   bewegingen die echt ongemak geven. Scrollgedrag blijft wel generiek, want dat
   is de ene beweging die aantoonbaar misselijk maakt en nooit terugkoppeling is.
   Getest met apps/local/tests/loading_spinner/run.sh, in beide standen. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { scroll-behavior: auto !important; }
}
/* ---------------- 'Op aanvraag' day state (rule engine, 2026-08-05) ----------------
   A third calendar state between available and taken: the day CAN be booked, but a rule
   flagged it (adjacent to another experience, or the week is at its cap) so the brewer
   confirms it personally.

   This does not break the "never paint unavailability red" rule above — that rule exists so
   a guest never faces a wall of refusals. Amber here is not a refusal: the day is clickable,
   it leads to the normal booking flow, and it carries an explanation. It reads as
   "yes, with a note", which is exactly what it is. */
.day.attention {
  border-color: rgba(245, 158, 11, 0.5);
  background: color-mix(in srgb, #f59e0b 11%, rgba(255, 255, 255, 0.04));
}

.day.attention:hover,
.day.attention:focus-visible {
  border-color: rgba(245, 158, 11, 0.8);
  background: color-mix(in srgb, #f59e0b 17%, rgba(255, 255, 255, 0.05));
  transform: translateY(-3px);
}

.day.attention .badge-text { color: #fbbf24; }

@media (hover: none) {
  .day.attention:hover { transform: none; }
}

/* Explanation on the slot card once an 'op aanvraag' date is picked. */
.slot-notice {
  /* The summary is a 4-column grid; without this the notice lands in ONE column and wraps to
     five lines with the rest of the row empty beside it. It is a sentence, so it gets the
     full width and wraps only where it genuinely runs out of room. */
  grid-column: 1 / -1;
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin-top: 0.25rem;
  padding: 0.8rem 0.9rem;
  border-radius: 12px;
  border: 1px solid rgba(245, 158, 11, 0.42);
  background: rgba(245, 158, 11, 0.09);
  line-height: 1.5;
  text-wrap: pretty;
}

.slot-notice .sbv-icon { flex-shrink: 0; margin-top: 0.15rem; color: #fbbf24; }
.slot-notice strong { display: block; margin-bottom: 0.15rem; }
.slot-notice span { color: var(--sbv-text-secondary); font-size: 0.93rem; }

@media (prefers-reduced-motion: reduce) {
  .day.attention:hover,
  .day.attention:focus-visible { transform: none; }
}
