/* Surface stack — mirrors brew_suite/booking/styles.css on purpose. Booking is
   the step BEFORE these pages in the same funnel; when its panels are warm ink
   and these were cool neutral grey (#202020 panel, #2b2b2b card, #444 border,
   #a0a0a0 copy) the hand-off read as two different products. The legacy token
   NAMES are kept — ~80 rules reference them — and only their values move onto
   the warm-ink ramp. Keep --booking-panel/-lit/-edge in step with booking. */
:root {
    --brand-yellow: #FFD900; --brand-black: #000000; --brand-white: #FFFFFF;
    --meat-color: #E74C3C; --vega-color: #2ECC71;
    --success-bg: #27ae60; --error-bg: #c0392b; --ticks-offset-y: 0px;

    /* warm-ink ramp (booking's --ink / --ink-2 / --cream) */
    --dark-bg:      #0f0e0b;  /* page + inset field wells — was #1a1a1a */
    --booking-panel:      #161410;  /* section panels */
    --card-bg:      #241f18;  /* raised cards ON a panel — was #2b2b2b */
    --text-primary: #f4efe3;  /* cream — was #f0f0f0 */
    --text-secondary: rgba(244, 239, 227, 0.75); /* was #a0a0a0 */
    /* Solid, not the rgba hairline booking uses: this token doubles as a FILL
       on .modal-btn--cancel and .switch .slider, where alpha would go muddy. */
    --border-color: #3a3428;  /* was #444444 */

    /* panel elevation — lit top edge instead of a lighter fill */
    --booking-border-panel: rgba(244, 239, 227, 0.2);
    --booking-panel-lit:  rgba(244, 239, 227, 0.055);
    --booking-panel-edge: rgba(244, 239, 227, 0.09);

    /* ÉÉN LETTERTYPE VOOR DE HELE TRECHTER (31-08-2026).
       `--sans` komt van de site-master shell waar deze pagina's op gemonteerd
       zijn; de rest is zijn terugval, letterlijk gelijk aan wat master.css zet.
       Gebruik ALTIJD dit token, nooit een losse familie.

       WAT HIER FOUT GING. Er stond op vier plekken `font-family: 'Poppins'` uit
       de tijd dat belevingen/ nog een losstaande app was: op `body`, op de
       verstuurknop, op de tijdrij en op een `input[type=date]`-regel. De pagina's
       zijn later op de shell gemonteerd (`body.booking-page` → Avenir), maar die
       vier bleven staan en overschreven de shell per element. Resultaat: de
       primaire knop en de starttijd stonden in een ander lettertype dan de rest
       van dezelfde pagina, en het viel niet op omdat je ze nooit naast elkaar in
       één blik ziet.

       Bewaakt door apps/local/tests/lettertype/. */
    --funnel-font: var(--sans, 'AvenirNextCn', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif);
}

/* ── HUISLOOK IN WARME INKT ───────────────────────────────────────────────
   branding/surfaces.css levert het materiaal (glas, ambient, hoeken, hoogtes,
   gloed); de waarden dáár zijn neutraalgrijs en horen bij het beheer en bij
   wbs/. Deze pagina's delen hun warme inkt met de marketingsite, en die inkt
   verliezen is precies wat de opmerking bovenaan dit bestand verbiedt. Dus:
   structuur overnemen, kleur houden.

   GESPIEGELD IN styles.css — de hub laadt dit bestand niet, de vier
   detailpagina's laden styles.css niet. Houd ze in stap. */
body.booking-page {
    --sbv-surface-0: var(--dark-bg);                  /* #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;

    --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);
}
    
    @keyframes glowing-ember {
        0% { box-shadow: 0 0 5px rgba(255, 217, 0, 0.7), 0 0 10px rgba(255, 165, 0, 0.5); }
        50% { box-shadow: 0 0 20px rgba(255, 217, 0, 1), 0 0 35px rgba(231, 76, 60, 0.7), 0 0 5px rgba(255, 255, 255, 0.5); }
        100% { box-shadow: 0 0 5px rgba(255, 217, 0, 0.7), 0 0 10px rgba(255, 165, 0, 0.5); }
    }
    
    /* Base font scale for readability (senior customers) — 2026-07-18.
       SINGLE lever: nearly every belevingen size is rem-based, so raising the
       root font-size scales the WHOLE app proportionally. Change --host-scale
       here — do NOT add per-element font-size overrides (keeps it consistent, no
       specials/!important scattered around). Only loaded on the 4 experience
       pages, so the rest of site/ is untouched. The iOS anti-zoom
       `font-size:16px` on inputs is px (not scaled) so it stays ≥16px.

       --host-scale is the SAME number, published for the shell chrome: these
       pages run inside site_master(), whose header, menu, footer, .ask pill and
       chat panel are px-boxed and rem-typed. Scaling the root scaled their type
       and not their boxes, which is how the chat panel ended up with a 30px
       title in an 18px-padded head and the menu drawer pushed its own footer off
       a 740px-tall phone. master.css §"shell chrome scale" divides this number
       back out, so the chrome renders like it does on site/. Change one, change
       the other — that is why they share a variable. */
    html { scroll-behavior: smooth; --host-scale: 1.25; font-size: calc(100% * var(--host-scale)); }
    * { margin: 0; padding: 0; box-sizing: border-box; }

    #ember-canvas {
        position: fixed;
        bottom: 0;
        left: 0;
        width: 100vw;
        height: 100dvh;
        z-index: 999;
        pointer-events: none;
        transform: translateZ(0);
        backface-visibility: hidden;
        -webkit-backface-visibility: hidden;
        will-change: transform, opacity;
    }
    @media (max-width: 767px) {
        #ember-canvas { bottom: -50px; height: calc(100dvh + 50px); }
    }
    @supports not (height: 100dvh) {
        #ember-canvas { height: 100vh; }
        @media (max-width: 767px) { #ember-canvas { height: calc(100vh + 50px); } }
    }
    @supports not (mask-image: linear-gradient(to top, black, transparent)) {
        #ember-canvas { opacity: 0.6; }
    }

    /* Een vraag van acht woorden past op geen telefoon op één regel. Dat mag
       breken — maar dan zonder los woord op de laatste regel. Korte labels en
       knoppen breken elders sowieso niet (white-space: nowrap daar). */
    .form-group label { text-wrap: pretty; }
    .form-error-msg { color: var(--meat-color); font-size: 0.9em; margin-top: 0.25rem; display: none; }
    .allergies-container { display: grid; gap: 0.5rem; }
    .allergies-container .toggle-wrapper { justify-content: flex-start; }
    /* FORMULIERBEDIENINGEN ERVEN HET LETTERTYPE NIET VANZELF. `<button>`,
       `<input>`, `<select>` en `<textarea>` krijgen van de browser hun eigen
       UA-lettertype en negeren `body`. Gemeten op de geopende trechter stonden er
       22 zulke elementen in ARIAL naast een pagina in Avenir — waaronder de
       "Inklappen"-knoppen, die gewoon in het zicht staan. De losse `font-family:
       inherit` die op sommige velden al stond, dekte precies die velden en verder
       niets; dit dekt de hele soort in één regel.
       Alleen de FAMILIE erven, niet `font` als geheel: dat zou ook de grootte
       terugzetten en daarmee --field-font-size ondermijnen. */
    button, input, select, textarea, optgroup { font-family: inherit; }

    body {
        font-family: var(--funnel-font);
        background:
            linear-gradient(180deg, rgba(255, 217, 0, 0.07) 0, rgba(26, 26, 26, 0) 20rem),
            var(--dark-bg);
        color: var(--text-primary);
        line-height: 1.6;
        font-size: 1em;
    }

    /* Shell-mounted (workshop.php on site_master, body.booking-page): the site
       master shell owns <body> — its chrome (brand lockup, MENU, footer, chat
       pill) must use the site's font, not belevingen's Poppins, and the site's
       warm-ink background, not belevingen's #1a1a1a + yellow wash. Without this
       the standalone body rule above bleeds Poppins into the shell chrome and
       the wordmark loses Bungee (colors.css redefines --brand to the yellow
       COLOR under .sbv-theme-*, colliding with master.css's --brand FONT, so
       .wm's `font-family: var(--brand)` becomes invalid and falls back to the
       inherited body font). Restore both to the master vocabulary here.
       De regel hierboven dat "de twee losstaande BBQ-pagina's .booking-page niet
       dragen" klopt niet meer: alle vier de pagina's die belevingen.css laden
       (workshop, plancha, low & slow, proeverij) zijn inmiddels gemonteerd en
       dragen de klasse. Het lettertype staat daarom nu in één token — zie
       --funnel-font bovenaan — zodat de `body`-regel en deze niet meer uit elkaar
       kúnnen lopen. Kleur en achtergrond blijven hier wel apart, want die
       verschillen wél tussen de shell en de losstaande stand. */
    body.booking-page {
        font-family: var(--funnel-font);
        background:
            linear-gradient(180deg, rgba(255, 217, 0, 0.06) 0, rgba(15, 14, 11, 0) 18rem),
            var(--ink, #0f0e0b);
        color: var(--cream, #f4efe3);
    }
    /* The wordmark font must survive the --brand color/font collision. */
    body.booking-page .brand-lockup .wm {
        font-family: 'Bungee', 'AvenirNextCn', system-ui, sans-serif;
    }
    
    .card {
        /* Gedeelde materiaallaag — zie branding/surfaces.css. De gerechtkaart
           houdt zijn warme vulling; alleen het materiaal (glas + hoek) komt uit
           de tokens, zodat de funnel hetzelfde aanvoelt als de rest. */
        background-color: var(--sbv-surface-2);
        backdrop-filter: var(--sbv-blur);
        -webkit-backdrop-filter: var(--sbv-blur);
        border-radius: var(--sbv-radius-sm);
        overflow: hidden; 
        /* Base border so all dishes are visually separated without feeling too heavy */
        border: 2px solid var(--border-color); 
        position: relative; 
        transition: transform 0.2s ease, border-color 0.3s ease; 
        border-top: 4px solid var(--border-color); /* default neutral, variant rules override color */
    }
    /* top + full border color cues for meat/vega (persist even when not selected) */
    .card.variant-meat { border-top-color: var(--meat-color); border-color: var(--meat-color); }
    .card.variant-vega { border-top-color: var(--vega-color); border-color: var(--vega-color); }
    /* Brand yellow glow for any selected card (variant top border color retained) */
    @keyframes brandGlowPulse {
        0%, 100% { box-shadow: 0 0 0 3px var(--brand-yellow), 0 0 18px 2px rgba(255,217,0,0.45), 0 0 38px 8px rgba(255,217,0,0.18); }
        50% { box-shadow: 0 0 0 3px var(--brand-yellow), 0 0 28px 4px rgba(255,217,0,0.75), 0 0 60px 14px rgba(255,217,0,0.28); }
    }
    @keyframes selectPulse {
        0% { transform: translateY(-5px) scale(1.02); }
        50% { transform: translateY(-5px) scale(1.06); }
        100% { transform: translateY(-5px) scale(1.02); }
    }
    .card.is-selected { 
        transform: translateY(-5px) scale(1.02);
        animation: brandGlowPulse 3.4s ease-in-out infinite;
        will-change: box-shadow, transform;
    }
    .card.just-selected {
        animation: selectPulse 0.4s ease-out, brandGlowPulse 3.4s ease-in-out infinite 0.4s;
    }
    @media (prefers-reduced-motion: reduce) {
        /* Gedeelde gloedwoordenschat: geel = dit is jouw keuze. Zie
           branding/surfaces.css — de bewegende variant hierboven is dezelfde
           betekenis, alleen als puls. */
        .card.is-selected { animation: none; box-shadow: var(--sbv-glow-brand); }
        .card.just-selected { animation: none; }
    }
    .card.variant-meat.is-selected { border-top-color: var(--meat-color); border-color: var(--meat-color); border-top-width: 6px; }
    .card.variant-vega.is-selected { border-top-color: var(--vega-color); border-color: var(--vega-color); border-top-width: 6px; }

    /* ── BIJBETAALD GERECHT (.is-extra) ──────────────────────────────────────
       WAT DEZE KLASSE ÉCHT BETEKENT — en dat is niet wat de naam suggereert.
       Het arrangement heeft per categorie een vast portiebudget. Vink je er meer
       aan dan dat budget draagt, dan opent er een modal ("kies aantal extra
       porties, €6,95 per portie"). Annuleren zet het vinkje weer uit; op
       Toevoegen krijgt het gerecht `is-extra` MÉT porties. `is-extra` is dus
       "hier betaal je voor", niet "deze doet niet mee": zo'n gerecht ligt gewoon
       op de BBQ en staat gewoon op de rekening.
       (Met 0 porties bestaat de klasse alleen zolang de modal openstaat, en die
       ligt er dan overheen — daar valt niets aan te tonen.)

       TOT 31-08-2026 DEED DIE KLASSE NIETS. Er stond geen enkele CSS-regel voor,
       in geen van de bestanden. Gevolg: een gerecht met toeslag was op de kaart
       niet te onderscheiden van een gerecht dat in de prijs zit. De gast ziet het
       verschil dan pas terug in het prijsoverzicht, en dat is precies het moment
       waarop je het niet wilt ontdekken.

       Bewust GEEN dempen en GEEN streepjesrand: dat leest als "telt niet mee",
       en het tegendeel is waar. Het gerecht blijft er volwaardig uitzien — het
       verschil zit in het pilletje eronder, dat er "· extra" bij draagt. En dus
       ook geen rood: dit is geen fout, het is een keuze die geld kost. */
    .card.is-selected.is-extra .alloc-label {
        /* De pil draagt hier meer dan één woord. De `text-transform: capitalize`
           verderop is gekozen toen het label altijd uit één woord bestond; op
           "Vlees: 4x · extra" maakt hij er "Vlees: 4x · Extra" van, en dat is geen
           sentence case. De JS levert de tekst hier al correct aan. */
        text-transform: none;
        letter-spacing: 0;
        /* Merkgeel, want dit is een keuze van de gast met een prijs eraan — niet
           de vlees/vega-kleur, die iets heel anders betekent. #0f0e0b op #FFD900
           meet 15,2:1. */
        background: var(--brand-yellow);
        color: var(--dark-bg);
    }
    
    .card-body { padding: 1rem; display: flex; flex-direction: column; flex-grow: 1; }
    .card-container { display: grid; gap: 1.5rem; grid-template-columns: 1fr; }
    .card-content { display: flex; flex-direction: column; height: 100%; border-radius: 8px; }
    .card-description { font-size: 0.75em; color: var(--text-secondary); flex-grow: 1; margin-bottom: 1rem; }
    .card-image { width: 100%; height: 180px; object-fit: cover; background-color: #333; }
    .card-title { font-size: 1em; color: var(--text-primary); font-weight: 600; margin-bottom: 0.5rem; }

    /* Removed variant border overrides for uniform brand glow */
    
    .card.is-vega .main-vega-toggle-wrapper .toggle-label { color: var(--vega-color); }
    .card.is-vega .partial-vega-controls { display: block; }
    .card.is-vega.is-partial .vega-quantity-selector { display: flex; }
    /* Allocation UI */
    .allocation-wrapper { margin-top: .75rem; display: none; flex-direction: column; }
    .card.is-selected .allocation-wrapper { display: flex; }
    .allocation-row { display: flex; align-items: center; justify-content: center; gap: .75rem; width:100%; }
    .allocation-row-centered { justify-content:center !important; }
    /* Sentence case, enforced in CSS on purpose.
       `text-transform: lowercase` used to pin this pill lowercase against the
       repo rule. The copy itself is written lowercase in TWO places — the PHP
       that renders it (`vlees: 0x`) and the JS that rewrites it on every
       portion change (js/experience_core.js -> updateDishPortionBadges) — so a
       string fix in one of them would be undone by the other on the first
       click; the transform is the only place that covers both.
       `capitalize`, not `::first-letter`: the pill is an inline-FLEX container
       and ::first-letter does not apply to flex containers (verified in
       Chrome — the rule renders as a no-op). The label is always exactly one
       word here (`vlees` / `vega`, workshop_render_helpers.php +
       bbq_lowandslow.php), so capitalize and sentence case are the same thing;
       the count that follows starts with a digit and is left alone.
       A pill NEVER wraps: nowrap + measured at 1 rendered line. */
    .alloc-label { text-transform: capitalize; font-size: .70rem; letter-spacing: .08em; font-weight:700; padding: .35rem .65rem; border-radius: 999px; background: #555; color:#fff; display:inline-flex; align-items:center; justify-content:center; white-space: nowrap; }
    /* Dark ink on the brand red, matching the vega pill beside it.
       White on #E74C3C measures 3.82:1 — below AA (4.5:1) and rendered at 14px
       bold, which is not large text, so there is no exemption. Flipping the ink
       instead of the fill keeps the brand red exactly as it is on the card
       border it belongs to: #000 on #E74C3C measures 5.50:1. */
    .alloc-label.alloc-meat { background: var(--meat-color); color:#000; }
    .alloc-label.alloc-vega { background: var(--vega-color); color:#000; }
    .hidden-portion-input { position:absolute; left:-9999px; width:1px; height:1px; opacity:0; pointer-events:none; }
    .alloc-input { width:70px; padding:.45rem 0; background:var(--dark-bg); border:1px solid var(--border-color); color:var(--text-primary); border-radius:6px; font-size:.9rem; font-family:inherit; text-align:center; display:inline-flex; align-items:center; justify-content:center; }
    /* Remove native spinners to avoid visual left bias */
    .alloc-input::-webkit-outer-spin-button,
    .alloc-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
    .alloc-input[type=number] { -moz-appearance: textfield; appearance: textfield; }
    .alloc-input:disabled { opacity:.4; }
    .distribution-mismatch { color: var(--brand-yellow); font-size:.75rem; margin-top:.25rem; text-align:center; }
    .vega-surcharge-line { font-size:.85rem; color: var(--vega-color); text-align:center; margin-top:.5rem; }
    #confirmation-wrapper { display: none; max-width: 600px; margin: 1.5rem auto 0; padding: 1rem; border: 1px dashed var(--brand-yellow); border-radius: 4px; }
    .container { max-width: 1200px; margin: 0 auto; padding: 0 1rem 2rem 1rem; position: relative; z-index: 2; }
    body { position: relative; z-index: 1; }
    .dish-select-checkbox { position: absolute; opacity: 0; width: 100%; height: 100%; cursor: pointer; z-index: 10; }
    .error-message { background-color: var(--error-bg); }
    .error-message h2 { margin-bottom: 0.5rem; font-size: 1.2rem; }
    /* ONE token owns field typography, so a control can never drift away from
       its own label. They had: label 0.9rem AvenirNextCn / input 1rem Poppins —
       a different family AND size, and Poppins' wider, rounder shapes made every
       input read as noticeably larger than the label above it. Both now inherit
       the page face and this size; only weight distinguishes them. */
    .form-group { --field-font-size: 0.9rem; }

    .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 600; }
    .form-group textarea, .form-group input[type="text"], .form-group input[type="email"], .form-group input[type="number"], .form-group input[type="tel"] { width: 100%; background-color: var(--dark-bg); border: 1px solid var(--border-color); border-radius: 4px; padding: 0.75rem; color: var(--text-primary); font-family: inherit; font-size: var(--field-font-size); line-height: 1.5; box-sizing: border-box; height: auto; }
    
    /* Autofill wordt verderop afgehandeld, bij de veldstatussen — zoek op
       ":-webkit-autofill". Het stond hier, en het overschreef met `!important`
       de hele `border`, waardoor een automatisch ingevuld veld zijn groene
       bevestiging kwijtraakte. */

    /* Hier stonden vijf regels voor `input[type="date"]`, met het label "legacy —
       kept for reference". Ze zijn weg: een NATIEF datumveld mag in deze repo niet
       bestaan (het bepaalt zijn weergave uit de browserlocale en toont dan
       09/03/2026 in plaats van dd-mm-jjjj — zie spec FR-110b), er staat een test
       onder die verbod (apps/local/tests/datumweergave/) en er is dus per definitie
       geen enkel element dat deze regels ooit raakt. Ze droegen wél een vierde
       `font-family: 'Poppins'` mee, wat het opsporen van de lettertype-mengeling
       onnodig verwarrend maakte: dood gewicht dat eruitziet als een beslissing. */


    /* Unified card-style sections — matches booking funnel visual language */
    /* Section panel — booking's .card treatment, 1:1. Lit from above rather
       than filled lighter; the fixed 200px falloff keeps a short panel and a
       tall one equally bright at the top edge where the eye enters. */
    .form-section,
    .intro-section,
    .menu-section,
    .summary-panel {
        background:
            linear-gradient(180deg, var(--booking-panel-lit), rgba(244, 239, 227, 0) 200px),
            var(--booking-panel);
        border: 1px solid var(--booking-border-panel);
        border-radius: 16px; /* was 8px — booking's panel radius */
        padding: 1.5rem;
        margin: 1.5rem 0;
        position: relative;
        box-shadow:
            inset 0 1px 0 var(--booking-panel-edge),
            0 28px 64px -30px #000;
    }

    .menu-section { margin-bottom: 3rem; }

    /* Section heading defaults */
    .form-section h2,
    .intro-section h2,
    .menu-section h2,
    .summary-panel h2 {
        border: none;
        font-size: 1.5rem;
        color: #ffffff;
        font-weight: 700;
        margin-bottom: 1rem;
    }
    
    .hidden-data { display: none; }
    /* `.info-message` dropped 2026-08-12: the four experience pages used it only for
       the booking confirmation, which now renders as .booking-confirmation below.
       (login.php and huurbrouwers/ carry their OWN .info-message rules inline —
       those are unrelated and unaffected.) `.error-message` is still in use. */
    .error-message { padding: 1.5rem; border-radius: 8px; margin: 2rem auto; color: var(--brand-white); text-align: center; max-width: 1200px; }

    /* ── Booking confirmation (partials/booking_confirmation.php) ──────────────
       Replaces the full-width green .info-message slab. Green is kept as an
       ACCENT (the mark, the rule under the title) instead of a 1200px fill: a
       solid #27ae60 band on a warm-ink page reads as a system alert, not as the
       end of a warm booking flow, and it forced white running text where the
       rest of the funnel uses cream on ink. Card sits on the page surface like
       every other panel, so the confirmation looks part of the same product. */
    .booking-confirmation {
        max-width: 640px;
        margin: 3rem auto;
        padding: 2.5rem 2rem 2rem;
        background: var(--booking-panel);
        border: 1px solid var(--border-color);
        border-top: 4px solid var(--success-bg);
        border-radius: 12px;
        text-align: center;
        color: var(--text-primary);
    }
    .booking-confirmation-mark {
        width: 56px;
        height: 56px;
        margin: 0 auto 1.25rem;
        border-radius: 50%;
        background: var(--success-bg);
        color: var(--brand-white);
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .booking-confirmation-mark .sbv-icon { width: 30px; height: 30px; stroke-width: 3; }
    .booking-confirmation h2 {
        font-size: 1.6rem;
        font-weight: 700;
        margin: 0 0 0.5rem;
        color: var(--text-primary);
        text-wrap: balance;
    }
    .booking-confirmation-lead {
        margin: 0 auto 1.75rem;
        color: var(--text-secondary);
        font-size: 1rem;
        line-height: 1.55;
        text-wrap: pretty;
    }
    .booking-confirmation-lead strong { color: var(--text-primary); }

    /* The code: a field the guest reads back over the phone, so it gets tabular
       spacing and a fill of its own rather than bold text inside a sentence. */
    .booking-confirmation-code {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: center;
        gap: 0.5rem 0.9rem;
        margin: 0 0 1.75rem;
        padding: 1rem 1.25rem;
        background: var(--dark-bg);
        border: 1px dashed var(--border-color);
        border-radius: 10px;
    }
    .booking-confirmation-code-label {
        flex: 1 0 100%;
        font-size: 0.72rem;
        letter-spacing: 0.09em;
        text-transform: uppercase;
        font-weight: 700;
        color: var(--text-secondary);
    }
    .booking-confirmation-code-value {
        /* Fluid, and NEVER wrapped: a reference code broken as "SBV-BBQ- / 331CC55B"
           invites the guest to read back half of it. The clamp keeps all 16
           characters on one line down to the narrowest phone. */
        font-size: clamp(1rem, 4.4vw, 1.35rem);
        white-space: nowrap;
        font-weight: 800;
        letter-spacing: 0.06em;
        color: var(--brand-yellow);
        /* selectable by hand wherever the copy button cannot be offered */
        user-select: all;
    }
    .booking-confirmation-copy {
        display: inline-flex;
        align-items: center;
        gap: 0.4rem;
        font: inherit;
        font-size: 0.8rem;
        font-weight: 600;
        white-space: nowrap;
        padding: 0.4rem 0.75rem;
        border-radius: 999px;
        border: 1px solid var(--border-color);
        background: var(--card-bg);
        color: var(--text-primary);
        cursor: pointer;
        transition: border-color 0.2s ease, color 0.2s ease;
    }
    .booking-confirmation-copy:hover,
    .booking-confirmation-copy:focus-visible { border-color: var(--brand-yellow); color: var(--brand-yellow); }
    .booking-confirmation-copy.is-copied { border-color: var(--success-bg); color: #6ee7a8; }

    /* Amber, not green: this is the one line on the page that must not be read as
       "all done". Light amber on a low-alpha amber tint over ink clears AA. */
    .booking-confirmation-notice {
        display: flex;
        gap: 0.7rem;
        text-align: left;
        margin: 0 0 1.75rem;
        padding: 0.9rem 1.1rem;
        border-radius: 10px;
        background: rgba(252, 211, 77, 0.10);
        border: 1px solid rgba(252, 211, 77, 0.45);
        color: #fcd34d;
    }
    .booking-confirmation-notice .sbv-icon { flex: 0 0 auto; margin-top: 0.15rem; }
    .booking-confirmation-notice p { margin: 0; font-size: 0.9rem; line-height: 1.5; text-wrap: pretty; }
    .booking-confirmation-notice strong { color: var(--brand-yellow); }

    .booking-confirmation-steps { text-align: left; margin: 0 0 1.75rem; }
    .booking-confirmation-steps h3 {
        font-size: 0.78rem;
        letter-spacing: 0.09em;
        text-transform: uppercase;
        font-weight: 700;
        color: var(--text-secondary);
        margin: 0 0 0.75rem;
    }
    .booking-confirmation-steps ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
    .booking-confirmation-steps li {
        display: flex;
        align-items: flex-start;
        gap: 0.7rem;
        font-size: 0.92rem;
        line-height: 1.5;
        color: var(--text-secondary);
    }
    .booking-confirmation-steps li .sbv-icon { flex: 0 0 auto; margin-top: 0.2rem; color: var(--brand-yellow); }

    .booking-confirmation-extra {
        margin: 0 0 1.75rem;
        padding-top: 1.25rem;
        border-top: 1px solid var(--border-color);
        color: var(--text-secondary);
        font-size: 0.92rem;
        line-height: 1.55;
    }
    .booking-confirmation-extra strong { color: var(--text-primary); }

    .booking-confirmation-actions {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: center;
        gap: 0.75rem 1.5rem;
        padding-top: 1.5rem;
        border-top: 1px solid var(--border-color);
    }
    .booking-confirmation-btn {
        display: inline-flex;
        align-items: center;
        gap: 0.5rem;
        white-space: nowrap;
        padding: 0.7rem 1.4rem;
        border-radius: 999px;
        background: var(--brand-yellow);
        color: var(--sbv-black, #111827);
        font-weight: 700;
        text-decoration: none;
        transition: filter 0.2s ease;
    }
    .booking-confirmation-btn:hover, .booking-confirmation-btn:focus-visible { filter: brightness(1.08); }
    .booking-confirmation-help {
        display: inline-flex;
        align-items: center;
        gap: 0.45rem;
        white-space: nowrap;
        color: var(--text-secondary);
        font-size: 0.9rem;
        text-decoration: none;
    }
    .booking-confirmation-help:hover, .booking-confirmation-help:focus-visible { color: var(--text-primary); }
    .booking-confirmation-help strong { color: var(--brand-yellow); }

    @media (max-width: 520px) {
        .booking-confirmation { margin: 1.5rem auto; padding: 2rem 1.25rem 1.5rem; border-radius: 10px; }
        .booking-confirmation h2 { font-size: 1.35rem; }
        /* Stack the actions rather than let the button label wrap. */
        .booking-confirmation-actions { flex-direction: column; }
        .booking-confirmation-btn { width: 100%; justify-content: center; }
    }
    .intro-header { text-align: center; padding: 2rem 0; }
    .intro-header h1 { color: var(--brand-white); font-size: 2rem; font-weight: 700; margin-bottom: -0.5rem; }
    .intro-header p { max-width: 800px; margin: 0 auto; color: var(--text-secondary); font-size: 1rem; }
    /* Variant visibility toggle uses a compact pill style (not tied to intro-tag sizing) */
    .variant-visibility-toggle {
        font-size: 0.70rem;
        letter-spacing: 0.09em;
        text-transform: uppercase;
        font-weight: 700;
        /* "TOON VEGA" is een label, geen zin: in de smalle <h2> van een telefoon
           brak hij in tweeën en zakte de knop naar 24px hoog. Nooit afbreken, en
           niet laten krimpen door de kop ernaast. */
        white-space: nowrap;
        flex: none;
        padding: 5px 10px;
        border-radius: 8px;
        line-height: 1;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        box-sizing: border-box;
        height: auto;
        vertical-align: middle;
        margin: 0;
        position: relative;
    }

    /* Step-kicker system — clean pill matching booking funnel */
    .intro-tag {
        display: inline-block;
        font-size: 0.78rem;
        font-weight: 800;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: var(--brand-yellow);
        background: transparent;
        padding: 0;
        border-radius: 0;
        min-height: 0;
        box-shadow: none;
        line-height: 1.2;
        vertical-align: middle;
        margin: 0;
        position: relative;
    }
    /* When intro-tag is inside an h2, bump font size of the h2 up */
    h2 .intro-tag {
        display: block;
        margin-bottom: 0.3rem;
        font-size: 0.78rem;
    }

    /* ==== COLLAPSIBLE SECTION SYSTEM ==== */
    .collapse-toggle {
        background: none;
        border: none;
        color: var(--text-secondary);
        cursor: pointer;
        padding: 0.25rem 0.5rem;
        display: inline-flex;
        align-items: center;
        gap: 0.5rem;
        font-size: 0.9rem;
        transition: color 0.2s ease;
    }
    .collapse-toggle:hover { color: var(--brand-yellow); }
    .collapse-toggle .sbv-icon { transition: transform 0.3s ease; }
    .collapse-toggle[aria-expanded="true"] .sbv-icon { transform: rotate(180deg); }
    .hidden-until-unlocked { display: none !important; }
    .collapsible-body { 
        max-height: 5000px; 
        overflow: visible; 
        opacity: 1;
        transition: max-height 0.4s ease, opacity 0.3s ease; 
    }
    .collapsible-body.is-collapsed { 
        max-height: 0; 
        overflow: hidden; 
        opacity: 0; 
    }
    .collapsible-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 1rem;
    }
    /* ==== END COLLAPSIBLE SYSTEM ==== */
    
    
    /* When inside section header ensure no bottom margin offset */
    .section-header .intro-tag { margin-bottom:0 !important; }
    .intro-section h2 {
        font-size: 1.75rem;
        margin-bottom: 0.5rem;
    }
    .intro-lead {
        color: var(--text-secondary);
        font-size: 1.05rem;
        line-height: 1.8;
    }
    .intro-lead b { color: var(--brand-yellow); font-weight: 700; }
    /* Policy note styling */
    .policy-note {
        margin-top: 1rem;
        background: rgba(255,255,255,0.03);
        border-left: 4px solid var(--brand-yellow);
        padding: 0.75rem 1rem;
        color: var(--text-secondary);
        font-size: 0.95rem;
        border-radius: 4px;
    }
    @media (min-width: 768px) {
        .intro-section h2 { font-size: 2rem; }
        .intro-lead { font-size: 1.1rem; }
    }
    .main-vega-toggle-wrapper { margin-top: auto; }
    .modal-btn { border: none; padding: 0.7rem 1.5rem; border-radius: 4px; font-weight: 600; cursor: pointer; transition: filter 160ms ease, transform 160ms ease, box-shadow 160ms ease; }
    .modal-btn--agree { background-color: var(--brand-yellow); color: var(--brand-black); }
    .modal-btn--cancel { background-color: var(--border-color); color: var(--text-primary); }
    /* Modal buttons had no hover at all — the confirm/cancel pair is exactly
       where the guest needs to feel which one they are about to press. */
    .modal-btn:hover,
    .modal-btn:focus-visible { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35); }
    .modal-btn--agree:hover, .modal-btn--agree:focus-visible { filter: brightness(1.08); }
    .modal-btn--cancel:hover, .modal-btn--cancel:focus-visible { filter: brightness(1.35); }
    .modal-buttons { display: flex; justify-content: center; gap: 1rem; margin-top: 1.5rem; }
    /* 560, not 450: this shell also hosts the "extra porties" slider, and 450 minus
       2x3rem padding minus the value badge left the rail ~250px wide — its tick dots
       read as a cramped dotted line. The wider shell also keeps the modal's own
       message on one line instead of breaking it mid-sentence. */
    .modal-content { background-color: var(--card-bg); color: var(--text-primary); padding: 2rem 3rem; border-radius: 8px; text-align: center; border-top: 4px solid var(--brand-yellow); max-width: 90%; width: 560px; }
    /* A full sentence may legitimately need two lines here; `pretty` keeps the break
       from stranding a couple of words on their own line. */
    .modal-content p { font-size: 1.1rem; margin-bottom: 1.5rem; text-wrap: pretty; }
    .modal-popup { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); display: none; align-items: center; justify-content: center; z-index: 1000; transition: opacity 0.3s ease; }
    .modal-popup.is-visible { display: flex; }
    /* Header: sticky left-aligned — matches booking funnel */
    .page-header {
        min-height: 76px;
        background: #ffffff;
        border-bottom: 1px solid rgba(26, 26, 26, 0.08);
        padding: 0.9rem clamp(1rem, 3vw, 2rem);
        display: flex;
        align-items: center;
        justify-content: flex-start;
        gap: 1rem;
        position: sticky;
        top: 0;
        z-index: 20;
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
    }
    .page-header .header-logo {
        width: min(360px, 58vw);
        height: auto;
        display: block;
    }

    @media (max-width: 700px) {
        .page-header {
            position: static;
            padding: 0.85rem clamp(0.75rem, 3vw, 1.5rem);
        }
        .page-header .header-logo {
            width: min(260px, 76vw);
        }
    }

    @media (max-width: 480px) {
        .page-header .header-logo {
            max-width: 85%;
            max-height: 40px;
        }
        
        /* Fix form sections and inputs on mobile */
        .form-section {
            padding: 1rem;
            margin-left: -0.5rem;
            margin-right: -0.5rem;
            border-radius: 4px;
        }
        
        .form-section::after {
            left: 1rem;
            right: 1rem;
        }
        
        .container {
            padding: 0 0.5rem 2rem 0.5rem;
        }
        
        /* Ensure all inputs fit properly */
        .form-group input[type="text"],
        .form-group input[type="email"],
        .form-group input[type="number"],
        .form-group input[type="tel"],
        .form-group textarea {
            width: 100%;
            box-sizing: border-box;
            padding: 0.65rem;
        }

        /* iOS zooms the viewport when a focused control is under 16px, so the
           token — not the input alone — steps up here. Overriding only the input
           would re-open the label/control mismatch on phones, in the other
           direction. */
        .form-group { --field-font-size: 16px; }
        
        /* Reduce section header sizes */
        .section-header h2 {
            font-size: 1.2rem;
        }
        
        .intro-tag {
            font-size: 0.8rem;
            padding: 10px 14px;
            min-height: 42px;
            border-radius: 10px;
        }
        
        /* VanillaJS Datepicker calendar mobile optimization */
        .datepicker {
            max-width: calc(100vw - 1rem) !important;
            left: 0.5rem !important;
            right: 0.5rem !important;
        }
        
        .datepicker-picker {
            max-width: 100%;
        }
        
        .datepicker-grid {
            max-width: 100%;
        }
        
        /* Ensure datepicker input stays full width on mobile */
        .form-group .datepicker-input,
        #booking_date_input.datepicker-input {
            width: 100% !important;
            display: block !important;
            box-sizing: border-box;
        }
    }
    
    /* Date chosen via the booking calendar: shown as locked/confirmed, not re-picked here. */
    #booking_date_input.booking-date-locked {
        cursor: default;
        background: rgba(255, 217, 0, 0.06);
        border-color: rgba(255, 217, 0, 0.45);
    }
    .booking-date-locked-note {
        display: flex;
        align-items: center;
        gap: 0.4rem;
        margin-top: 0.5rem;
        font-size: 0.9rem;
        color: var(--text-secondary);
    }
    .booking-date-locked-note .sbv-icon { flex: 0 0 auto; }
    .booking-date-locked-note a {
        color: #FFD900;
        font-weight: 600;
        text-decoration: underline;
    }
    .booking-date-locked-note a:hover { color: #ffffff; }

    .partial-vega-controls { display: none; margin-bottom: 1rem; }
    .section-counter { font-size: 1rem; font-weight: 400; color: var(--text-secondary); white-space: nowrap; }
    .section-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid rgba(255,217,0,0.6); padding-bottom: 0.5rem; margin-bottom: 2rem; }
    .section-header h2 { display:flex; align-items:center; font-size: 1.5rem; color: var(--text-primary); font-weight: 600; border: none; margin: 0; padding: 0; line-height:1.2; }
    .section-header h2 > .variant-visibility-toggle { padding:5px 10px; line-height:1; }
    .section-guidance { font-size: .85rem; background: rgba(255,255,255,0.06); padding: .35rem .6rem; border-radius: 4px; color: var(--text-secondary); letter-spacing:.5px; }
    /* "Je stelt één menu samen voor de hele groep." Staat vóór het eerste vinkje,
       omdat gasten de gerechtenkaart anders als tapas lezen — zie de uitleg bij dit
       blok in bbq_lowandslow.php. Merkgeel kader: dit is geen sfeertekst maar een
       spelregel, en hij moet gelezen zijn voordat er iets aangevinkt wordt.
       `color` staat expliciet bij de achtergrond — huisregel, bewaakt door
       apps/local/tests/contrast/. */
    .dish-scope-note {
        display: flex;
        align-items: flex-start;
        gap: .6rem;
        margin: 0 0 1.75rem;
        padding: .85rem 1rem;
        background: rgba(255, 217, 0, 0.10);
        border-left: 4px solid var(--brand-yellow);
        border-radius: 4px;
        color: var(--text-primary);
        font-size: .95rem;
        line-height: 1.5;
    }
    .dish-scope-note .sbv-icon { flex: 0 0 auto; margin-top: .15rem; color: var(--brand-yellow); }
    /* Dieetwensen: bewust RUSTIGER dan de groepsregel erboven. Het is een uitnodiging
       tot overleg, geen spelregel — merkgeel zou het gewicht geven van iets dat we
       aanbieden, en dat doen we juist niet. Zie de uitleg bij dit blok in
       bbq_lowandslow.php. */
    .dish-diet-note {
        display: flex;
        align-items: flex-start;
        gap: .6rem;
        margin: -1rem 0 1.75rem;
        padding: .7rem 1rem;
        border-left: 2px solid var(--sbv-surface-edge, rgba(244, 239, 227, 0.14));
        color: var(--text-secondary);
        font-size: .88rem;
        line-height: 1.5;
    }
    .dish-diet-note strong { color: var(--text-primary); }
    .dish-diet-note .sbv-icon { flex: 0 0 auto; margin-top: .15rem; color: var(--text-secondary); }
    .vega-discovery-hint {
        display: none;
        background: rgba(76, 175, 80, 0.1);
        border-left: 3px solid var(--vega-color);
        padding: 0.75rem 1rem;
        margin: 1rem 0;
        border-radius: 4px;
        font-size: 0.9rem;
        color: var(--text-secondary);
        cursor: pointer;
        transition: all 0.2s ease;
        grid-column: 1 / -1; /* Span across all grid columns */
    }
    .vega-discovery-hint:hover {
        background: rgba(76, 175, 80, 0.15);
        transform: translateX(4px);
    }
    .vega-discovery-hint strong {
        color: var(--vega-color);
    }
    
    /* Package Tier Cards */
    .package-tiers {
        display: grid;
        /* 200px, WAS 280px (iPad-melding 15-09-2026: "brons > zilver, dan enter en goud").
           Een ladder van drie hoort op één rij te staan of gestapeld te zijn — nooit 2 + 1, want
           dan leest Goud als een losse aanbieding onder de andere twee in plaats van als de
           bovenste trede. Onder 769px stapelt de mediaregel verderop hem al tot één kolom; met
           280px als minimum paste er tussen 769 en ~800px maar twee naast elkaar, precies de
           breedte van een iPad. Bij 769px is er ruim 690px werkbreedte: 3 × 200 + 2 × 24 gap
           past, 3 × 280 niet. Verhoog dit getal niet zonder opnieuw te meten. */
        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
        gap: 1.5rem;
        margin: 2rem 0;
        /* Horizontal breathing room so the hover lift/scale(1.05) of a card
           (esp. .package-recommended) isn't clipped by the collapsible-body's
           overflow:hidden edge. Vertical padding lets the -4px lift + shadow
           show too. */
        padding: 0.75rem 1rem;
    }
    .package-card {
        background: rgba(255, 255, 255, 0.03);
        border: 2px solid rgba(255, 255, 255, 0.1);
        border-radius: 12px;
        padding: 2rem 1.5rem;
        text-align: center;
        transition: all 0.3s ease;
        position: relative;
        cursor: pointer;
        display: flex;
        flex-direction: column;
    }
    /* The card is now reachable by keyboard (role=button + tabindex, see
       partials/package_card.php), so focus has to be VISIBLE — the tier cards are
       the only way to choose a package, and an invisible focus ring on them means
       a keyboard user is choosing blind. Yellow, offset outward so it reads
       against the card's own 2px border and the recommended card's scale. */
    .package-card:focus-visible {
        outline: 3px solid var(--brand-yellow);
        outline-offset: 3px;
    }
    /* Bronze tint for Classic tier */
    .package-card[data-tier="classic"] {
        background: rgba(205, 127, 50, 0.16);
        border-color: rgba(205, 127, 50, 0.4);
    }
    /* Bronze tint for Workshop bronze tier */
    .package-card[data-workshop-tier="bronze"] {
        background: rgba(205, 127, 50, 0.16);
        border-color: rgba(205, 127, 50, 0.4);
    }
    /* Silver tint for Premium tier */
    .package-card[data-tier="premium"] {
        background: rgba(192, 192, 192, 0.16);
        border-color: rgba(192, 192, 192, 0.5);
    }
    /* Silver tint for Workshop silver tier */
    .package-card[data-workshop-tier="silver"] {
        background: rgba(192, 192, 192, 0.16);
        border-color: rgba(192, 192, 192, 0.5);
    }
    /* Gold tint for Deluxe tier */
    .package-card[data-tier="deluxe"] {
        background: rgba(255, 215, 0, 0.16);
        border-color: rgba(255, 215, 0, 0.4);
    }
    /* Gold tint for Workshop gold tier */
    .package-card[data-workshop-tier="gold"] {
        background: rgba(255, 215, 0, 0.16);
        border-color: rgba(255, 215, 0, 0.4);
    }
    .package-card:hover {
        transform: translateY(-4px);
        border-color: var(--brand-yellow);
        box-shadow: 0 8px 24px rgba(255, 217, 0, 0.2);
    }
    .package-card.selected {
        border-color: var(--brand-yellow);
        background: rgba(255, 217, 0, 0.08);
        box-shadow: inset 0 0 0 3px rgba(255, 217, 0, 0.3);
    }
    /* Combo-only restriction: Premium/Deluxe plancha are not bookable in the
       combi (too long + too much beer). Greyed, not clickable, with a lock note.
       Still shown so the customer sees they exist — just "alleen los te boeken". */
    .package-card--combo-disabled {
        opacity: 0.45;
        filter: grayscale(0.85);
        cursor: not-allowed;
        pointer-events: none; /* also blocks the JS click handler on the card */
    }
    .package-card--combo-disabled:hover {
        transform: none;
        border-color: rgba(255, 255, 255, 0.1);
        box-shadow: none;
    }
    /* Always in the DOM as a grid row (see partials/package_card.php); with no
       note it must render as nothing — its padding/border would otherwise show
       as an empty pill. NOT display:none: the element has to keep occupying its
       grid row, contributing zero height, or cards with a note and cards
       without would fall out of step with each other. */
    .package-combo-disabled-note:empty {
        margin: 0;
        padding: 0;
        border: 0;
        min-height: 0;
        background: none;
        /* The visible note is centred in its row; an empty one must sit flush at
           the row's start so it occupies the track without offsetting itself. */
        align-self: start;
    }

    .package-combo-disabled-note {
        display: inline-flex;
        align-items: center;
        gap: 0.35rem;
        align-self: center;
        margin-bottom: 0.75rem;
        padding: 0.3rem 0.7rem;
        border-radius: 20px;
        background: rgba(255, 255, 255, 0.08);
        border: 1px solid rgba(255, 255, 255, 0.18);
        color: var(--text-primary);
        font-size: 0.72rem;
        font-weight: 700;
        letter-spacing: 0.3px;
        /* the parent card is greyscale-dimmed; keep this readable */
        filter: none;
    }
    .package-recommended:hover {
        transform: scale(1.05) translateY(-4px);
        border-color: var(--brand-yellow);
    }
    /* A header strip across the card, not a chip the width of its own text: the
       tier check overlaps its right end (see .package-check), and a text-width
       chip gave that disc nothing to sit on. Side padding is symmetric so the
       label stays optically centred while keeping clear of the disc, and the
       label never wraps — a tier name breaking over two lines is not allowed. */
    .package-badge {
        display: block;
        width: 100%;
        background: rgba(255, 255, 255, 0.1);
        color: var(--text-secondary);
        padding: 0.4rem 2.25rem;
        border-radius: 20px;
        font-size: 0.75rem;
        font-weight: 700;
        letter-spacing: 0.5px;
        white-space: nowrap;
        /* Spacing moved to .package-card-head — see the note there. */
        transition: all 0.3s ease;
    }
    /* Make badge brand yellow when card is selected */
    .package-card.selected .package-badge {
        background: var(--brand-yellow);
        color: var(--dark-bg);
    }
    .recommended-badge {
        background: var(--brand-yellow);
        color: var(--dark-bg);
        transition: all 0.3s ease;
    }
    .package-name {
        font-size: 1.5rem;
        font-weight: 700;
        color: var(--text-primary);
        margin-bottom: 0.5rem;
    }
    .package-price {
        font-size: 2.5rem;
        font-weight: 800;
        color: var(--brand-yellow);
        margin-bottom: 0.25rem;
    }
    .package-price span {
        font-size: 1rem;
        font-weight: 400;
        color: var(--text-secondary);
    }
    /* Head row: holds the badge and is the box the selected-check is centred
       against. Rendered on every card even when there is no badge.
       The gap below lives HERE, not on .package-badge — a margin on the badge
       itself offsets it inside this flex box, so anything centred on the head
       lands half that margin away from the badge's true middle. */
    .package-card-head {
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        /* The check disc is absolutely positioned against this box and centred
           on it, so an empty head row (the Badge field in the menu-admin is free
           text and can be cleared) would collapse to 0 and hang the disc half
           outside the card. Sits just under the badge's own rendered height, so
           every badged card is unchanged. */
        min-height: 1.9rem;
    }

    .package-card-head:has(.package-badge) {
        margin-bottom: 1rem;
    }

    .package-savings {
        font-size: 0.85rem;
        color: #2ecc71;
        margin-bottom: 1rem;
        font-weight: 600;
    }

    /* The slot is always in the DOM (see partials/package_card.php) so cards
       keep matching rows; with no text it must take up nothing at all. */
    .package-savings:empty {
        margin: 0;
        min-height: 0;
    }
    .package-features {
        list-style: none;
        padding: 0;
        margin: 1.5rem 0;
        text-align: left;
    }
    .package-features li {
        padding: 0.5rem 0;
        color: var(--text-secondary);
        font-size: 0.9rem;
        border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    }
    .package-features li:last-child {
        border-bottom: none;
    }
    .package-features strong {
        color: var(--brand-yellow);
    }
    
    .package-note {
        margin-top: auto;
        padding-top: 1rem;
        font-size: 0.8rem;
        color: var(--sbv-yellow);
        font-style: italic;
        opacity: 0.9;
        text-align: left;
    }
    
    /* The tier check is an element in the head row (see partials/package_card.php
       and the ".package-check" block below), not a card-level ::after glyph. */

    /* ══ Aligned tier comparison — plancha, low & slow, workshop and the
       workshop combo extension. (Proeverij has no tier ladder.)

       Why subgrid: the cards hold different amounts of content, so with each
       card laying itself out independently the name/price/features rows drifted
       apart and the tiers could not be read across. The rows are defined ONCE on
       the container and every card borrows them, so row N sits at the same
       height in all of them, whatever it contains.

       Every card must contribute all six rows — package_card.php therefore
       always emits the combo-note and savings slots, empty when unused. Adding
       a new element to that partial means bumping BOTH numbers below. */
    .package-tiers {
        /* note, head, name, price, savings, features */
        grid-template-rows: auto auto auto auto auto 1fr;
        align-items: stretch;
        /* The 1.5rem gap is meant to separate the CARDS. Once the cards borrow
           these rows it would also be injected between every row inside them
           (5 × 24px per card), which made the tiers look stretched.
           Columns keep the gap; rows are spaced by the elements' own margins. */
        column-gap: 1.5rem;
        row-gap: 0;
    }

    .package-card {
        display: grid;
        grid-template-rows: subgrid;
        grid-row: span 6;
    }

    /* Retire the old card-level check in every variant, including the
       id-scoped workshop one (which out-specifies a plain class reset). */
    .package-card.selected::after,
    .package-recommended.selected::after,
    #workshop-tiers .package-recommended::after,
    #plancha-extension .package-recommended::after { content: none; }

    /* ── The tier check ──────────────────────────────────────────────────
       Anchored to the head, so it is centred on the badge row by definition —
       `top: 1.8rem` from the card top was a hardcoded estimate that could only
       ever be right for one badge height. It sits half ON the full-width badge
       and half past its right end: the mark reads as belonging to that tier's
       header strip rather than floating in the card's corner.

       Rendered in EVERY card and hidden by default (opacity, not display), so
       switching tier can animate — an element that only exists while selected
       has nothing to animate out of, and no path to draw. */
    .package-check {
        position: absolute;
        top: 50%;
        right: -0.75rem;
        transform: translateY(-50%) scale(0.4);
        z-index: 2;
        width: 36px;
        height: 36px;
        border-radius: 50%;
        background: #2ecc71;
        color: #fff;
        display: flex;
        align-items: center;
        justify-content: center;
        box-shadow: 0 2px 8px rgba(46, 204, 113, 0.4);
        opacity: 0;
        pointer-events: none;
        /* the out-state: shrink away when the guest moves to another tier */
        transition: opacity 140ms ease, transform 140ms ease;
    }

    .package-check svg {
        width: 20px;
        height: 20px;
        display: block;
        overflow: visible;
    }

    /* Undrawn until the card is picked. Lucide's check path is ~21.6 units
       long; 26 covers it with margin, so the line starts fully retracted. */
    .package-check svg path {
        stroke-dasharray: 26;
        stroke-dashoffset: 26;
    }

    /* The ring that pulses out of the disc on selection — the "click" beat. */
    .package-check::after {
        content: '';
        position: absolute;
        inset: 0;
        border-radius: 50%;
        border: 2px solid rgba(46, 204, 113, 0.7);
        opacity: 0;
    }

    /* Shown ONLY on .selected — the check means "this is my choice". It must
       never ride the recommended card: an unselected tier wearing a check reads
       as an already-made selection while the flow still requires a tier click
       to proceed (recommendation is the "Aanbevolen" badge's job).
       Adding the class starts the animations from their first frame, so every
       switch replays the sequence: disc pops → ring pulses out → check draws. */
    .package-card.selected .package-check {
        opacity: 1;
        transform: translateY(-50%) scale(1);
        animation: tierCheckPop 420ms ease-out both;
    }

    .package-card.selected .package-check::after {
        animation: tierCheckRing 520ms ease-out both;
    }

    .package-card.selected .package-check svg path {
        /* starts after the disc has landed, so the mark reads as two beats */
        animation: tierCheckDraw 260ms ease-out 170ms both;
    }

    @keyframes tierCheckPop {
        0%   { opacity: 0; transform: translateY(-50%) scale(0.2); }
        45%  { opacity: 1; transform: translateY(-50%) scale(1.18); }
        70%  { transform: translateY(-50%) scale(0.94); }
        100% { opacity: 1; transform: translateY(-50%) scale(1); }
    }

    @keyframes tierCheckRing {
        0%   { opacity: 0.85; transform: scale(1); }
        100% { opacity: 0; transform: scale(1.9); }
    }

    @keyframes tierCheckDraw {
        from { stroke-dashoffset: 26; }
        to   { stroke-dashoffset: 0; }
    }

    /* The check means SELECTION on every page (2026-08-04, reverses the earlier
       recommended-marker meaning on workshop/combo: an unselected tier wearing a
       check read as an already-made choice). Recommendation is the "Aanbevolen"
       badge's job. Only a greyed-out (combo-disabled) card suppresses it — comes
       AFTER the shown-rules above, source order settles the tie. */
    #plancha-extension .package-card--combo-disabled .package-check {
        opacity: 0;
        transform: translateY(-50%) scale(0.4);
        animation: none;
    }

    @media (prefers-reduced-motion: reduce) {
        .package-check,
        .package-check::after,
        .package-card.selected .package-check,
        .package-card.selected .package-check::after,
        .package-card.selected .package-check svg path { animation: none; }
        /* no draw, so the check has to be there in full */
        .package-check svg path { stroke-dashoffset: 0; }
    }
    
    @media (max-width: 768px) {
        .package-tiers {
            grid-template-columns: 1fr;
        }
        /* Gestapeld raakten de kaarten elkaar: gemeten op 390px stond er 4px en
           −4px tussen Brons/Zilver/Goud, dus ze plakten aan elkaar (de −4 is de
           dikkere rand van de gekozen kaart).
           GEEN row-gap gebruiken, hoe voor de hand liggend dat ook lijkt. De
           kaarten lenen de rijen van .package-tiers (zie de regel daar), dus een
           row-gap komt óók tussen elke rij BINNEN elke kaart te staan — precies de
           uitgerekte tiers waar die comment voor waarschuwt. Het bestand zegt zelf
           hoe het wel moet: "rows are spaced by the elements' own margins". */
        .package-card { margin-bottom: 1.5rem; }
        .package-card:last-child { margin-bottom: 0; }
        .package-recommended {
            transform: scale(1);
        }
        .package-recommended:hover {
            transform: translateY(-4px);
        }
    }
    
    /* Chef's favoriet Badge */
    .signature-badge {
        position: absolute;
        top: 12px;
        right: 12px;
        z-index: 10;
        background: linear-gradient(135deg, #8B4513 0%, #D2691E 100%);
        color: white;
        padding: 6px 12px;
        border-radius: 20px;
        font-size: 0.75rem;
        font-weight: 600;
        display: flex;
        align-items: center;
        gap: 6px;
        box-shadow: 0 2px 8px rgba(139, 69, 19, 0.3);
        letter-spacing: 0.3px;
    }
    
    .signature-badge i {
        width: 14px;
        height: 14px;
    }
    
    /* Add subtle amber border + gold glow to signature dishes */
    .card.is-signature {
        border-color: rgba(218, 165, 32, 0.7); /* chef amber */
        border-top-color: rgba(218, 165, 32, 0.9);
        position: relative;
    }
    
    .card.is-signature::before {
        content: '';
        position: absolute;
        top: -2px;
        left: -2px;
        right: -2px;
        bottom: -2px;
        border-radius: 14px;
        background: linear-gradient(135deg, rgba(218, 165, 32, 0.15), rgba(184, 134, 11, 0.1));
        z-index: -1;
        opacity: 0;
        transition: opacity 0.3s;
    }
    
    .card.is-signature:hover::before {
        opacity: 1;
    }
    
    /* Premium dish notification */
    .premium-dish-notification {
        background: linear-gradient(135deg, #FFF9E6 0%, #FFF3CC 100%);
        border: 2px solid var(--brand-yellow);
        border-radius: 8px;
        padding: 12px 16px;
        margin: 16px 0;
        display: flex;
        align-items: center;
        gap: 12px;
        box-shadow: 0 2px 8px rgba(255, 217, 0, 0.2);
        animation: slideInNotification 0.3s ease-out;
        transition: opacity 0.3s ease-out;
    }
    
    @keyframes slideInNotification {
        from {
            opacity: 0;
            transform: translateY(-10px);
        }
        to {
            opacity: 1;
            transform: translateY(0);
        }
    }
    
    .premium-dish-notification .notification-content {
        display: flex;
        align-items: center;
        gap: 10px;
        width: 100%;
    }
    
    .premium-dish-notification i {
        color: var(--brand-yellow);
        flex-shrink: 0;
        width: 20px;
        height: 20px;
    }
    
    .premium-dish-notification span {
        color: #854d0e;
        font-size: 0.9rem;
        line-height: 1.4;
    }
    
    .premium-dish-notification strong {
        color: #713f12;
    }
    
    /* Variant visibility toggle buttons: match height/font of primary category tab (yellow) */
    /* Contrast, not decoration: this pill is 0.70rem bold uppercase, so it needs
       WCAG AA 4.5:1. White on the raw --vega-color (#2ECC71) measured 2.10:1 and
       on --meat-color (#E74C3C) 3.82:1 — both fail, vega badly. The gradients are
       darkened here to 4.7:1 / 5.4:1 while keeping the green/red identity. The
       --meat-color/--vega-color tokens themselves are unchanged: they are used as
       large fills elsewhere (slider track, badges) where they are fine. */
    .variant-visibility-toggle { margin-left:8px; border:none; cursor:pointer; justify-content:center; background:linear-gradient(90deg,#1e8449,#17794a); color:#fff; box-shadow:0 1px 2px rgba(0,0,0,.25), 0 0 0 1px rgba(255,255,255,0.08); transition:.18s background,.18s transform; position:relative; font-size:0.70rem; }
    /* Force identical metrics (in case some earlier rule zeroed padding) - increased by 25% for width stability */
    h2 .variant-visibility-toggle { padding:5px 13px !important; line-height:1 !important; }
    .variant-visibility-toggle.toggle-meat { background:linear-gradient(90deg,#c0392b,#b23122); }
    .variant-visibility-toggle.toggle-active { filter:brightness(1.05); }
    .variant-visibility-toggle:hover { transform:translateY(-1px); }
    .variant-visibility-toggle:active { transform:none; }
    /* Optional guide lines (apply .with-guides on parent to debug alignment) */
    .with-guides .intro-tag, .with-guides .variant-visibility-toggle { outline:1px solid rgba(0,180,255,0.55); outline-offset:0; }
    /* Removed meat/vega colored glow overrides (replaced by brand yellow) */
    .section-guidance strong { color: var(--brand-yellow); }
    .slider-container { display: flex; align-items: center; gap: 1rem; min-height: 50px; position: relative; padding: 0 12px; }
    /* Provide gradient background for meat/vega slider container using --mv-gradient variable */
    .slider-container.both-badges { --mv-gradient: linear-gradient(90deg, var(--meat-color) 0%, var(--meat-color) 50%, var(--vega-color) 50%, var(--vega-color) 100%); }
    /* Slider sits at mid-level; ticks above track, thumb above ticks */
    .slider-container input[type="range"] {
        margin: 0;
        position: relative;
        z-index: 9;
        flex-grow: 1;
        -webkit-appearance: none;
        appearance: none;
        width: 100%;
        height: 8px;
        background: transparent;
        border-radius: 5px;
        outline: none;
        opacity: 1;
        transition: opacity .2s;
    }
    .slider-container input[type="range"]:hover { opacity: 1; }
    /* Focus indicator lives on the THUMB, never as a box around the input.
       Generic focus affordances (the a11y outline rules below, and the
       .form-group input:focus yellow halo) hug the input's full-width 8px box —
       on a slider that paints a SECOND rail whose caps stick out 12px past the
       end dots ("blank space left of the first dot"), showing after every drag
       because focus sticks to the dragged slider until something else is
       touched. Specificity here (0-3-1) must beat *:focus-visible (0-2-0) and
       .form-group input:focus (0-2-1); box-shadow: none also guards against any
       future generic halo re-matching ranges. */
    .slider-container input[type="range"]:focus,
    .slider-container input[type="range"]:focus-visible {
        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); /* same ring as .switch */
    }
    .slider-container input[type="range"]:focus-visible::-moz-range-thumb {
        box-shadow: 0 0 0 3px rgba(255, 217, 0, 0.45);
    }
    
    /* Webkit slider track */
    .slider-container input[type="range"]::-webkit-slider-runnable-track {
        height: 8px;
        background: transparent !important; /* Transparent - faux track provides visual */
        border: 0 !important;
        border-radius: 5px;
        position: relative;
        z-index: 1;
    }
    
    /* Mozilla slider track */
    .slider-container input[type="range"]::-moz-range-track {
        height: 8px;
        background: transparent !important; /* Transparent - faux track provides visual */
        border: 0 !important;
        border-radius: 5px;
    }
    /* Firefox also paints a PROGRESS segment (left of the thumb) in the accent
       colour by default — the track/thumb rules above don't cover it, so it drew
       a coloured bar from the input's left edge (12px left of the first dot) to
       the thumb. Kill it: the faux track is the only rail. */
    .slider-container input[type="range"]::-moz-range-progress {
        height: 8px;
        background: transparent !important;
        border: 0 !important;
    }
    
    /* Webkit slider thumb */
    .slider-container input[type="range"]::-webkit-slider-thumb { 
        -webkit-appearance: none; 
        appearance: none; 
        width: 24px; 
        height: 24px; 
        background-color: var(--brand-yellow); 
        border-radius: 50%; 
        border: 3px solid var(--dark-bg); 
        cursor: ew-resize; 
        margin-top: -8px; 
        position: relative; 
        z-index: 10;
    }
    
    /* Mozilla slider thumb */
    .slider-container input[type="range"]::-moz-range-thumb { 
        width: 24px; 
        height: 24px; 
        background-color: var(--brand-yellow); 
        border-radius: 50%; 
        border: 3px solid var(--dark-bg); 
        cursor: ew-resize; 
        transform: translateY(-8px); 
        position: relative; 
        z-index: 10;
    }
    
    /* Faux track overlay that we position exactly from first to last tick (hides native bar tails) */
    .slider-faux-track {
        position: absolute;
        height: 8px;
        background: rgba(255,255,255,0.22);
        border-radius: 5px;
        z-index: 6; /* above native track (transparent), below ticks (8) */
        pointer-events: none;
        /* Promote to its own GPU layer. Without this the native range input's
           repaint DURING a pointer-drag invalidates this sibling's paint region,
           so the grey rail flickers/blanks WHILE sliding and only re-renders on
           release (the DOM left/width are correct the whole time — it's a
           compositing sync artifact, not a positioning bug). A dedicated layer
           paints independently and stays visible mid-drag. */
        transform: translateZ(0);
        will-change: transform;
        backface-visibility: hidden;
    }
    
    /* Modal slider track - use full width as fallback until JS positions it */
    #extra_quantity_slider_track {
        left: 12px; /* match slider-container padding */
        right: 12px;
        width: calc(100% - 24px);
    }
    /* DOM-based overlay ticks for maximum reliability and contrast */
    .slider-ticks {
        position: absolute;
        left: 0; /* Ticks positioned from container's content-box (after padding) */
        width: 100%; /* Span full container width */
        top: calc(50% - 4px); /* center on 8px rail */
        height: 8px;
        border-radius: 5px;
        pointer-events: none;
        z-index: 8; /* above faux track (6), below thumb (10) */
        /* Own GPU layer — same reason as .slider-faux-track: keep the tick dots
           painted independently of the native input's drag repaints. */
        transform: translateZ(0);
        will-change: transform;
        backface-visibility: hidden;
    }
    /* Tick layer sits above faux track but below the slider thumb for proper visual hierarchy */
    .slider-container .slider-ticks { z-index: 8; }
    /* Meat/vega ticks use same layer */
    #meat_vega_slider_ticks { z-index: 8; }
    .slider-ticks .tick-dot {
        width: 10px;
        height: 10px;
        border-radius: 50%;
        background: #fff;
        border: 2px solid #000;
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
    }
    /* Hide the active tick so only the thumb is visible (prevents concentric rings) */
    .slider-ticks .tick-dot.active { visibility: hidden; }
    .slider-container.both-badges { gap: 1rem; }
    .both-badges .slider-ticks { left: calc(50px + 1rem); right: calc(50px + 1rem); }
    
    /* Unified slider value display badges */
    .dual-badge,
    #slider_value_display,
    #extra-quantity-display,
    #time_extension_display {
        height: 50px; 
        min-width: 50px; 
        width: auto; 
        padding: 0 0.75rem; 
        flex-shrink: 0; 
        display: flex; 
        align-items: center; 
        justify-content: center; 
        background: var(--brand-white); 
        color: var(--brand-black); 
        font-weight: 700; 
        border-radius: 8px; 
        font-size: 0.9rem; 
        line-height: 1; 
        border: 2px solid var(--brand-black); 
        position: relative; 
        z-index: 10; 
        text-align: center; 
        white-space: nowrap; 
        transition: transform 0.2s ease;
    }
    .dual-badge:hover,
    #slider_value_display:hover,
    #extra-quantity-display:hover,
    #time_extension_display:hover {
        transform: scale(1.05);
    }
    
    /* Color-coded badges for meat/vega slider */
    #meat_vega_meat_display {
        background: #c73e3a;
        color: #fff;
        border-color: #c73e3a;
    }
    #meat_vega_vega_display {
        background: #3fb573;
        color: #fff;
        border-color: #3fb573;
    }
    
    /* Time extension display: allow multi-line on mobile */
    #time_extension_display {
        white-space: normal;
        line-height: 1.1;
        flex-direction: column;
        font-size: 0.85rem;
        padding: 0.3rem 0.5rem;
    }
    .submit-btn {
        display: block;
        width: 100%;
        margin: 1.25rem 0 0 0;
        padding: 1rem 1.5rem;
        background: var(--brand-yellow);
        color: var(--brand-black);
        border: 1px solid var(--brand-yellow);
        border-radius: 8px;
        font-size: 1rem;
        font-weight: 900;
        font-family: var(--funnel-font);
        cursor: pointer;
        transition: background 140ms ease, transform 120ms ease;
        min-height: 52px;
    }
    .submit-btn:hover:not(:disabled) {
        background: #eab308;
        border-color: #eab308;
        transform: translateY(-1px);
    }
    /* DISABLED IS STILL READABLE. Measured 07-09-2026 at 2.10:1 — #888 on #555,
       then multiplied down by opacity 0.65 — against a 3:1 floor. On proeverij
       this button is visible from the moment the page loads (the other three
       funnels keep theirs inside a step that is still hidden), so the guest
       arrived at the page and the one thing they are meant to aim at was grey
       mush they could not read.

       The opacity was the real culprit and it is gone: "disabled" is now carried
       entirely by the colours, which is the only way this stays measurable —
       opacity fades the label and its own background together, so no pair of
       colours can be verified while it is in play. Now 7.3:1.

       Guarded by apps/local/tests/contrast/, which since the same date visits
       all four capture pages instead of only the hub. */
    .submit-btn:disabled { background-color: #3a3a3a; border-color: #4a4a4a; color: #cfcfcf; cursor: not-allowed; }
    /* Was max-width:600px with auto side margins, which CENTRED this list inside a
       full-width panel while every other row sat on the left edge — the rows read as
       stray, floating text. One column, one edge. */
    #summary-list { list-style: none; margin-bottom: 1.5rem; }
    #summary-list li { padding: 0.5rem 0; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; font-size: 0.95rem; }
    #summary-list li:last-child { border-bottom: none; }
    #summary-list li span:first-child { flex-grow: 1; padding-right: 1rem; }
    #summary-list li span:last-child { white-space: nowrap; }
    .summary-panel {
        /* Overrides the shared panel rule above — keeps the gold edge that
           marks this as the running total, gains the same lit top edge. */
        background:
            linear-gradient(180deg, var(--booking-panel-lit), rgba(244, 239, 227, 0) 200px),
            var(--booking-panel);
        border: 1px solid rgba(255, 217, 0, 0.35);
        padding: 1.5rem;
        border-radius: 16px;
        margin-top: 0;
        box-shadow:
            inset 0 1px 0 var(--booking-panel-edge),
            0 28px 64px -30px #000;
    }
    .summary-panel h3 { margin-bottom: 1.25rem; font-size: 1.2rem; font-weight: 800; color: #ffffff; }
    .summary-total { margin-top: 1rem; }
    
    /* Pricing breakdown toggle */
    .pricing-section {
        margin-top: 1.5rem;
    }
    .breakdown-toggle {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 0.5rem;
        width: 100%;
        max-width: 600px;
        margin: 1rem auto;
        padding: 0.75rem 1rem;
        background: rgba(255,217,0,0.1);
        border: 1px solid var(--brand-yellow);
        border-radius: 6px;
        color: var(--brand-yellow);
        font-size: 0.95rem;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s ease;
    }
    .breakdown-toggle:hover {
        background: rgba(255,217,0,0.2);
        transform: translateY(-1px);
    }
    .breakdown-toggle:focus {
        outline: 2px solid var(--brand-yellow);
        outline-offset: 2px;
    }
    .breakdown-toggle .toggle-icon {
        transition: transform 0.3s ease;
        font-size: 0.8rem;
    }
    .breakdown-toggle[aria-expanded="true"] .toggle-icon {
        transform: rotate(180deg);
    }
    .pricing-breakdown {
        max-width: 600px;
        margin: 0 auto;
        padding: 1.5rem;
        background: rgba(0,0,0,0.2);
        border-radius: 8px;
        border: 1px solid rgba(255,217,0,0.3);
    }
    .breakdown-section {
        margin-bottom: 1.5rem;
        padding-bottom: 1rem;
        border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    .breakdown-section:last-child {
        border-bottom: none;
        margin-bottom: 0;
    }
    .breakdown-section h4 {
        font-size: 0.9rem;
        text-transform: uppercase;
        letter-spacing: 0.05em;
        color: var(--brand-yellow);
        margin-bottom: 0.75rem;
        font-weight: 600;
    }
    .breakdown-line {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0.5rem 0;
        font-size: 0.95rem;
        color: var(--text-primary);
    }
    .breakdown-line.subtle {
        font-size: 0.85rem;
        color: var(--text-secondary);
    }
    .breakdown-line.total-line {
        font-weight: 700;
        padding-top: 0.75rem;
        margin-top: 0.5rem;
        border-top: 1px solid rgba(255,217,0,0.3);
        color: var(--brand-yellow);
    }
    .breakdown-section.grand-total {
        background: rgba(255,217,0,0.05);
        padding: 1rem;
        border-radius: 6px;
        border: 1px solid rgba(255,217,0,0.3);
    }
    .breakdown-section.grand-total .breakdown-line:first-child {
        font-size: 1.2rem;
        font-weight: 700;
        color: var(--brand-yellow);
    }
    
    /* Timeline visualization */
    .timeline-viz {
        margin: 1rem 0 2.5rem 0;
        padding: 1.25rem;
        background: rgba(0,0,0,0.2);
        border-radius: 8px;
        border: 1px solid rgba(255,217,0,0.3);
        width: 100%;
        box-sizing: border-box;
    }
    .timeline-bar {
        display: flex;
        flex-direction: row;
        height: 50px;
        border-radius: 6px;
        overflow: hidden;
        box-shadow: 0 2px 8px rgba(0,0,0,0.3);
        margin-bottom: 0.75rem;
        width: 100%;
    }
    .timeline-segment {
        display: flex;
        align-items: center;
        justify-content: center;
        position: relative;
        transition: all 0.3s ease;
    }
    .timeline-inloop {
        background: linear-gradient(135deg, #666, #888);
        flex: 0 0 auto;
        min-width: 60px; /* Minimal width to fit "Inloop" text with comfortable padding */
    }
    .timeline-bbq {
        background: linear-gradient(135deg, var(--brand-yellow), #ffb300);
        flex: 1;
    }
    .timeline-extra {
        background: linear-gradient(135deg, #2ECC71, #27ae60);
        flex: 0 0 auto;
        min-width: 55px; /* Minimum width for two-line text */
        display: flex; /* Ensure flex display for visibility */
    }
    .timeline-label {
        font-size: 0.75rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.05em;
        color: var(--brand-black);
        text-shadow: 0 1px 2px rgba(255,255,255,0.3);
        padding: 0 0.5rem;
        white-space: nowrap;
        line-height: 1.2;
    }
    /* Show/hide timeline label variants */
    .timeline-label-mobile {
        display: inline; /* Show + on all devices */
        font-size: 1.1rem; /* Larger for + symbol */
        font-weight: 900; /* Extra bold for visibility */
        color: #fff; /* White color for + symbol */
    }
    .timeline-label-full {
        display: none; /* Hide "Extra tijd" text on all devices */
    }
    /* Mobile: adjust timeline label styling */
    @media (max-width: 520px) {
        .timeline-label {
            font-size: 0.6rem; /* Smaller for better fit */
            padding: 0.2rem 0.3rem;
            letter-spacing: 0.02em;
            white-space: normal;
            text-align: center;
            line-height: 1.2;
        }
        .timeline-extra {
            min-width: 40px; /* Wider for better + visibility */
        }
    }
    .timeline-times {
        display: flex;
        position: relative;
        font-size: 0.9rem;
        color: var(--text-secondary);
        font-weight: 600;
        padding-bottom: 0.75rem;
    }
    .time-marker {
        color: var(--brand-yellow);
        transition: opacity 0.3s ease;
        position: absolute;
    }
    #timeline-start-label {
        left: 0;
    }
    #timeline-bbq-end-label {
        left: 50%; /* Will be updated dynamically by JavaScript */
        transform: translateX(-50%);
    }
    #timeline-end-label {
        right: 0;
    }
    
    /* Accessibility improvements */
    /* Improved focus indicators */
    a:focus, button:focus, input:focus, select:focus, textarea:focus {
        outline: 2px solid var(--brand-yellow);
        outline-offset: 2px;
    }
    /* Screen reader only text */
    .sr-only {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border-width: 0;
    }
    /* Ensure sufficient color contrast */
    .text-secondary {
        color: #b0b0b0; /* Improved from #a0a0a0 */
    }
    /* Focus visible only for keyboard (not mouse clicks) */
    *:focus:not(:focus-visible) {
        outline: none;
    }
    *:focus-visible {
        outline: 2px solid var(--brand-yellow);
        outline-offset: 2px;
    }
    
    .switch { position: relative; display: inline-block; width: 60px; height: 34px; flex-shrink: 0; }
    .switch input { opacity: 0; width: 0; height: 0; }
    .switch input:checked + .slider { background-color: var(--brand-yellow); }
    .switch input:checked + .slider:before { transform: translateX(26px); background-color: var(--dark-bg); }
    .switch .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--border-color); transition: .4s; border-radius: 34px; }
    /* The vlees/vega toggles looked inert until clicked; a hover ring makes the
       whole pill read as the control it is, not just the knob. */
    .switch:hover .slider { box-shadow: 0 0 0 3px rgba(255, 217, 0, 0.22); }
    .switch:hover .slider:before { background-color: #ffffff; }
    .switch input:focus-visible + .slider { box-shadow: 0 0 0 3px rgba(255, 217, 0, 0.45); }
    .switch .slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: var(--text-primary); transition: .4s; border-radius: 50%; }
    .switch.switch--vega .slider:before { background-color: var(--brand-yellow); }
    .switch.switch--vega input:checked + .slider { background-color: var(--vega-color); }
    .switch.switch--vega input:checked + .slider:before { background-color: var(--dark-bg); }
    .toggle-label { margin-left: 10px; font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); }
    .toggle-wrapper { display: flex; align-items: center; justify-content: flex-start; position: relative; z-index: 20; }
    .vega-quantity-select { background-color: var(--dark-bg); color: var(--text-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 0.25rem; z-index: 20; position: relative; }
    .vega-quantity-selector { display: none; margin-top: 1rem; flex-direction: column; align-items: flex-start; gap: 0.25rem; }
    .vega-quantity-selector div { display: flex; align-items: center; gap: 0.5rem; }
    .vega-quantity-selector label { font-size: 0.9rem; margin-right: 0; white-space: nowrap; }
    .vega-disclaimer { font-size: 0.75em; font-style: italic; color: var(--text-secondary); text-align: left; }
    @media (min-width: 768px) { .card-container { grid-template-columns: repeat(2, 1fr); } }
    @media (min-width: 1200px) { .card-container { grid-template-columns: repeat(4, 1fr); } }
    
    /* Hier stond de zwevende "Totaal"-pil (.floating-price-*). Verwijderd op
       31-08-2026, besluit van de eigenaar: op de telefoon lag hij permanent over
       de inhoud — over de tier-kaarten en over de koppen — en hij zette de aandacht
       op de prijs in plaats van op de beleving. Het totaal staat in het
       overzichtspaneel, dat de gast onderweg vanzelf tegenkomt.
       Meegegaan: de `floatBounce`-animatie, die als enige nog eindeloos doorliep op
       deze pagina's. */

    /* ---- Bier-tegoed explainer (.ble-*) ----
       The allowance rule shown as two scannable numbers + one sentence, instead of the
       paragraph it used to be. Scoped to its own class prefix: the .beer-limit-explainer
       shell is ALSO used for unrelated callouts (the workshop combi tip), so never hang
       layout rules off that class. No per-tier glass count here on purpose — the tier
       cards already state it, and hardcoding one tier's number is wrong for the others. */
    .ble-head {
        display: flex; align-items: center; gap: 0.5rem;
        font-weight: 700; margin-bottom: 0.85rem;
    }
    .ble-head .sbv-icon { flex-shrink: 0; color: var(--brand-yellow); }
    .ble-stats { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: 0.75rem; }
    .ble-stat {
        flex: 1 1 auto; min-width: 0;
        padding: 0.55rem 0.85rem;
        background: rgba(0, 0, 0, 0.22);
        border: 1px solid rgba(255, 217, 0, 0.22);
        border-radius: 8px;
        /* Revealed with the step (display:none -> block restarts CSS animations), so the
           numbers land one after the other without any observer JS. */
        animation: bleRise 420ms cubic-bezier(.2,.7,.3,1) both;
    }
    .ble-stat:nth-child(2) { animation-delay: 90ms; }
    .ble-stat-value {
        display: block;
        font-size: 1.15rem; font-weight: 700; line-height: 1.2;
        color: var(--brand-yellow);
        white-space: nowrap; /* "20 cl" / "€9,95" must never break */
    }
    .ble-stat-label {
        display: block; margin-top: 0.1rem;
        font-size: 0.8rem; color: var(--text-secondary);
        white-space: nowrap;
    }
    .ble-line {
        display: flex; align-items: flex-start; gap: 0.45rem;
        font-size: 0.9rem; line-height: 1.5; margin: 0;
        text-wrap: pretty;
    }
    .ble-line .sbv-icon { flex-shrink: 0; margin-top: 0.18rem; color: var(--brand-yellow); }
    @keyframes bleRise {
        from { opacity: 0; transform: translateY(8px); }
        to   { opacity: 1; transform: translateY(0); }
    }
    @media (prefers-reduced-motion: reduce) {
        .ble-stat { animation: none; }
    }
    /* Phone: stacking beats squeezing two nowrap numbers into one row. */
    @media (max-width: 420px) {
        .ble-stats { flex-direction: column; }
    }


    /* Enhanced mobile responsiveness */
    @media (max-width: 520px) { 
        .section-header { flex-direction: column; align-items: flex-start; } 
        .section-counter { margin-top: 0.25rem; }
        
        /* Mobile: keep time inputs side by side with equal widths */
        .time-row {
            display: flex;
            flex-direction: row;
            align-items: center;
            gap: 0.5rem;
            flex-wrap: nowrap;
        }
        .time-row .time-input,
        .time-row .time-display {
            flex: 0 0 auto;
            width: 110px !important;
            min-width: 110px;
            max-width: 110px;
        }
        .time-row span:not(.time-display) {
            flex: 0 0 auto;
        }
        
        /* Mobile: larger touch targets for sliders */
        .slider-container {
            min-height: 60px;
            padding: 0.5rem 0;
            gap: 0.5rem; /* Reduced gap to prevent overlap */
        }
        #slider_value_display,
        #extra-quantity-display,
        #time_extension_display {
            width: 50px;
            height: 50px;
            font-size: 0.9rem;
            padding: 0 0.4rem;
        }
        .dual-badge {
            width: 56px;
            height: 56px;
            font-size: 1.1rem;
        }
        
        /* Mobile: more compact intro */
        .intro-header h1 {
            font-size: 1.5rem;
        }
        .intro-lead {
            font-size: 0.95rem;
        }
        
        /* Mobile: larger submit button */
        .submit-btn {
            font-size: 1.1rem;
            padding: 1.25rem;
        }
        
        /* Mobile: better spacing for form sections */
        .form-section {
            padding: 1.25rem;
        }
        
        /* Mobile: better modal sizing */
        .modal-content {
            width: 95%;
            padding: 1.5rem 1.25rem;
        }
    }
    
    /* Tablet improvements */
    @media (min-width: 521px) and (max-width: 767px) {
        .card-container {
            grid-template-columns: repeat(2, 1fr);
        }
    }
    /* Time inputs row */
    .time-input, .time-display {
        width: 120px;
        height: 44px;
        font-family: var(--funnel-font);
        font-size: 1rem;
        background-color: var(--dark-bg);
        color: var(--text-primary);
        border: 1px solid var(--border-color);
        border-radius: 4px;
    }
    /* Dezelfde 3px linkerrand als elk ander veld reserveert, zodat de tijdkeuze
       niet verspringt zodra hij groen wordt — en zodat de dikke groene rand er
       even dik uitziet als bij het datumveld ernaast. .time-display krijgt hem
       ook: die staat op dezelfde regel en zou anders 2px hoger uitlijnen.
       Alleen de BREEDTE staat hier; de kleur komt uit de veldstatus-regels. */
    .time-input, .time-display { border-left-width: 3px; }
    .time-input { 
        text-align: center; 
        padding: 0 2rem 0 1.5rem; /* Balanced padding to center text with arrow */
        /* Reset browser defaults */
        -webkit-appearance: none;
        -moz-appearance: none;
        appearance: none;
        /* Custom dropdown arrow */
        background-image: url('data:image/svg+xml;charset=US-ASCII,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 5"><path fill="%23ffffff" d="M2 5L0 3h4z"/></svg>');
        background-repeat: no-repeat;
        background-position: right 0.75rem center;
        background-size: 0.65rem auto;
    }
    /* The dropdown list is a SEPARATE surface from the closed control, and it
       does not inherit the dark colours above. Left unstyled it falls back to
       the platform default — on a dark UA that is light text on a light popup,
       i.e. the start times the guest is picking from are unreadable. The repo
       rule is explicit: on dark UIs, style <select> AND <option>.
       #f4efe3 on #0f0e0b measures 16.8:1. */
    .time-input option,
    .time-input optgroup {
        background-color: var(--dark-bg);
        color: var(--text-primary);
    }
    .time-input option:checked,
    .time-input option:hover {
        /* #0f0e0b on #FFD900 measures 15.2:1. */
        background-color: var(--brand-yellow);
        color: var(--dark-bg);
    }
    .time-display { display: inline-flex; align-items: center; justify-content: center; text-align: center; padding: 0 0.5rem; }
    .time-row { 
        display: flex !important; 
        gap: 0.75rem; 
        flex-wrap: nowrap; 
        align-items: center; 
        justify-content: flex-start;
    }
    .time-input {
        flex: 0 0 auto;
        min-width: 110px;
    }
    /* Focus ring: the time inputs sit flush against .collapsible-body's
       overflow:hidden edge (0px clearance left/bottom), so a normal outline
       (drawn OUTSIDE the box, +2px offset) gets clipped and renders as a
       partial border. Use an INSET box-shadow ring instead — it's painted
       inside the element, so nothing extends past the clip edge. */
    .time-input:focus,
    .time-input:focus-visible,
    .time-display:focus,
    .time-display:focus-visible {
        outline: none;
        border-color: var(--brand-yellow);
        box-shadow: inset 0 0 0 2px var(--brand-yellow);
    }

    /* Variant icons in top-right corner of dish photos */
    .card-image-wrapper {
        position: relative;
        width: 100%;
        /* De sear-band hieronder schuift dwars over de foto en mag daar niet
           buiten komen. */
        overflow: hidden;
    }

    /* ══════════════════════════════════════════════════════════════
       "OP DE BBQ" — het gerecht wordt gegrild bij het kiezen
       Twee lagen op de foto, allebei puur decoratief (pointer-events: none):
         ::before  de hete band die één keer overheen veegt   (transform)
         ::after   de grillstrepen die daarachter opkomen      (opacity)

       ALLEEN transform en opacity, met opzet. Dat zijn de twee eigenschappen die
       de compositor zelf afhandelt; alles daarbuiten (box-shadow, filter, mask)
       kost een repaint per frame, en dit draait op een kaart met een foto erin,
       op een telefoon, terwijl de gast scrollt.

       EN HET LOOPT ÉÉN KEER AF, ZONDER SPOOR. De bestaande selectiegloed
       (brandGlowPulse) is eindeloos; dat kan deze er niet nog eens bovenop doen.
       Beide lagen eindigen op nul — zie de uitleg bij de rusttoestand hieronder
       voor waarom de strepen NIET blijven staan.

       OOK op een bijbetaald gerecht (.is-extra). Dat leek eerst onlogisch — de
       naam suggereert "valt buiten het arrangement" — maar gemeten gedrag zegt
       iets anders: zo'n gerecht heeft gewoon porties en ligt gewoon op de BBQ, de
       gast betaalt er alleen voor. Het verschil hoort in het prijspilletje te
       zitten, niet in de vraag of het gegrild wordt. Zie de .is-extra-regels
       hierboven. */
    .card-image-wrapper::before,
    .card-image-wrapper::after {
        content: '';
        position: absolute;
        inset: 0;
        pointer-events: none;
        z-index: 1;
    }
    /* De grillstrepen. Diagonaal, want een grillrooster ligt nooit haaks op de
       cameralijn, en dat leest meteen als "rooster" in plaats van "streepjes". */
    .card-image-wrapper::after {
        background: repeating-linear-gradient(
            115deg,
            rgba(28, 16, 6, 0.85) 0 5px,
            transparent 5px 26px
        );
        opacity: 0;
        mix-blend-mode: multiply;
    }
    /* De hete band: smal, warm, en breder dan de kaart zodat hij er echt
       doorheen trekt in plaats van erin te verschijnen. */
    .card-image-wrapper::before {
        left: -60%;
        right: -60%;
        background: linear-gradient(
            100deg,
            transparent 42%,
            rgba(255, 149, 41, 0.55) 50%,
            rgba(255, 217, 0, 0.35) 54%,
            transparent 62%
        );
        opacity: 0;
    }

    /* DE STREPEN BLIJVEN NIET STAAN, en dat is het hele punt van deze regel.
       Eerst wel: een gekozen gerecht hield de grillstrepen op 30%. Dat leest fout.
       Donkere diagonale arcering over een productfoto is de standaardtaal voor
       "niet beschikbaar" of "uitverkocht" — precies het tegenovergestelde van wat
       hier bedoeld was. En de veeg duurt maar 0,45s: wie een halve minuut later
       terugscrollt ziet alleen nog die arcering, zonder de beweging die hem
       verklaarde. De gast concludeert dan dat zijn keuze niet is gelukt.

       De veeg blijft dus een MOMENT, geen toestand. Dat de kaart gekozen is, zegt
       de gele gloed al — die is er permanent voor. Twee permanente signalen
       stapelen levert geen duidelijkheid op, alleen ruis. */

    /* De veeg zelf. DE DUUR HANGT AAN EEN TIMER: `just-selected` wordt na
       dishIgniteMs() weer van de kaart gehaald (js/experience_core.js, gedeeld
       door bbq_lowandslow.php / bbq_plancha.php / workshop.php), en op dat moment
       stopt deze animatie waar hij is. Alles in dit blok moet dus binnen dat
       venster passen — wordt die functie kleiner, dan kapt hij de veeg af.

       DRIE KEER TE SNEL BEVONDEN, en de laatste keer om de scherpste reden.
       0,45s ("veel en veel te snel"), toen 1,05s, en op 03-09-2026: "far too
       quick, it needs to be low and slow like the experience we are selling".
       Dat is geen smaakkwestie. Het product heet Low & Slow; een gerecht dat in
       een seconde klaar is verkoopt precies het tegenovergestelde van wat de
       gast boekt. Vandaar ~2,7x trager dan de vorige ronde: de hitte trekt er
       hoorbaar traag overheen in plaats van eroverheen te flitsen. */
    .card.just-selected .card-image-wrapper::before {
        animation: dish-sear-band 2.8s cubic-bezier(0.35, 0, 0.25, 1);
    }
    /* De strepen lopen iets langer door dan de band: het rooster blijft even
       nagloeien nadat de hitte voorbij is, in plaats van tegelijk met de band te
       verdwijnen. Dat kleine verschil is wat het "grillen" laat lezen als iets dat
       gebeurt, en niet als een lichtflits. */
    .card.just-selected .card-image-wrapper::after {
        animation: dish-sear-marks 3.2s cubic-bezier(0.3, 0, 0.3, 1);
    }
    @keyframes dish-sear-band {
        0%   { opacity: 0; transform: translateX(-100%); }
        18%  { opacity: 1; }
        82%  { opacity: 1; }
        100% { opacity: 0; transform: translateX(100%); }
    }
    /* Op en weer af. Eindigt op 0, gelijk aan de rusttoestand van ::after, dus er
       is geen sprongetje wanneer .just-selected van de kaart wordt gehaald. */
    @keyframes dish-sear-marks {
        0%   { opacity: 0; }
        45%  { opacity: 0.55; }
        100% { opacity: 0; }
    }

    /* ── KOLEN EN VONKEN ─────────────────────────────────────────────────────
       De hitteband en de strepen waren te ingetogen: "ik verwachtte vlammen".
       Dit is de laag die het gerecht echt op het vuur zet.

       GEEN GETEKENDE VLAMMEN MEER (2026-09-03). Hier stonden negen gele
       druppelvormen ("vlammetjes") plus een confetti-burst; de eigenaar noemde het
       "cringe, comic style" — en terecht: vuur als tekening leest als een icoon.
       Wat er nu staat is een gloeiend kolenbed (deze ::before) en een korte regen
       van opstijgende vonken uit dezelfde fabriek als het gordijn en de
       paginaachtergrond: window.SBV.emberField, aangeroepen in igniteDish()
       (js/experience_core.js). Eén bewegingstaal, en niets dat op een emoji lijkt.

       De laag wordt door igniteDish() ingehangen en na 1,3s weer weggehaald. Ze
       staat dus NIET in de HTML — bewust, want een vuurlaag die blijft hangen
       wordt vanzelf een toestand, en dat is precies de fout die de grillstrepen
       eerder maakten ("uitverkocht").

       Alleen opacity animeert. De `filter: blur()` op het kolenbed staat vast en
       verandert nooit tijdens de animatie; een blur die per frame herberekend moet
       worden is op een telefoon een heel ander verhaal. */
    .dish-fire {
        position: absolute;
        inset: 0;
        pointer-events: none;
        z-index: 2;
        /* GEEN mix-blend-mode. Eerst stond hier `screen`, wat op papier klopt —
           vuur is licht, dus optellen bij wat eronder ligt. In de praktijk staat op
           deze kaarten een gerecht op een wit bord, en geel op wit via `screen` is
           wit: gemeten stond het vuur er wél (opacity 0,79, keurig op zijn plek)
           maar was er niets van te zien. Normaal overtekenen leest op elke foto. */
    }
    /* Het vonkencanvas. De fabriek zet zelf de maat (in px, gelijk aan de foto);
       hier alleen de plaats en de levensduur.

       DE UITDOOF IS NIET COSMETISCH. Het canvas wordt door igniteDish() uit de
       DOM gehaald als de vuurlaag klaar is. Op 1,3s viel dat niet op; op de trage
       duur van nu staan er nog zichtbaar vonken te stijgen op het moment dat de
       laag verdwijnt, en dan knipt het beeld eruit. Deze animatie laat ze doven
       vóór het opruimen, zodat het einde een einde is en geen knip. */
    .dish-fire canvas {
        position: absolute;
        left: 0;
        top: 0;
        display: block;
        animation: dish-sparks-life 3.2s ease-out backwards;
    }
    @keyframes dish-sparks-life {
        0%   { opacity: 0; }
        12%  { opacity: 1; }
        58%  { opacity: 1; }
        100% { opacity: 0; }
    }
    /* Het kolenbed: een warme gloed langs de onderrand die één keer oplaait en
       dooft — de bron waar de vonken uit opstijgen. Zachter dan eerst (0,85 naar
       0,6): een gloed ónder het gerecht, geen oranje vlak eroverheen. */
    .dish-fire::before {
        content: '';
        position: absolute;
        left: -5%;
        right: -5%;
        bottom: -6px;
        height: 42%;
        background: linear-gradient(
            to top,
            rgba(255, 149, 41, 0.6) 0%,
            rgba(255, 91, 46, 0.3) 40%,
            rgba(255, 91, 46, 0) 100%
        );
        filter: blur(10px);
        animation: dish-embers 3.2s ease-out backwards;
    }
    /* Kolen gloeien langzaam op en doven láng na. Het aandeel dat "opkomen" is
       gaat daarom omlaag (25% van 1,1s was 0,28s; 12% van 3,2s is 0,38s) — anders
       zou het oplaaien mee-uitrekken tot bijna een seconde en dat leest als een
       lamp die aangaat, niet als kolen die pakken. */
    @keyframes dish-embers {
        0%   { opacity: 0; }
        12%  { opacity: 1; }
        100% { opacity: 0; }
    }
    /* GEEN prefers-reduced-motion-uitzondering voor de veeg, en dat is een besluit.
       Die stond hier eerst wél, en het gevolg was dat de eigenaar meldde: "nu is er
       helemaal geen animatie meer". Zijn besturingssysteem meldt reduced motion —
       ongewild, het is hier een bekend gegeven — en daarmee viel niet alleen deze
       veeg weg maar ook de stuiter en de gloedpuls (zie de regel bij .card
       .is-selected). Wat er onder RM overbleef was een statische schaduw: het
       kiezen van een gerecht gaf gewoon geen enkele terugkoppeling meer.

       Waar RM voor bedoeld is, is beweging die klachten geeft: parallax, grote
       verplaatsingen, dingen die blijven doorlopen. Dit is 0,45 seconde opacity en
       een transform binnen één kaart, één keer, en er beweegt geen tekst mee. Het
       infinite-geval op deze kaarten (brandGlowPulse) is wél de soort beweging waar
       die regel voor is, en die RM-uitzondering blijft dan ook gewoon staan.

       Bewaakt: apps/local/tests/gerecht_animatie/ draait de hele meting twee keer,
       met en zonder reduced motion, precies omdat dit anders stil terugkomt. */
    .variant-icon {
        position: absolute;
        top: 0.5rem;
        right: 0.5rem;
        width: 32px;
        height: 32px;
        background: rgba(0, 0, 0, 0.7);
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0.4rem;
        backdrop-filter: blur(4px);
        border: 2.5px solid rgba(255, 255, 255, 0.6);
        box-shadow: 0 0 12px rgba(255, 255, 255, 0.4), 0 2px 6px rgba(0, 0, 0, 0.3);
    }
    .variant-icon svg {
        width: 20px;
        height: 20px;
        stroke-width: 2;
    }
    .variant-meat .variant-icon svg {
        stroke: var(--meat-color);
    }
    .variant-vega .variant-icon svg {
        stroke: var(--vega-color);
    }
    
    /* ========================================
       VanillaJS Datepicker - Dark Theme Overrides
       ULTRA HIGH SPECIFICITY - Must override library CSS
       ======================================== */
    
    /* Dropdown and picker containers - FORCE DARK with max specificity */
    .datepicker.datepicker-dropdown,
    div.datepicker-dropdown,
    .datepicker-dropdown.datepicker-dropdown,
    .datepicker-dropdown .datepicker-picker,
    div.datepicker-dropdown .datepicker-picker,
    .datepicker-dropdown div.datepicker-picker {
        background: var(--card-bg) !important;
        background-color: var(--card-bg) !important;
        border: 1px solid var(--border-color) !important;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
        border-radius: 8px !important;
    }
    
    /* Picker - ultra specific */
    .datepicker-picker.datepicker-picker,
    div.datepicker-picker,
    .datepicker-dropdown > .datepicker-picker {
        background: var(--card-bg) !important;
        background-color: var(--card-bg) !important;
    }
    
    /* Main content area - force dark */
    .datepicker-main.datepicker-main,
    .datepicker-picker .datepicker-main,
    div.datepicker-main {
        background: var(--card-bg) !important;
        background-color: var(--card-bg) !important;
    }
    
    /* Header controls - max specificity */
    .datepicker-controls.datepicker-controls,
    div.datepicker-controls,
    .datepicker-header .datepicker-controls,
    .datepicker-picker .datepicker-controls {
        background: var(--card-bg) !important;
        background-color: var(--card-bg) !important;
        border-bottom: 1px solid var(--border-color) !important;
    }
    
    /* Navigation buttons - ULTRA specific to override white */
    .datepicker-controls .button.button,
    .datepicker-controls > .button,
    .datepicker-header .datepicker-controls .button,
    button.button[class*="button"] {
        background: transparent !important;
        background-color: transparent !important;
        color: #e5e5e5 !important;
        border: none !important;
        border-color: transparent !important;
    }
    .datepicker-controls .button:hover.button,
    .datepicker-controls .button:hover,
    .datepicker-header .datepicker-controls .button:hover {
        color: var(--brand-yellow) !important;
        background: rgba(255,217,0,0.1) !important;
        background-color: rgba(255,217,0,0.1) !important;
        border-color: transparent !important;
    }
    .datepicker-controls .button:focus,
    .datepicker-controls .button:active {
        background: rgba(255,217,0,0.15) !important;
        background-color: rgba(255,217,0,0.15) !important;
        border-color: transparent !important;
        box-shadow: none !important;
    }
    
    /* Month/Year title - force yellow */
    .datepicker-controls .view-switch.view-switch,
    button.view-switch {
        color: var(--brand-yellow) !important;
        font-weight: 600 !important;
        background: transparent !important;
        background-color: transparent !important;
    }
    
    /* Weekday headers */
    .datepicker .dow,
    span.dow {
        color: #e5e5e5 !important;
        font-weight: 600 !important;
    }
    
    /* Day cells - override white backgrounds */
    .datepicker-cell.datepicker-cell,
    span.datepicker-cell,
    .datepicker-grid .datepicker-cell {
        color: var(--text-primary) !important;
        background: transparent !important;
        background-color: transparent !important;
    }
    .datepicker-cell:not(.disabled):hover,
    span.datepicker-cell:not(.disabled):hover {
        /* Hover fill, not a border — needs to sit one step ABOVE the dropdown
           surface (--card-bg), so it stays a literal rather than the border token. */
        background: #332c22 !important;
        background-color: #332c22 !important;
        color: var(--brand-yellow) !important;
    }
    
    /* Focused state */
    .datepicker-cell.focused:not(.selected),
    span.datepicker-cell.focused:not(.selected) {
        background: var(--brand-yellow) !important;
        background-color: var(--brand-yellow) !important;
        color: var(--brand-black) !important;
        font-weight: 600 !important;
    }
    
    /* Selected date */
    .datepicker-cell.selected,
    .datepicker-cell.selected:hover,
    span.datepicker-cell.selected {
        background: var(--brand-yellow) !important;
        background-color: var(--brand-yellow) !important;
        color: var(--brand-black) !important;
        font-weight: 600 !important;
    }
    
    /* Today marker */
    .datepicker-cell.today:not(.selected),
    span.datepicker-cell.today:not(.selected) {
        background: rgba(255,217,0,0.2) !important;
        background-color: rgba(255,217,0,0.2) !important;
        border: 1px solid #555 !important;
    }
    .datepicker-cell.today:not(.selected):not(.disabled) {
        color: var(--brand-yellow) !important;
    }
    
    /* Disabled dates */
    .datepicker-cell.disabled,
    span.datepicker-cell.disabled {
        color: #555 !important;
        opacity: 0.4 !important;
    }
    
    /* Previous/next month dates */
    .datepicker-cell.prev:not(.disabled),
    .datepicker-cell.next:not(.disabled) {
        color: #666 !important;
    }
    
    /* Ensure input field stays responsive */
    #test_field.datepicker-input {
        width: 100% !important;
        max-width: 100% !important;
        display: block !important;
        box-sizing: border-box !important;
    }
    
    /* Toast game pulse animation */
    @keyframes pulse-glow {
        0%, 100% {
            transform: scale(1);
            box-shadow: 0 0 0 0 rgba(255, 217, 0, 0.4);
        }
        50% {
            transform: scale(1.05);
            box-shadow: 0 0 20px 10px rgba(255, 217, 0, 0.2);
        }
    }
    
    .toast-glass-clickable:hover {
        transform: scale(1.1) !important;
        background: rgba(255, 217, 0, 0.2) !important;
        box-shadow: 0 0 30px 15px rgba(255, 217, 0, 0.3) !important;
    }
    
    .toast-glass-clickable:active {
        transform: scale(0.95) !important;
    }
    
    /* Toast button custom styling */
    #toast-button:hover:not(:disabled) {
        transform: scale(1.05);
        box-shadow: 0 6px 30px rgba(255, 217, 0, 0.6), 0 0 40px rgba(255, 217, 0, 0.3);
        background: #ffed4e !important;
    }
    
    #toast-button:active:not(:disabled) {
        transform: scale(0.98);
    }
    
    #toast-button:disabled {
        opacity: 0.6 !important;
        cursor: not-allowed !important;
    }
    
    /* ── Progressive completion ──────────────────────────────
       Still-empty required fields INVITE (gold); completed ones CONFIRM
       (green). Nothing animates — the form reads as a checklist filling
       itself in, which pulls the guest through without ever nagging.

       This replaces `input[required]:placeholder-shown { border: 2px solid
       rgba(255,68,68,.3) }`, which painted every untouched field red on load:
       red is the error colour, so an empty form announced three mistakes the
       guest had not yet made. Red is now reserved for real validation errors
       (.form-error-msg), which is the only thing that should ever use it.

       The input STATE rules live further down, next to the v2 field styling
       that would otherwise override them — see "field states" there. Only the
       label marker lives here, since nothing competes for it. */

    /* Label styling for required fields */
    label:has(+ input[required])::after,
    label:has(+ div input[required])::after {
        content: " *";
        color: #ff4444;
        font-weight: bold;
    }

    /* …en zodra het veld af is, verdwijnt die markering. GEEN groen vinkje meer
       (besluit van de eigenaar, 31-08-2026): het veld zelf zegt het al met een
       groene rand rondom en een dikke groene linkerrand, en dat is op een halve
       meter afstand beter te zien dan een icoontje van 0,8em naast de tekst. Twee
       keer hetzelfde zeggen maakt het niet duidelijker.

       WAAROM DIT `content: none` IS EN NIET EEN VERWIJDERDE REGEL. Het rode
       sterretje hierboven is een LOSSE regel voor verplichte velden. Haal je dit
       blok weg, dan komt dat sterretje terug op velden die allang in orde zijn —
       rood op een afgerond veld, terwijl rood in deze trechter uitsluitend voor
       echte validatiefouten is (.form-error-msg). De markering hoort dus actief
       uitgezet te worden, niet vergeten.

       De selectorlijst blijft daarmee precies zo waardevol als hij was: hij
       definieert "dit veld is netjes ingevuld", nu alleen met een andere uitkomst.

       LET OP — GEEN `:has()` BINNEN `:has()`. Dat is per spec verboden, en een
       ongeldige selector maakt de HELE lijst ongeldig: één genest exemplaar hier
       liet de browser dit blok in zijn geheel weggooien. Er valt niets om en je
       ziet geen fout — het rode sterretje komt gewoon terug op afgeronde velden.
       Vandaar `select:valid` (wat bij een verplichte keuzelijst precies "er is een
       echte optie gekozen" betekent) in plaats van `select:has(option:checked...)`.

       EN GESCOPED OP .form-group, om dezelfde reden als de randregels: de honeypot
       (#website) is een LEEG tekstveld ZONDER placeholder-attribuut, en voor zo'n
       veld is `:not(:placeholder-shown)` altijd waar. Dat is meteen de les voor elk
       nieuw veld hier: een verplicht veld zonder placeholder meldt zichzelf af als
       "goed ingevuld" terwijl het leeg is. Bewaakt door apps/local/tests/veldstatus/. */
    .form-group label:has(+ input:not([type="range"]):not([type="checkbox"]):not([type="radio"]):not(:placeholder-shown):valid)::after,
    .form-group label:has(+ div input:not([type="range"]):not([type="checkbox"]):not([type="radio"]):not(:placeholder-shown):valid)::after,
    .form-group label:has(+ textarea:not(:placeholder-shown):valid)::after,
    .form-group label:has(+ div textarea:not(:placeholder-shown):valid)::after,
    .form-group label:has(+ select:valid)::after,
    .form-group label:has(+ div select:valid)::after {
        content: none;
    }
    /* ============================================================
       v2.0 STRUCTURAL ADDITIONS — layout, hero band, step kickers
       ============================================================ */

    /* Container: max-width + breathing room */
    .container {
        max-width: 1180px;
        margin: 0 auto;
        padding: 0 clamp(1rem, 3vw, 2rem) 3rem;
        position: relative;
        z-index: 2;
    }

    /* ---- HERO BAND ---- */
    .page-hero {
        position: relative;
        height: clamp(220px, 30vw, 320px);
        border-radius: 8px;
        overflow: hidden;
        margin: 1.5rem 0 0;
        box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
    }
    .page-hero-img {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
    }
    .page-hero-overlay {
        position: absolute;
        inset: 0;
        background: linear-gradient(
            90deg,
            rgba(26, 26, 26, 0.92) 0%,
            rgba(26, 26, 26, 0.65) 50%,
            rgba(26, 26, 26, 0.35) 100%
        );
        pointer-events: none;
    }
    .page-hero-content {
        position: relative;
        z-index: 2;
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: clamp(1.25rem, 4vw, 3rem);
        gap: 0.65rem;
        max-width: 560px;
    }
    .page-hero-badge {
        display: inline-flex;
        align-items: center;
        gap: 0.4rem;
        padding: 0.3rem 0.65rem;
        background: rgba(255, 217, 0, 0.92);
        color: var(--dark-bg); /* warm ink on gold, same as the site's ::selection */
        border-radius: 999px;
        font-size: 0.78rem;
        font-weight: 800;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        width: fit-content;
    }
    .page-hero-title {
        margin: 0;
        font-size: clamp(1.6rem, 4vw, 2.6rem);
        font-weight: 900;
        color: #ffffff;
        line-height: 1.1;
    }
    .page-hero-facts {
        display: flex;
        flex-wrap: wrap;
        gap: 0.5rem 1rem;
        margin-top: 0.2rem;
    }
    .page-hero-fact {
        display: inline-flex;
        align-items: center;
        gap: 0.35rem;
        color: rgba(255, 255, 255, 0.82);
        font-size: 0.88rem;
        font-weight: 500;
        white-space: nowrap; /* een feit breekt nooit over twee regels */
    }

    /* De vierde: de haak, niet de logistiek. Vlammenkleur en vol contrast zodat
       hij uit de rij springt zonder groter te zijn — hij moet als laatste blijven
       hangen, niet als eerste schreeuwen. */
    .page-hero-fact--fire {
        color: var(--brand-yellow, #ffd900);
        font-weight: 600;
    }
    .page-hero-fact--fire .sbv-icon { color: currentColor; }

    /* ---- STEP KICKER (new) ---- */
    .step-kicker {
        display: block;
        margin-bottom: 0.3rem;
        color: var(--brand-yellow);
        font-size: 0.78rem;
        font-weight: 800;
        text-transform: uppercase;
        letter-spacing: 0.06em;
    }
    /* When h2 immediately follows a step-kicker, ensure no gap */
    .step-kicker + h2 { margin-top: 0; color: #ffffff; font-size: 1.35rem; font-weight: 700; }

    /* Intro section: no top accent bar needed since hero provides context */
    .intro-section {
        background: transparent;
        border: none;
        box-shadow: none;
        padding: 1.25rem 0 0.5rem;
        margin: 0 0 0.5rem;
    }
    .intro-lead {
        color: var(--text-secondary);
        font-size: 0.95rem;
        line-height: 1.75;
        /* No artificial measure cap. A 72ch cap held this to ~643px inside a
           1100px section, so a lead that fits the container was still broken
           across lines for no reason the guest can see. Wrap as little as the
           container allows; text-wrap: pretty then stops the last line from
           being left with a single orphan word when a wrap IS unavoidable. */
        max-width: none;
        text-wrap: pretty;
    }
    /* Kept so existing markup using it stays valid — it is now the same as the
       base rule rather than an exception to it. */
    .intro-lead--wide { max-width: none; }
    .intro-lead b { color: #ffffff; font-weight: 700; }

    /* Improve form-group spacing */
    .form-group { margin-top: 1.1rem; }
    .form-group:first-child { margin-top: 0; }
    .form-group label { font-size: var(--field-font-size); color: rgba(255,255,255,0.85); margin-bottom: 0.4rem; }

    /* Form inputs: match booking funnel style */
    .form-group input[type="text"],
    .form-group input[type="email"],
    .form-group input[type="number"],
    .form-group input[type="tel"],
    .form-group textarea {
        border-radius: 8px;
        border: 1px solid rgba(255, 255, 255, 0.12);
        background: rgba(255, 255, 255, 0.04);
        transition: border-color 120ms ease;
        /* Font-size deliberately NOT set here — --field-font-size owns it, so
           the control stays locked to its label. This rule used to re-declare
           0.95rem and silently win over the token. */
        /* Every field reserves the same 3px left edge, so text lines up
           identically whether or not the edge is coloured. border-box sizing
           means colouring it costs no reflow. */
        border-left-width: 3px;
    }

    /* ---- Field states: progressive completion ----
       Ordered before :focus and excluded via :not(:focus) so focus always wins
       while the guest is actually in the field. No !important anywhere — these
       replace two competing !important blocks (one painting every untouched
       required field red, a second added later purely to undo the first). */
    .form-group input[required]:placeholder-shown:not(:focus),
    .form-group textarea[required]:placeholder-shown:not(:focus),
    .form-group select[required]:not(:has(option:checked:not([value=""]))):not(:focus) {
        border-color: rgba(255, 217, 0, 0.4);
        border-left-color: var(--brand-yellow);
        border-left-width: 3px;
    }

    /* Filled → green: rand rondom + een dikke groene linkerrand. :valid means
       "has content" here (these inputs carry no pattern beyond type=email), so it
       is a completeness cue, not a claim the value was verified — submit-time JS
       validation still owns that.

       GELDT VOOR ELK VELD DAT NETJES IS INGEVULD, niet alleen voor de verplichte
       tekstvelden (31-08-2026). Daarvoor sloeg de regel drie soorten over, en de
       gast zag naast een groen omrand datumveld een tijdkeuze die er nog
       "onaangeraakt" uitzag terwijl hij wél gevuld was:
        - `<select>` (de starttijd) — die kan `:placeholder-shown` per definitie
          niet matchen, dus die test moet via de gekozen option lopen;
        - `<textarea>` zonder `required` (de opmerkingen voor de chef);
        - optionele tekstvelden (de gelegenheid).
       De honeypot (#website) valt hier niet onder: die staat buiten .form-group,
       en dat moet zo blijven — een spamval mag nooit terugkoppelen dat hij
       "goed" is ingevuld.

       Aankruisvakjes en keuzerondjes worden expliciet uitgesloten: die kunnen
       `:placeholder-shown` ook niet matchen, dus `:not(:placeholder-shown)` is er
       altijd waar en ze zouden onvoorwaardelijk groen kleuren. */
    .form-group input:not([type="range"]):not([type="checkbox"]):not([type="radio"]):not(:placeholder-shown):valid:not(:focus),
    .form-group textarea:not(:placeholder-shown):valid:not(:focus),
    .form-group select:has(option:checked:not([value=""])):not(:focus) {
        border-color: rgba(34, 197, 94, 0.45);
        border-left-color: #22c55e;
        border-left-width: 3px;
    }

    /* Text-ish fields only — NEVER the range sliders: on a full-width 8px range
       input this 3px halo painted a second, wider "rail" whose caps stuck out
       12px past the end dots whenever the slider had focus (i.e. after every
       drag), reading as a misaligned track. Sliders get a thumb ring instead
       (see .slider-container rules). */
    .form-group input:not([type="range"]):focus,
    .form-group textarea:focus {
        outline: none;
        border-color: rgba(255, 217, 0, 0.5);
        box-shadow: 0 0 0 3px rgba(255, 217, 0, 0.12);
    }

    /* …MAAR een veld dat al klopt blijft GROEN terwijl je erin staat (31-08-2026).
       Hierboven won focus onvoorwaardelijk, en dat betekende: je typt je naam, het
       veld is af, en het staat nog steeds goud. De gast kijkt precies dán naar het
       veld dat hij zojuist heeft ingevuld — dat is het moment waarop "dit is goed"
       moet landen, niet pas nadat hij ergens anders heeft geklikt. De bevestiging
       kwam dus altijd te laat om iemand op te vallen.

       Focus blijft wél zichtbaar: de gloed eromheen zegt "je staat hier", de
       randkleur zegt "dit klopt". Twee signalen, twee eigenschappen — die hoeven
       niet om dezelfde kleur te vechten.

       Voor een e-mailadres werkt dit vanzelf goed: half getypt is `:valid` onwaar,
       dus die staat goud tot het adres compleet is. */
    .form-group input:not([type="range"]):not([type="checkbox"]):not([type="radio"]):not(:placeholder-shown):valid:focus,
    .form-group textarea:not(:placeholder-shown):valid:focus {
        border-color: rgba(34, 197, 94, 0.65);
        border-left-color: #22c55e;
        border-left-width: 3px;
        box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.16);
    }

    /* De tijdkeuze gebruikt een INSET ring in plaats van een gloed eromheen (zie
       .time-input:focus hierboven: de rij staat vlak tegen de overflow:hidden-rand
       van .collapsible-body, waar een gloed buiten de doos wordt afgekapt). Die
       constructie blijft; alleen de kleur volgt nu de geldigheid. */
    .form-group select.time-input:has(option:checked:not([value=""])):focus,
    .form-group select.time-input:has(option:checked:not([value=""])):focus-visible {
        border-color: rgba(34, 197, 94, 0.65);
        border-left-color: #22c55e;
        box-shadow: inset 0 0 0 2px #22c55e;
    }

    /* ── AUTOMATISCH INGEVULD (browser-autofill) ─────────────────────────────
       Chrome tekent een autofill-veld met zijn EIGEN laag: een lichtblauwe/gele
       achtergrond en een eigen tekstkleur, allebei niet met `background-color` te
       overschrijven. De enige manier eromheen is een inset-schaduw van 1000px in
       de paginakleur plus `-webkit-text-fill-color` — anders staat er donkere
       tekst op een lichte vulling midden in een donker formulier.

       WAT HIER FOUT WAS. Deze regels stonden bovenaan het bestand en zetten
       `border: 1px solid ... !important`. Daarmee sloegen ze precies de
       bevestiging weg waar het hier om gaat: een veld dat de browser voor je
       invult — naam, e-mail, telefoon, juist de velden die iedereen laat
       invullen — kreeg een grijze 1px rand in plaats van de groene, en de dikke
       linkerrand kromp mee terug naar 1px. Gemeld op 31-08-2026.

       Nu: alleen de vulling en de tekstkleur worden hier gezet, nooit de rand.
       De randkleur blijft van de statusregels hierboven, dus autofill ziet er
       precies zo uit als handmatig typen. Geen `!important` meer nodig — dit blok
       staat ná de focusregels, dus het wint op volgorde.

       De focusgloed moet in DEZELFDE box-shadow als de autofill-vulling: het is
       één eigenschap, dus een tweede declaratie zou de vulling wegvagen en de
       lichte achtergrond terugbrengen. Vandaar de twee samengestelde varianten.

       SAFARI OP iPAD/iPHONE TREKT ZICH VAN DIE INSET-SCHADUW NIETS AAN (12-09-2026).
       Gemeld met een schermafdruk van de plancha-aanvraag op een iPad: e-mail en
       telefoon stonden als LICHTE tekst op een LICHTGELE vulling, onleesbaar. Dat is
       precies de helft van dit blok die het wél haalt: `-webkit-text-fill-color` zet de
       crèmekleur (#f4efe3), maar iOS Safari tekent zijn autofill-markering BOVEN de
       inset-schaduw, dus de donkere vulling verdwijnt en de crème blijft staan.
       Safari doet dit ook bij een veld dat nog leeg is — het toont dan de waarde uit de
       contactkaart als voorbeeld, met `autocomplete="off"` en al.

       Drie lagen, want geen enkele laag dekt alle browsers:
       1. de inset-schaduw hierboven — Chrome en Safari op de Mac;
       2. een ANIMATIE naar background-color — een auteursanimatie wint wél van de
          autofill-laag van WebKit, dit is de laag die iOS repareert;
       3. een donkere text-shadow als vangnet — onzichtbaar op #0f0e0b, maar als een
          toekomstige Safari het geel tóch doorlaat blijft de tekst leesbaar.
       `:autofill` (de standaardnaam) staat in EIGEN regels, nooit in dezelfde
       selectorlijst als `:-webkit-autofill`: een browser die er één niet kent gooit
       anders de hele regel weg. */
    @keyframes belevingen-autofill-fill {
        from { background-color: var(--dark-bg); }
        to   { background-color: var(--dark-bg); }
    }
    .form-group input:-webkit-autofill,
    .form-group textarea:-webkit-autofill {
        -webkit-box-shadow: 0 0 0 1000px var(--dark-bg) inset;
        box-shadow: 0 0 0 1000px var(--dark-bg) inset;
        -webkit-text-fill-color: var(--text-primary);
        caret-color: var(--text-primary);
        text-shadow: 0 0 2px rgba(0, 0, 0, 0.85);
        animation: belevingen-autofill-fill 1s both;
    }
    .form-group input:autofill,
    .form-group textarea:autofill {
        -webkit-box-shadow: 0 0 0 1000px var(--dark-bg) inset;
        box-shadow: 0 0 0 1000px var(--dark-bg) inset;
        -webkit-text-fill-color: var(--text-primary);
        caret-color: var(--text-primary);
        text-shadow: 0 0 2px rgba(0, 0, 0, 0.85);
        animation: belevingen-autofill-fill 1s both;
    }
    /* Autofill + focus, nog niet geldig → vulling + gouden gloed. */
    .form-group input:-webkit-autofill:focus,
    .form-group textarea:-webkit-autofill:focus {
        -webkit-box-shadow: 0 0 0 1000px var(--dark-bg) inset, 0 0 0 3px rgba(255, 217, 0, 0.12);
        box-shadow: 0 0 0 1000px var(--dark-bg) inset, 0 0 0 3px rgba(255, 217, 0, 0.12);
    }
    /* Autofill + focus + geldig → vulling + groene gloed, gelijk aan typen. */
    .form-group input:-webkit-autofill:not(:placeholder-shown):valid:focus,
    .form-group textarea:-webkit-autofill:not(:placeholder-shown):valid:focus {
        -webkit-box-shadow: 0 0 0 1000px var(--dark-bg) inset, 0 0 0 3px rgba(34, 197, 94, 0.16);
        box-shadow: 0 0 0 1000px var(--dark-bg) inset, 0 0 0 3px rgba(34, 197, 94, 0.16);
    }

    /* ---- STACKED LAYOUT ---- */
    /* Form sections and summary panel stack vertically (single column). */
    @media (min-width: 900px) {
        #main-form-content > aside.summary-panel {
            margin-top: 1.5rem;
        }
    }

    /* ---- SECTION HEADER refinements ---- */
    .section-header {
        border-bottom: 1px solid rgba(255, 217, 0, 0.35);
        margin-bottom: 1.5rem;
    }
    .section-header h2 {
        font-size: 1.2rem;
        font-weight: 700;
        color: #ffffff;
    }

    /* ---- POLICY NOTE ---- */
    .policy-note {
        border-left-color: rgba(255, 217, 0, 0.6);
        font-size: 0.88rem;
    }

    /* ---- CONFIRMATION NOTICE inside summary panel ---- */
    #final-confirmation-notice {
        font-size: 0.82rem;
        border-radius: 6px;
        padding: 0.65rem 0.85rem;
        margin-top: 1rem;
        margin-bottom: 0.75rem;
        text-align: left;
        display: flex;
        align-items: flex-start;
        gap: 0.5rem;
        line-height: 1.5;
    }
    #final-confirmation-notice a { color: var(--brand-yellow); transition: color 140ms ease; }
    /* The tel:/mailto: links in this notice are the escape hatch for "ik moet
       iets wijzigen" — they were coloured but gave no hover feedback. */
    #final-confirmation-notice a:hover,
    #final-confirmation-notice a:focus-visible { color: #ffffff; text-decoration: underline; text-underline-offset: 2px; }

    /* ---- HELP / PHONE LINK ---- */
    .intro-help {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        margin-top: 0.85rem;
        padding: 0.7rem 1rem;
        border: 1px solid rgba(255, 255, 255, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.03);
        color: var(--text-secondary);
        font-size: 0.88rem;
        width: fit-content;
    }
    .intro-help a { color: var(--brand-yellow); font-weight: 700; text-decoration: none; }
    .intro-help a:hover { color: #ffffff; }

    /* .intro-help IS the <a> next to the hero CTA, so the rule above (which only
       styles a NESTED link) never fired for it — hovering the phone pill did
       nothing at all, right beside a CTA that lifts. Same lift vocabulary as
       .hero-book-btn, in the secondary weight. */
    a.intro-help {
        text-decoration: none;
        transition: background 160ms ease, border-color 160ms ease, transform 160ms ease, box-shadow 160ms ease;
    }
    a.intro-help:hover,
    a.intro-help:focus-visible {
        background: rgba(255, 255, 255, 0.07);
        border-color: rgba(255, 217, 0, 0.55);
        transform: translateY(-2px);
        box-shadow: 0 6px 18px rgba(0, 0, 0, 0.3);
    }
    a.intro-help:hover strong { color: #ffffff; }

    /* ---- HERO BOOK CTA ---- */
    .intro-ctas {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.75rem;
        margin-top: 1.1rem;
    }
    .hero-book-btn {
        display: inline-flex;
        align-items: center;
        gap: 0.45rem;
        padding: 0.8rem 1.5rem;
        background: var(--brand-yellow);
        color: #111;
        font-size: 0.95rem;
        font-weight: 800;
        border-radius: 10px;
        text-decoration: none;
        line-height: 1;
        transition: transform 0.15s ease, box-shadow 0.15s ease;
        white-space: nowrap;
    }
    .hero-book-btn:hover {
        transform: translateY(-2px);
        box-shadow: 0 6px 20px rgba(255,217,0,0.35);
    }
    .intro-ctas .intro-help {
        margin-top: 0; /* reset when inside flex container */
    }
    @media (max-width: 899px) {
        .hero-book-btn { width: 100%; justify-content: center; font-size: 1rem; padding: 0.9rem 1.5rem; }
        .intro-ctas .intro-help { width: 100%; }
    }

    /* ---- MOBILE: stack summary below form sections ---- */
    @media (max-width: 899px) {
        #main-form-content > aside.summary-panel {
            margin-top: 0;
        }
        /* DE HERO MOET DE CHROME VRIJHOUDEN.
           De merkbalk (.bar + .brand-lockup) ligt absoluut over de hero en is op
           een telefoon 78px hoog, tot y=108. De hero was 200px hoog met zijn
           inhoud tegen de ONDERKANT (justify-content: flex-end); paste die inhoud
           niet, dan liep hij aan de bovenkant het beeld uit — dwars door het
           woordmerk. Gemeten op 390px: de gele pil stond op y=38, dus 69px middenin
           de balk, en de titel overlapte de onderste 20px ervan.
           Twee dingen samen: `min-height` in plaats van een vaste `height`, zodat
           een langere titel de hero laat GROEIEN in plaats van eruit te lopen, en
           een padding-top die de balk reserveert. */
        .page-hero {
            height: auto;
            min-height: clamp(250px, 64vw, 300px);
        }
        .page-hero-content { padding-top: 120px; }
        .page-hero-overlay {
            background: linear-gradient(
                180deg,
                rgba(26,26,26,0.20) 0%,
                rgba(26,26,26,0.90) 100%
            );
        }
        .page-hero-content {
            justify-content: flex-end;
            padding-bottom: 1.5rem;
        }
    }
    @media (max-width: 600px) {
        .page-hero-facts { gap: 0.35rem 0.75rem; }
        .page-hero-fact { font-size: 0.82rem; }
    }

/* ================================================================
   Help panel — floating "Hulp?" button + slide-up support panel
   ================================================================ */
.help-float-btn {
    position: fixed;
    bottom: 1.75rem;
    right: 1.5rem;
    z-index: 1001;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.65rem 1.15rem;
    background: var(--brand-yellow);
    color: #111;
    border: none;
    border-radius: 2rem;
    font-size: 0.88rem;
    font-weight: 700;
    cursor: pointer;
    box-shadow: 0 4px 20px rgba(0,0,0,0.45);
    transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
    line-height: 1;
    white-space: nowrap;
}
.help-float-btn:hover {
    background: #ffe03a;
    transform: translateY(-2px);
    box-shadow: 0 6px 24px rgba(0,0,0,0.5);
}
.help-float-btn.is-open {
    background: #333;
    color: #fff;
}

.help-panel {
    position: fixed;
    bottom: 5.25rem;
    right: 1.5rem;
    z-index: 1000;
    width: 348px;
    max-width: calc(100vw - 2rem);
    max-height: calc(100dvh - 9rem);
    overflow-y: auto;
    /* Floats above a panel, so it takes the raised card tone, not the panel one. */
    background: var(--card-bg);
    border: 1px solid var(--booking-border-panel);
    border-radius: 16px;
    box-shadow:
        inset 0 1px 0 var(--booking-panel-edge),
        0 16px 56px rgba(0, 0, 0, 0.65);
    opacity: 0;
    visibility: hidden;
    transform: translateY(10px);
    transition: opacity 0.22s ease, transform 0.22s ease, visibility 0s 0.22s;
    overscroll-behavior: contain;
}
.help-panel.is-open {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition: opacity 0.22s ease, transform 0.22s ease, visibility 0s 0s;
}

.help-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1.1rem 1.25rem 0.9rem;
    border-bottom: 1px solid rgba(255,255,255,0.08);
    position: sticky;
    top: 0;
    /* Must stay OPAQUE and identical to .help-panel — it is the sticky mask the
       scrolled content passes under. */
    background: var(--card-bg);
    border-radius: 16px 16px 0 0;
    z-index: 1;
}
.help-panel-header h3 {
    margin: 0;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text-primary);
}
.help-close-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    background: rgba(255,255,255,0.08);
    border: none;
    border-radius: 50%;
    color: var(--text-secondary);
    cursor: pointer;
    transition: background 0.15s ease;
    flex-shrink: 0;
}
.help-close-btn:hover { background: rgba(255,255,255,0.15); color: var(--text-primary); }

.help-panel-body { padding: 1rem 1.25rem 1.35rem; }

.help-section-label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-secondary);
    margin: 0 0 0.75rem;
}

/* FAQ accordion */
.help-faq { margin-bottom: 1.5rem; }
.help-faq-item { border-bottom: 1px solid rgba(255,255,255,0.07); }
.help-faq-item:last-child { border-bottom: none; }
.help-faq-q {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.75rem 0;
    background: none;
    border: none;
    color: var(--text-primary);
    font-size: 0.88rem;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    transition: color 0.15s ease;
    line-height: 1.4;
}
.help-faq-q:hover { color: var(--brand-yellow); }
.help-faq-chevron {
    flex-shrink: 0;
    transition: transform 0.2s ease;
    color: var(--text-secondary);
}
.help-faq-q[aria-expanded="true"] .help-faq-chevron { transform: rotate(180deg); }
.help-faq-a {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.22s ease;
}
.help-faq-a.is-open { max-height: 200px; }
.help-faq-a p {
    margin: 0 0 0.85rem;
    font-size: 0.85rem;
    color: var(--text-secondary);
    line-height: 1.55;
}

/* Brouwer CTA section */
.help-brouwer { padding-top: 0.25rem; }
.help-brouwer-intro {
    font-size: 0.85rem;
    color: var(--text-secondary);
    line-height: 1.5;
    margin: 0 0 1rem;
}
.help-ctas { display: flex; flex-direction: column; gap: 0.6rem; }
.help-cta-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border-radius: 10px;
    font-size: 0.9rem;
    font-weight: 700;
    text-decoration: none;
    transition: transform 0.15s ease, opacity 0.15s ease;
    line-height: 1;
}
.help-cta-btn:hover { transform: translateY(-1px); opacity: 0.9; }
.help-cta-phone {
    background: var(--brand-yellow);
    color: #111;
}
.help-cta-whatsapp {
    background: #25D366;
    color: #fff;
}

@media (max-width: 480px) {
    .help-float-btn { bottom: 1.25rem; right: 1rem; }
    .help-panel { right: 0.75rem; bottom: 4.75rem; width: calc(100vw - 1.5rem); max-height: calc(100dvh - 8rem); }
}

/* ─── Upsell nudges ────────────────────────────────────── */
.upsell-nudge {
    display: none;
    /* Ruimer dan voorheen (0.65/0.8): sinds er een knop in staat drukte de tekst
       tegen de rand en de knop tegen de tekst. */
    padding: 0.8rem 1rem;
    background: rgba(255,217,0,0.07);
    border: 1px solid rgba(255,217,0,0.22);
    border-radius: 8px;
    font-size: 0.81rem;
    color: var(--text-secondary);
    line-height: 1.5;
    margin-bottom: 0.6rem;
}
.upsell-nudge strong { color: var(--brand-yellow); }
.upsell-nudge.is-visible { display: block; }

/* Knop in een upsell-nudge.
   Zonder deze knop was de nudge een doodlopende weg: hij vertelde de gast wat
   een upgrade kost en liet hem vervolgens zelf terugscrollen naar de
   pakketkaarten, zes secties hoger. Wie dat niet doorheeft, boekt gewoon door.

   Effen geel met donkere tekst, want dit staat op een donkere UI: een
   doorschijnende gele tint composit naar olijfgroen en haalt AA niet. */
.upsell-nudge-action {
    display: inline-block;
    /* De knop staat INLINE achter de tekst en liep daar tegenaan: één spatie is
       geen ruimte. Marge links maakt de knop los van de zin, marge boven vangt
       de regel op wanneer de tekst voor de knop afbreekt. */
    margin: 0.35rem 0 0.15rem 0.75rem;
    padding: 0.5rem 1rem;
    vertical-align: middle;
    background: var(--brand-yellow);
    color: var(--sbv-black, #111827);
    border: 0;
    border-radius: 6px;
    font: inherit;
    font-weight: 700;
    font-size: 0.8rem;
    line-height: 1.2;
    cursor: pointer;
    /* Knoplabels mogen nooit over twee regels: past de tekst niet, dan is de
       tekst te lang, niet de knop te smal. */
    white-space: nowrap;
    transition: background 0.15s ease, transform 0.15s ease;
}
.upsell-nudge-action:hover { background: #ffe23d; }
.upsell-nudge-action:active { transform: translateY(1px); }
.upsell-nudge-action:focus-visible {
    outline: 2px solid var(--brand-yellow);
    outline-offset: 2px;
}

/* ─── Bottle add-on section ────────────────────────────── */
.bottle-addon-section {
    display: none;
    padding: 0.7rem 0.8rem;
    background: rgba(255,255,255,0.04);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 8px;
    margin-bottom: 0.6rem;
}
.bottle-addon-section.is-visible { display: block; }
.bottle-addon-label {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    cursor: pointer;
}
.bottle-addon-label input[type="checkbox"] {
    margin-top: 0.18rem;
    width: 15px;
    height: 15px;
    flex-shrink: 0;
    accent-color: var(--brand-yellow);
    cursor: pointer;
}
.bottle-addon-title {
    display: block;
    font-size: 0.83rem;
    font-weight: 600;
    color: var(--text-primary);
    line-height: 1.3;
}
.bottle-addon-price {
    display: inline-block;
    font-size: 0.76rem;
    color: var(--brand-yellow);
    font-weight: 700;
    margin-top: 0.1rem;
}
.bottle-addon-desc {
    margin: 0.4rem 0 0 1.55rem;
    font-size: 0.76rem;
    color: var(--text-secondary);
    line-height: 1.45;
}
.bottle-addon-desc a { color: var(--brand-yellow); }

/* Proeverij snackkeuze — a single-axis radio pick (no tier ladder). */
.snack-choice-group {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 0.75rem;
}
.snack-choice-option {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.9rem 1.1rem;
    border: 2px solid var(--border-color);
    border-radius: 12px;
    cursor: pointer;
    background: var(--card-bg);
    color: var(--text-primary);
    transition: border-color 0.15s ease, background 0.15s ease;
}
.snack-choice-option:hover {
    border-color: var(--brand-yellow);
}
.snack-choice-option:has(input:checked) {
    border-color: var(--brand-yellow);
    background: rgba(255, 217, 0, 0.08);
}
.snack-choice-option:has(input:focus-visible) {
    outline: 2px solid var(--brand-yellow);
    outline-offset: 2px;
}
.snack-choice-option input {
    accent-color: var(--brand-yellow);
    width: 1.1rem;
    height: 1.1rem;
    flex: 0 0 auto;
}
.snack-choice-uplift {
    display: block;
    font-size: 0.78rem;
    color: var(--brand-yellow);
    font-weight: 600;
    margin-top: 0.15rem;
}
.snack-choice-note {
    margin: 0.6rem 0 0;
    font-size: 0.8rem;
    color: var(--text-secondary);
    line-height: 1.5;
}

/* Live price total (proeverij) — mirrors the server-computed total 1:1. */
.price-summary {
    margin-top: 1.1rem;
    padding: 0.9rem 1.1rem;
    border-radius: 12px;
    background: var(--card-bg);
    border: 1px solid var(--border-color);
}
.price-summary-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 0.9rem;
    color: var(--text-secondary);
    padding: 0.3rem 0;
}
.price-summary-total {
    margin-top: 0.4rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--border-color);
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text-primary);
}

/* ── Progressive step gating (shared; JS-driven via partials/step_gate.js) ──
   step_gate.js adds .step-locked to hide a step until the previous is complete,
   and .step-revealing for a brief fade-in. No HTML display:none, so no-JS keeps
   every step visible (progressive enhancement). Owner decision 2026-07-18. */
.step-locked { display: none !important; }

/* ── DE STAP KOMT BINNEN ──────────────────────────────────────────────────
   Dit is het ritme van de hele trechter: een gast maakt dit vier à vijf keer mee
   per boeking. Het was één blok dat 380ms opkwam; nu komt de sectie op en volgen
   de onderdelen ná elkaar, in de volgorde waarin je ze hoort te lezen. Dat is
   het verschil tussen "er verscheen een vak" en "er wordt iets voor je
   klaargezet".

   GEEN prefers-reduced-motion-tak meer, en dat is een correctie. Die stond hier
   wél, en daarmee zag de eigenaar — wiens besturingssysteem ongewild reduced
   motion meldt — de stappen gewoon knallen: `animation: none`, opacity meteen 1.
   De trechter voelde daardoor statisch terwijl de animatie er al maanden in zat.
   Zelfde val als bij het weerblok en de grillveeg; zie de regel in CONTEXT.md.
   Wat hier beweegt is bovendien decoratie: de inhoud verschijnt hoe dan ook, want
   het onthullen zelf is een display-schakelaar.

   DE DUUR HANGT AAN partials/step_gate.js: die haalt `.step-revealing` na 950ms
   weer weg en kapt daarmee alles af wat langer loopt. Verhoog je hier iets, kijk
   dan ook daar. */
.step-revealing {
    animation: stepReveal 420ms cubic-bezier(0.22, 0.9, 0.28, 1) both;
}
@keyframes stepReveal {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* De onderdelen erbinnen, één voor één. `both` zorgt dat een onderdeel tijdens
   zijn wachttijd al onzichtbaar staat — zonder dat stond alles er meteen en
   sprong het daarna alsnog. Let op: dit is een TIJDELIJKE klasse. De ruststand
   van elk onderdeel is gewoon zichtbaar, dus valt de animatie weg dan staat de
   stap er compleet; een `opacity: 0` in de ruststand is precies hoe hier eerder
   blokken permanent verdwenen. */
.step-revealing > *,
.step-revealing .collapsible-body > * {
    animation: stepChild 340ms cubic-bezier(0.22, 0.9, 0.28, 1) both;
}
@keyframes stepChild {
    from { opacity: 0; transform: translateY(9px); }
    to   { opacity: 1; transform: translateY(0); }
}
/* Trapsgewijs. Zes treden is genoeg: daarna leest het niet meer als volgorde
   maar als vertraging, en alles daarvoorbij krijgt dezelfde laatste trede. */
.step-revealing > *:nth-child(1),
.step-revealing .collapsible-body > *:nth-child(1) { animation-delay:  60ms; }
.step-revealing > *:nth-child(2),
.step-revealing .collapsible-body > *:nth-child(2) { animation-delay: 120ms; }
.step-revealing > *:nth-child(3),
.step-revealing .collapsible-body > *:nth-child(3) { animation-delay: 180ms; }
.step-revealing > *:nth-child(4),
.step-revealing .collapsible-body > *:nth-child(4) { animation-delay: 240ms; }
.step-revealing > *:nth-child(5),
.step-revealing .collapsible-body > *:nth-child(5) { animation-delay: 300ms; }
.step-revealing > *:nth-child(n+6),
.step-revealing .collapsible-body > *:nth-child(n+6) { animation-delay: 340ms; }

/* "Jouw selectie" hidden flag. The attribute means "nothing worth summarising yet";
   EACH PAGE decides its own condition, so do not re-point this selector at a
   page-specific one — plancha hides until the huisregels are done, workshop until a
   tier is chosen, low & slow until the first dish is ticked.
   Low & slow used to hide it until every mandatory dish was chosen (2026-08-12,
   reversed): that buried the one message explaining what was still missing inside a
   display:none element exactly when it applied, so the guest discovered the gap only
   by pressing "Boeking aanvragen" and meeting a modal — which read as a hard block.
   No attribute in the HTML → no-JS keeps the panel visible (progressive
   enhancement; a no-JS user can still submit). */
.summary-panel[data-selection-incomplete="true"] { display: none; }

/* Missing-course reminder. A NUDGE, not an error: skipping a course is allowed
   (see FR-044a), so this uses the amber "let op" register rather than the red
   error one, and it is a real button — clicking jumps to the category that is
   short. Full-width so the tap target matches the line the guest reads. */
.selection-completeness-warning { list-style: none; margin-top: 0.5rem; padding: 0; }
.selection-completeness-btn {
    display: block;
    width: 100%;
    text-align: left;
    font: inherit;
    font-size: 0.85rem;
    line-height: 1.45;
    /* Amber on the dark panel: judged against the COMPOSITED background, a light
       amber text on a low-alpha amber fill clears AA — the old #b45309 on
       rgba(15,23,42,.85) did not, and that slate fill was a token violation too. */
    color: #fcd34d;
    background: rgba(252, 211, 77, 0.10);
    border: 1px solid rgba(252, 211, 77, 0.45);
    border-radius: 0.375rem;
    padding: 0.55rem 0.75rem;
    cursor: pointer;
    text-wrap: pretty;
    transition: background-color 0.2s ease, border-color 0.2s ease;
}
.selection-completeness-btn:hover,
.selection-completeness-btn:focus-visible {
    background: rgba(252, 211, 77, 0.18);
    border-color: rgba(252, 211, 77, 0.75);
}
.selection-completeness-btn strong { color: var(--brand-yellow); }

/* ══════════════════════════════════════════════════════════════
   KLIMAATKAART — het weerblok van de trechter
   Markup: partials/weather_expectation.php · logica: js/weather_card.js
   Gebruikt door bbq_lowandslow.php, bbq_plancha.php en workshop.php; alle drie
   laden alleen dit bestand, dus dit is de enige plek waar dit blok staat.

   WAT HIER STOND. Twee meters onder elkaar met een gouden pijltje op een
   regenboogverloop, en daarboven "Regenkans 43%". Drie dingen mis:
    - dat percentage bestond niet. Het was (12+14)/2 gedeeld door 30,4 — de bron
      zegt "12 tot 14 natte dagen", en een gast leest "43%" als de kans op ZIJN
      avond;
    - de temperatuurschaal liep van -10° tot 40°, dus alle Nederlandse maanden
      lagen samengedrukt in het middelste derde deel;
    - regen kreeg een stripje, temperatuur de volle breedte. Voor een BBQ buiten
      is regen minstens zo bepalend.

   Nu: een lucht met een scrim, het gemiddelde als groot cijfer, de temperatuur als
   BAND over 0–30° (gemiddeld → warmste, geen pijl die één waarde aanwijst) en één
   staafje per dag van de maand — de ondergrens vol, de speling tot de bovengrens
   op halve sterkte. Zo staat de onzekerheid in het beeld in plaats van
   weggemiddeld tot één getal.

   REGEN IS BLAUW, en dat is een bewuste breuk met de warme paletregel van deze
   trechter. Besluit van de eigenaar (31-08-2026): functie boven vorm, de gast
   moet in een halve seconde zien welk deel over regen gaat. #6aa9e0 haalt
   6,8:1 tegen de gecomposite kaartachtergrond — ruim boven de 3:1 die WCAG voor
   betekenisdragende grafiek vraagt. Verander dit niet "voor de uniformiteit".
   ══════════════════════════════════════════════════════════════ */
.wx-card {
  position: relative;
  /* De maat komt van de KAART, niet van de wortel: deze trechter zet een eigen
     schaal op :root, en een cijfer in rem zou daar op precies deze pagina's
     kwart te groot uitvallen. Vandaar cqi hieronder. */
  container-type: inline-size;
  margin-top: 0.75rem;
  padding-bottom: 0.9rem;
  border: 1px solid var(--sbv-surface-edge, rgba(244, 239, 227, 0.09));
  border-radius: var(--sbv-radius-lg, 16px);
  background: var(--sbv-surface-2, rgba(38, 34, 26, 0.66));
  backdrop-filter: var(--sbv-blur, blur(14px));
  -webkit-backdrop-filter: var(--sbv-blur, blur(14px));
  box-shadow: var(--sbv-elev-2, 0 10px 30px rgba(0, 0, 0, 0.35));
  color: var(--text-primary, #f4efe3);
  /* GEEN overflow: hidden hier — de huisregels-gate zit in deze kaart. Alleen de
     lucht clipt zichzelf. */

  --wx-accent: #e2a33f;          /* zacht */
  --wx-sky-top: rgba(226, 163, 63, 0.14);
  --wx-slab: #1b1813;
}
.wx-card[data-wx="koud"] { --wx-accent: #c3b39a; --wx-sky-top: rgba(195, 179, 154, 0.10); }
.wx-card[data-wx="warm"] { --wx-accent: #ffd900; --wx-sky-top: rgba(255, 217, 0, 0.16); }

/* De kaart komt één keer omhoog zodra de datum gekozen is. Bewust een animatie op
   .is-in en GEEN begintoestand op .wx-card zelf: de kaart is standaard gewoon
   zichtbaar, dus als de animatie om welke reden dan ook niet draait staat het blok
   er alsnog. Een `opacity: 0` in de ruststand is precies hoe een blok hier eerder
   permanent onzichtbaar werd. */
.wx-card.is-in { animation: wx-kaart-in 0.5s cubic-bezier(0.22, 0.9, 0.28, 1); }
@keyframes wx-kaart-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* De lucht. Puur CSS-verloop — er hangt bewust geen canvas onder: dit is een
   boekingsformulier, en een tweede rAF-lus kost de gast frames tijdens scrollen en
   typen. De scrim is niet decoratief maar draagt de leesbaarheid: tekst
   compositeert altijd op een bekende ondergrond. Geen zon/wolk-icoon, want de bron
   kent graden en natte dagen — geen hemelconditie. */
.wx-sky {
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(112px, 34cqi, 156px);
  overflow: hidden;
  border-radius: var(--sbv-radius-lg, 16px) var(--sbv-radius-lg, 16px) 0 0;
  pointer-events: none;
  background: linear-gradient(180deg, var(--wx-sky-top), transparent 78%);
}
.wx-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(27, 24, 19, 0.30) 0%, rgba(27, 24, 19, 0.72) 62%, var(--wx-slab) 100%);
}
.wx-head, .wx-figure, .wx-band, .wx-days, .wx-advice { position: relative; z-index: 1; }

/* De kop staat op één regel. Hier stond tot 07-09-2026 ook de pil
   "KNMI-klimaatgemiddelde" naast; die is eruit, en daarmee ook de container query
   die de twee naast elkaar zette zodra de KAART breed genoeg was. */
.wx-head {
  padding: 0.85rem 1rem 0;
  display: grid;
  gap: 0.3rem;
  justify-items: start;
}
.wx-title { margin: 0; font-size: 0.95rem; font-weight: 600; white-space: nowrap; }

/* Het grote cijfer. cqi en niet rem, om dezelfde reden als container-type hierboven. */
.wx-figure { padding: 0.4rem 1rem 0.1rem; }
.wx-temp {
  margin: 0;
  font-size: clamp(44px, 22cqi, 76px);
  line-height: 0.92;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--wx-accent);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.55);
}
.wx-deg { font-size: 0.44em; vertical-align: 0.62em; }
.wx-sub { margin: 0.2rem 0 0; font-size: 0.8rem; color: var(--text-secondary, rgba(244, 239, 227, 0.75)); white-space: nowrap; }
.wx-sub strong { color: var(--text-primary, #f4efe3); }

/* De band: de rail is context, de vulling is de data. De rail haalt zijn dofheid
   uit rgba-stops en NIET uit `opacity` — anders dooft hij zijn eigen kind mee. */
.wx-band { padding: 0.6rem 1rem 0; }
.wx-rail {
  position: relative;
  height: 10px;
  border-radius: var(--sbv-radius-pill, 999px);
  background: linear-gradient(90deg,
    rgba(120, 104, 78, 0.30) 0%, rgba(184, 130, 60, 0.30) 55%, rgba(255, 217, 0, 0.30) 100%);
}
/* Schuift in bij elke datumwissel: de JS zet eerst left=right=het gemiddelde en
   daarna de echte grenzen, dus de band GROEIT vanaf het gemiddelde naar rechts. */
.wx-fill {
  position: absolute;
  top: -3px;
  bottom: -3px;
  left: var(--wx-from, 40%);
  right: var(--wx-to, 20%);
  border-radius: var(--sbv-radius-pill, 999px);
  background: var(--wx-accent);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35), 0 0 18px -2px var(--wx-accent);
  transition: left 0.42s cubic-bezier(0.22, 0.9, 0.28, 1), right 0.42s cubic-bezier(0.22, 0.9, 0.28, 1);
}
.wx-scale {
  display: flex;
  justify-content: space-between;
  margin-top: 0.3rem;
  font-size: 0.72rem;
  color: var(--text-secondary, rgba(244, 239, 227, 0.75));
}

/* Eén staafje per dag van de maand. grid-auto-flow: column vult de rij zonder dat
   het aantal dagen ergens hardgecodeerd staat — februari heeft er 28 of 29 en dat
   hoort vanzelf te kloppen. */
.wx-days { padding: 0.75rem 1rem 0; }
.wx-dots {
  display: grid;
  grid-auto-flow: column;
  gap: 3px;
  height: 14px;
  margin-bottom: 0.4rem;
}
.wx-day { border-radius: 2px; background: rgba(244, 239, 227, 0.14); }
/* De regenkleur staat hier, niet in --wx-accent: de kaart is warm, regen is blauw.
   Zie de kop van dit blok — dat is een besluit, geen slordigheid. */
.wx-day.is-nat { background: #6aa9e0; }
.wx-day.is-mss { background: rgba(106, 169, 224, 0.42); }

/* Blok voor blok: elk staafje krijgt van de JS een --i mee (zijn plek in de rij) en
   komt 22ms na zijn voorganger op. Eenendertig dagen zijn zo in ~0,9s compleet.
   `backwards` laat het staafje tijdens zijn wachttijd al de begintoestand aannemen —
   zonder dat zou de hele rij er eerst staan en dán pas gaan groeien. */
.wx-dots .wx-day {
  animation: wx-dag-op 0.32s cubic-bezier(0.2, 1.1, 0.35, 1) backwards;
  animation-delay: calc(var(--i, 0) * 22ms);
  transform-origin: bottom;
}
@keyframes wx-dag-op {
  from { opacity: 0; transform: scaleY(0.15); }
  to   { opacity: 1; transform: scaleY(1); }
}

/* De enige plek in dit blok waar tekst mag breken — het is een zin, geen label. */
.wx-advice {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0.85rem 1rem 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--sbv-surface-edge, rgba(244, 239, 227, 0.12));
  font-size: 0.85rem;
  line-height: 1.55;
  color: var(--text-secondary, rgba(244, 239, 227, 0.75));
  text-wrap: pretty;
}
.wx-advice .sbv-icon { flex: 0 0 auto; margin-top: 0.2rem; }
.wx-advice strong { color: var(--text-primary, #f4efe3); }

/* ==================================================================
   HET VUUR-OVERLAY bij de huisregels-gate (partials/fire_celebration.php,
   js/fire_celebration.js).
================================================================== */
.fire-fx {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: grid;
  place-items: center;
  padding: 20px;
}

.fire-fx[hidden] { display: none; }

.fire-fx-scrim {
  position: absolute;
  inset: 0;
  background: rgba(10, 9, 7, 0.72);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity 220ms ease;
}

.fire-fx.is-open .fire-fx-scrim { opacity: 1; }

.fire-fx-card {
  position: relative;
  width: min(420px, 100%);
  border-radius: 18px;
  overflow: hidden;
  background: var(--sbv-surface-2, #17150f);
  color: var(--text-primary, #f4efe3);
  border: 1px solid rgba(255, 217, 0, 0.35);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
  opacity: 0;
  /* De entree volgt de vonken: van onderen omhoog, en verder en langer dan een
     gewone kaart (14px/240ms), zodat hij uit het vuur lijkt te komen in plaats
     van erop te landen. Zie CARD_DELAY_MS in js/fire_celebration.js voor het
     moment waarop dit start. */
  transform: translateY(44px) scale(0.96);
  transition: opacity 320ms cubic-bezier(0.23, 1, 0.32, 1),
              transform 460ms cubic-bezier(0.23, 1, 0.32, 1);
}

.fire-fx.is-open .fire-fx-card { opacity: 1; transform: none; }

.fire-fx-photo { position: relative; }

.fire-fx-photo img {
  display: block;
  width: 100%;
  height: auto;
  /* De bron is vierkant; 3/2 met een centrale crop houdt de duim links en het
     vuur rechts allebei in beeld. 16/9 sneed er één van de twee af. */
  aspect-ratio: 3 / 2;
  object-fit: cover;
  object-position: center;
}

/* De vlammen lekken over de onderrand van de foto omhoog. Alleen transform en
   opacity — compositorwerk, geen canvas en geen tweede rAF-lus in een
   boekingsformulier. */
.fire-fx-flames {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 90px;
  display: flex;
  justify-content: space-around;
  align-items: flex-end;
  pointer-events: none;
  filter: blur(6px);
}

.fire-fx-flames span {
  width: 34px;
  height: 34px;
  border-radius: 50% 50% 20% 20%;
  background: radial-gradient(circle at 50% 80%, #fff3b0 0%, #ffb020 45%, rgba(255, 90, 0, 0) 72%);
  color: var(--text-primary, #f4efe3);
  transform: translateY(20px) scaleY(0.6);
  opacity: 0;
}

.fire-fx.is-open .fire-fx-flames span {
  animation: fire-fx-lick 1500ms ease-in-out infinite;
}

.fire-fx-flames span:nth-child(1) { animation-delay: 0ms; }
.fire-fx-flames span:nth-child(2) { animation-delay: 180ms; }
.fire-fx-flames span:nth-child(3) { animation-delay: 360ms; }
.fire-fx-flames span:nth-child(4) { animation-delay: 120ms; }
.fire-fx-flames span:nth-child(5) { animation-delay: 300ms; }
.fire-fx-flames span:nth-child(6) { animation-delay: 60ms; }

@keyframes fire-fx-lick {
  0%   { transform: translateY(24px) scaleY(0.55); opacity: 0; }
  30%  { opacity: 0.95; }
  100% { transform: translateY(-40px) scaleY(1.5); opacity: 0; }
}

/* Alles gecentreerd: dit is een viering, geen formulier. Eén middenas voor de
   titel, de regel eronder en de knop — links uitgelijnd las het als een blokje
   tekst met een knop eronder, en dat is precies niet wat dit moment is. */
.fire-fx-body {
  padding: 16px 18px 18px;
  background: var(--sbv-surface-2, #17150f);
  color: var(--text-primary, #f4efe3);
  text-align: center;
}

.fire-fx-title {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--brand-yellow, #ffd900);
}

.fire-fx-sub {
  margin: 0.3rem auto 0.9rem;
  max-width: 30ch; /* twee korte regels in plaats van één lange, rond de as */
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--text-secondary, rgba(244, 239, 227, 0.78));
}

.fire-fx-close {
  appearance: none;
  border: 1px solid rgba(255, 217, 0, 0.5);
  background: transparent;
  color: var(--brand-yellow, #ffd900);
  font: inherit;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.82rem;
  padding: 10px 20px;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 180ms ease, color 180ms ease, transform 140ms cubic-bezier(0.23, 1, 0.32, 1);
}

.fire-fx-close:active { transform: scale(0.97); }

@media (hover: hover) and (pointer: fine) {
  .fire-fx-close:hover { background: var(--brand-yellow, #ffd900); color: var(--dark-bg, #0f0e0b); }
}

/* Minder beweging: de kaart komt er gewoon te staan en de vlammen staan stil.
   Het overlay VERDWIJNT niet — het is de bevestiging van de pakketkeuze, en die
   mag niemand missen; alleen de beweging gaat eraf. */
@media (prefers-reduced-motion: reduce) {
  .fire-fx-scrim,
  .fire-fx-card { transition: none; }
  .fire-fx-card { opacity: 1; transform: none; }
  .fire-fx.is-open .fire-fx-flames span { animation: none; opacity: 0.8; transform: none; }
}

/* De huisregels-gate zit IN deze kaart (zie de valkuil in de partial). De haarlijn
   scheidt hem van het weerdeel zonder er een tweede kaart van te maken.
   Bewust op de gate zelf en niet als los divje ertussen: de gate wordt pas verderop
   in de trechter onthuld (inline display door de pagina-JS), en een losse lijn bleef
   tot dat moment als zwevende haarlijn met lege ruimte eronder in de kaart hangen.
   Zo kan de lijn per constructie niet zonder zijn inhoud bestaan. */
.wx-card > #toast-game-container {
  position: relative;
  z-index: 1;
  margin: 0.85rem 1rem 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--sbv-surface-edge, rgba(244, 239, 227, 0.12));
}

/* Kan de renderer niet geladen worden, dan blijven de lege cijfers weg maar
   MOET de gate zichtbaar blijven — zonder die vinkjes kan niemand boeken. */
.wx-card[data-wx-degraded="ja"] .wx-sky,
.wx-card[data-wx-degraded="ja"] .wx-figure,
.wx-card[data-wx-degraded="ja"] .wx-band,
.wx-card[data-wx-degraded="ja"] .wx-days,
.wx-card[data-wx-degraded="ja"] .wx-advice,
.wx-card[data-wx-degraded="ja"] .wx-head { display: none; }
.wx-card[data-wx-degraded="ja"] > #toast-game-container {
  margin-top: 0.85rem;
  padding-top: 0;
  border-top: 0;
}

/* ══════════════════════════════════════════════════════════════
   Telefoon (≤600px): breedte terugwinnen in de trechter.
   80% van de gasten boekt op een telefoon, en daar ging op 360px 39% van de
   schermbreedte op aan padding: container 20px, .form-section 25px en daar
   binnenin nog eens .menu-section met 30px. Een gerechtkaart hield 211px van de
   345px over — kleine foto's, smalle knoppen, meer scrollen.

   Staat bewust ONDERAAN dit bestand. Eerdere media-queries zetten dezelfde
   eigenschappen (@480 zet .container op 0.5rem, @520 zet .form-section op
   1.25rem) en een media-query telt niet mee voor specificiteit — alleen
   volgorde. Hoger in het bestand zou dit stil verliezen, net zoals die
   @480-regel nu al verliest van de @520-regel eronder.
   ══════════════════════════════════════════════════════════════ */
@media (max-width: 600px) {
    .container { padding-left: 0.75rem; padding-right: 0.75rem; }
    .form-section { padding: 1rem; }

    /* Paneel-in-paneel heeft op een telefoon geen doel: de buitenste sectie is de
       kaart al. Binnenin blijft de kop staan en markeert een haarlijn de grens,
       zodat de groepering leesbaar blijft zonder een tweede rand en tweede
       padding. */
    .form-section .menu-section {
        padding: 1.25rem 0 0;
        margin: 1.25rem 0 0;
        border: 0;
        border-top: 1px solid var(--border-color);
        border-radius: 0;
        background: none;
        box-shadow: none;
    }
    .form-section .menu-section:first-of-type {
        padding-top: 0;
        margin-top: 0;
        border-top: 0;
    }
}
