/* ==========================================================================
   U Jabka — Soda design system: HOMEPAGE
   Page-specific layout for resources/views/frontend/home.php. Loaded after
   tokens.css / base.css / components.css / shell.css, all of which it consumes
   and none of which it redeclares.

   Two rules govern this file:

   1. NO SCROLL LOCK, ANYWHERE. The approved prototype hid the whole page behind
      `body:not(.ready) { overflow: hidden }` until ~19 MB of GLB had downloaded,
      and its error path never removed it. Nothing here may ever set overflow
      on html or body, and no state class may gate page content on 3D.
   2. MOOD IS LOW-INTENSITY BELOW THE HERO. The per-category mood persists down
      the page, but only ever as glows (<= .10 alpha), borders, accent glyphs and
      hover states. Mood never colours body text and never sits behind text.
      Contrast is owned entirely by the static tokens in tokens.css.
   ========================================================================== */

/* ======================================================= 0. COLD-LOAD VEIL

   PORTED, NOT DESIGNED. The authorised 21st.dev loading overlay is the visual
   and choreographic authority for this block. Its literal numbers, transcribed:

     overlay    position fixed; top/left/right/bottom 0; z-index 11
     reveal     clip-path: inset(0 0 0% 0) -> inset(0 0 100% 0) — a WIPE UP
                from the bottom edge, .4s ease-in-out. NOT a fade.
     pointer    pointer-events go none once the clip starts
     counter    position absolute; right/bottom clamp(1rem, 2vw, 3rem);
                font-size clamp(3rem, 8vw, 12rem); text "<n>%"
     sequence   counter reaches 100 -> 100ms -> the clip (400ms) -> the page
                content reveals: opacity 0->1 and translateY(100px)->0 over
                "opacity .6s ease-out, transform .6s ease-out"
                (loader.js runs the sequence and that last half)

   FIVE DOCUMENTED DIFFERENCES, every one of them deliberate:

     1. z-index 200, not the source's 11. The Soda shell floats a fixed header
        at z-index 100 (shell.css); at 11 the veil would sit UNDER the header.
     2. pointer-events: none from birth, not the source's `auto` until the
        clip. The veil is documented as never being an obstacle — the page
        beneath it stays interactive for its whole life, and nothing here may
        gate page content (see rule 1 at the top of this file).
     3. The wipe is a keyframe animation rather than a `transition`, because
        the SAME declaration has to double as the pure-CSS failsafe that
        dismisses the veil with no JavaScript at all (2s hold + the source's
        .4s = 2.4s). Property, endpoints, duration and easing are the
        source's exactly; adding .is-done restarts it with no delay, which is
        precisely what the source's isClipping state flip does.
     4. The glossy-black ground and the branded "U Jabka" composition inside
        the overlay are the owner-approved difference: the source overlay
        declares no background and carries its own greeting instead.
     5. The readiness authority is loader.js, not the source's hardcoded
        2000ms demo timer. The percentage is a REAL readiness indicator.
   ========================================================================== */

html.theme-soda .soda-loader {
    /* Source: position fixed; top/left/right/bottom 0. */
    position: fixed;
    inset: 0;
    /* Difference 1. Source: z-index 11. */
    z-index: 200;

    /* Difference 4 — the approved branded ground and its centred lockup. */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: var(--vesper-void);

    /* Difference 2 — never an obstacle, at any point in the sequence. */
    pointer-events: none;

    /* Source: clip-path inset(0 0 0% 0) at rest. */
    clip-path: inset(0 0 0% 0);

    /* Difference 3 — the source's .4s ease-in-out clip, held 2s so that with
       no JavaScript at all the veil still leaves at 2.4s. The keyframe name
       is unchanged from the previous revision because a pinned browser
       assertion reads this shorthand out of the stylesheet to prove the
       failsafe is armed; only the MECHANIC inside it changed, fade -> clip. */
    animation: soda-loader-out var(--t21-dur-clip) var(--t21-ease-in-out) 2s forwards;
}

/* loader.js's dismissal: the same clip, no delay. Adding the class restarts
   the animation, so the veil leaves now instead of at the failsafe. */
html.theme-soda .soda-loader.is-done {
    /* THE KEYFRAME NAME MUST DIFFER FROM THE RESTING RULE'S. Re-declaring the
       same animation-name with only a different animation-delay does NOT
       restart an animation: the element keeps the running one and merely
       re-anchors its start 2s into the past, so it computes as already
       finished and the veil SNAPS instead of wiping. That loses the whole
       ported mechanic on the one path a visitor with JavaScript takes.
       A distinct name is a different animation, so it plays its full 400ms. */
    animation: soda-loader-wipe var(--t21-dur-clip) var(--t21-ease-in-out) forwards;
}

@keyframes soda-loader-out {
    from {
        clip-path: inset(0 0 0% 0);
    }

    to {
        clip-path: inset(0 0 100% 0);
        /* Not a visual addition — the clip has already emptied the box. It
           only takes the spent veil out of rendering and hit testing for
           good, which is what the source's unmount does. */
        visibility: hidden;
    }
}

/* Byte-identical to soda-loader-out; it exists only to carry a second name,
   for the restart reason documented above. */
@keyframes soda-loader-wipe {
    from {
        clip-path: inset(0 0 0% 0);
    }

    to {
        clip-path: inset(0 0 100% 0);
        visibility: hidden;
    }
}

/* ---- the percentage counter -------------------------------------------

   The source's three declarations, verbatim: the two clamps are ITS
   responsive rule and are deliberately not replaced by breakpoints. */

html.theme-soda .soda-loader__count {
    position: absolute;
    right: clamp(1rem, 2vw, 3rem);
    bottom: clamp(1rem, 2vw, 3rem);
    font-size: clamp(3rem, 8vw, 12rem);
    /* The source declares no colour and inherits the page foreground. Ours
       cannot: the veil's ground is branded black in BOTH surface themes,
       where the page foreground goes near-black. The veil's own chalk is
       used — the same ink the wordmark carries. No line-height is declared,
       exactly as in the source. */
    color: var(--vesper-chalk);
}

/* ---- the owner-approved brand composition ------------------------------

   The approved metal mark IS the "U"; the wordmark "Jabka" surfaces beside
   it in ONE smooth opacity + blur-to-sharp reveal, so the composition reads
   "U Jabka". Explicitly NOT a typewriter: no per-letter steps, no caret, no
   discrete J/Ja/Jab states. Both reveals finish at .67s, inside the shortest
   possible veil (450ms minimum hold + the source's 100ms settle = 550ms
   before the clip even starts), and keep sharpening through the wipe. */

html.theme-soda .soda-loader__brand {
    display: flex;
    align-items: center;
    gap: 1.1rem;
}

html.theme-soda .soda-loader__mark {
    width: 3.2rem;
    height: auto;
    animation: soda-loader-mark .5s var(--ease) both;
}

@keyframes soda-loader-mark {
    from {
        opacity: 0;
        transform: translateY(.4rem);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

html.theme-soda .soda-loader__word {
    font-family: var(--font-display);
    font-size: 1.9rem;
    font-weight: 500;
    letter-spacing: .04em;
    color: var(--vesper-chalk);
    animation: soda-loader-word .55s var(--ease) .12s both;
}

@keyframes soda-loader-word {
    from {
        opacity: 0;
        filter: blur(10px);
        transform: translateX(-.3rem);
    }

    to {
        opacity: 1;
        filter: blur(0);
        transform: none;
    }
}

/*
 * REDUCED MOTION — the delay has to go, not just the durations.
 *
 * base.css collapses animation-DURATION under this preference; it says
 * nothing about animation-DELAY. The veil would therefore sit fully opaque
 * for the whole failsafe hold and then vanish in a millisecond — with
 * JavaScript off, that is not a reduced experience, it is a two-second wait
 * for someone who asked for less. The failsafe stays (nothing here can block
 * permanently) but fires almost at once, and the mark/word delays collapse
 * too, so the reduced experience is one static branded frame — the finished
 * composition, never a masked sequence. loader.js skips the counter ramp and
 * the page-content reveal under the same preference.
 */
@media (prefers-reduced-motion: reduce) {
    html.theme-soda .soda-loader {
        animation-delay: .12s;
    }

    html.theme-soda .soda-loader__mark,
    html.theme-soda .soda-loader__word {
        animation-delay: 0s;
    }
}

/* ============================================================ 1. HERO SHELL

   Vesper's hero, transcribed from docs/vesper/src/views/home/hero/hero.tsx.

   ONE FULL-VIEWPORT PLANE WITH EVERYTHING ANCHORED TO ITS EDGES. Nothing is
   centred and nothing sits in a container: the H1 is pinned top-left at the
   24px gutter, and a 209px band is pinned to the floor carrying the tagline
   (left), the support line (right), the finish selector (bottom-left) and the
   CTA (bottom-right). The object owns the middle. That diagonal — headline
   top-left, CTA bottom-right, object between them — IS the composition.

   Every vw is Vesper's own Figma pixel / 14.4 at the 1440 base:
     1.667vw =  24px gutter       9.028vw = 130px  H1 top
     5.556vw =  80px display     41.875vw = 603px  H1 width
     2.153vw =  31px band floor  14.514vw = 209px  band height
   ========================================================================== */

html.theme-soda .soda-hero {
    position: relative;
    z-index: 6;
    /*
     * THE HERO IS ONE SCREEN THAT HOLDS, ON A TRACK THAT SCROLLS.
     *
     * It used to be exactly one viewport tall. It is now --hero-track tall with
     * a single sticky screen inside it (.soda-vine__frame), which is the
     * mechanic the ported ZoomParallax background needs: the track's scroll
     * travel is the camera's dolly, and the screen the visitor is looking at
     * does not move while that happens. The composition inside the screen is
     * untouched — same headline, same band, same subject, same places.
     *
     * The track length is the ONE number that tunes how long the vineyard is
     * held on screen. 250vh = the hero screen plus 1.5 screens of dolly.
     *
     * THE FIXED BAR STILL FLOATS OVER IT. `main` pads --header-band; the hero
     * cancels it and runs underneath so the ground reaches y=0. `dvh` rather
     * than `vh` on the screen itself: on mobile `vh` is the LARGEST viewport,
     * so with browser chrome shown the screen would overflow.
     */
    --hero-track: 250vh;

    margin-top: calc(-1 * var(--header-band));
    min-height: var(--hero-track);
    /* Contains the object; `clip` rather than `hidden` so it can never become a
       scroll container of its own — which would also break the sticky screen,
       because sticky sticks to its nearest scrollport. */
    overflow: clip;
    isolation: isolate;

    /*
     * VESPER'S GROUND, NOT THE MOOD WASH.
     *
     * The hero used to paint a per-category radial (orange / blue / green) that
     * repainted the whole first screen in the product mood. Vesper's ground is
     * one flat deep violet with its accent rationed to interaction. The
     * theme x mood BACKEND is untouched — resolver, cookie and <html data-mood>
     * all still work; the hero simply stops rendering mood as a full-bleed
     * surface, which is the rule the rest of the site already follows.
     */
    background: var(--vesper-void);
    color: var(--vesper-chalk);
}

/* ---------------------------------------------- the ambient vineyard layer

   THE BACKGROUND WAS SWAPPED (owner request, 2026-08-31). The 21st
   BeamsBackground port that used to sit here — a drifting, pulsing, twice
   blurred field of light beams — is gone, and with it its pulsing veil and
   its parked-loop rule. The ground is now the VINEYARD: vineyard.js draws
   rows of posts and wires receding to a vanishing point, trees grown branch
   by branch, and phone silhouettes ripening on their tips, then dollies the
   whole drawing forward on the hero's scroll track (the ZoomParallax
   mechanic). Nothing else about the hero changed: same copy, same places.

   THE THREE LAYERS THEMSELVES LIVE IN vineyard.css, because the preview page
   renders the identical component. This block only says what the HERO adds:
   the sticky screen's height (dvh, for mobile chrome) and the arrival of the
   subject below. Both layers stay decorative — aria-hidden,
   pointer-transparent, empty without JS — but they are no longer REMOVED
   under reduced motion: the drawing is the composition, so vineyard.js
   paints one finished frame instead of animating towards it. */

html.theme-soda .soda-hero .soda-vine__frame {
    /* `vh` first as the floor, then `dvh`: with browser chrome shown, `vh` is
       the largest viewport and the screen would overflow its own track. */
    height: 100vh;
    height: 100dvh;
}

/* -------------------------------------------------- the subject's arrival

   THE SUBJECT WAITS FOR THE DRAWING, then flies in.

   Layer 0 renders the poster visible, and that is still the contract: with
   this stylesheet's JS counterpart blocked, broken or refused, the stage is
   simply there from the first paint, exactly as before. The holding pattern
   is keyed on data-vineyard-waiting, which vineyard.js writes ONLY once it
   has actually started an animated draw — no JS, no attribute, no waiting.

   It is released by data-vineyard-released, which is set when the picture
   completes and, failing that, by vineyard.js's own wall-clock failsafe. The
   subject can therefore be late, but it can never be lost.

   WHAT MOVES, AND WHY IT IS NOT `transform`. The stage already owns its
   transform, and it owns a DIFFERENT one per breakpoint: translateY(-50%)
   while it is absolutely centred, `none` once the composition restacks into a
   column. Writing the arrival into `transform` would mean restating both
   states inside every breakpoint and getting the centring wrong the first time
   someone adds a third. The independent `translate` and `scale` properties
   compose with whatever transform is in force instead, so this block is
   breakpoint-agnostic: the object pushes in and scales up from wherever it
   happens to rest. No filter — a blur on a 58vw box is a full-screen
   compositing cost for a 900ms effect. */

html.theme-soda .soda-hero[data-vineyard-waiting] .soda-hero__stage {
    opacity: 0;
    translate: 9vw 3vh;
    scale: .86;
}

html.theme-soda .soda-hero[data-vineyard-waiting][data-vineyard-released] .soda-hero__stage {
    opacity: 1;
    translate: none;
    scale: none;
    transition:
        opacity 900ms var(--ease),
        translate 1200ms cubic-bezier(.16, .84, .28, 1),
        scale 1200ms cubic-bezier(.16, .84, .28, 1);
}

@media (prefers-reduced-motion: reduce) {
    /* vineyard.js does not hold the subject back at all under reduced motion —
       it never writes data-vineyard-waiting. This is the belt-and-braces half:
       if the attribute is ever present (a preference changed mid-session), the
       subject is present too, and it arrives without travelling. */
    html.theme-soda .soda-hero[data-vineyard-waiting] .soda-hero__stage {
        opacity: 1;
        translate: none;
        scale: none;
        transition: none;
    }
}

/* -------------------------------------------------------------- the object */

/*
 * ONE BOX, TWO LAYERS.
 *
 * .soda-hero__stage owns the geometry; the poster and the single canvas fill it
 * identically. That is what makes the handoff a crossfade IN PLACE at every
 * width — no relayout, no jump, and no second set of positioning rules that
 * could drift out of step with the first.
 *
 * Bleeding off the right edge is deliberate: the object is not a centred
 * illustration on a dark plate, it owns the frame the way Vesper's orb does.
 */
html.theme-soda .soda-hero__stage {
    position: absolute;
    z-index: 1;
    top: 50%;
    left: 50%;
    width: 58vw;
    /* The poster is a 600x600 SVG; the box is square so the canvas gets the
       same aspect and the subject is framed against a stable viewport. */
    aspect-ratio: 1;
    transform: translateY(-50%);
    /* The stage is not a target; only the canvas takes the pointer, and only
       once there is something on it to turn. */
    pointer-events: none;
    user-select: none;
}

html.theme-soda .soda-hero__object {
    display: block;
    width: 100%;
    max-width: none;
    height: 100%;
    object-fit: contain;
    /* The poster is the floor of the composition: it is visible until a real
       frame has been drawn, and it comes straight back if one never is. */
    opacity: 1;
    transition: opacity var(--dur-slow) var(--ease);
}

html.theme-soda .soda-hero__canvas {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    opacity: 0;
    transition: opacity var(--dur-slow) var(--ease);
    /*
     * TOUCH MUST NOT TRAP THE PAGE.
     *
     * `pan-y` hands every vertical gesture to the document, so a swipe over the
     * subject scrolls the page exactly as it does everywhere else, and only the
     * horizontal component reaches the renderer. This is why hero-scene.js
     * never calls preventDefault on a pointer move — cancelling the event is
     * the other way to get horizontal drag, and it is the way that eats the
     * scroll.
     */
    touch-action: pan-y;
}

/* ============================================ image-accordion hero subject

   The alternative to the renderer, selected by hero.subject_mode. Same stage
   box, same composition, same poster underneath — only what fills the box
   changes, which is what makes the two modes comparable at all.

   ZERO SCRIPT. The panels are a native radio group: one visually-hidden input
   per finish followed by its label, and `:checked + label` does the expanding.
   Arrow keys move between them because that is what a radio group does, a tap
   checks one, hover is a preview and never the only way in, and the whole thing
   works with JavaScript disabled. The accordion mode therefore ships no
   renderer, no three.js, no canvas and no module of its own.

   RESTRAINED, NOT ELASTIC. One flex-basis transition on the panel and one
   opacity transition on its label. No spring, no scale, no shadow, no
   permanent animation — under prefers-reduced-motion the panels simply snap.
*/

/* THE STAGE, RE-FRAMED FOR PANELS. Two things the 3D subject can afford and an
   accordion cannot:

   It BLEEDS off the right edge — fine for one object whose far shoulder nobody
   misses, fatal for a row of panels, because the one that falls off the edge is
   a finish the visitor never learns exists. So the stage is pulled back inside
   the gutter.

   It is SQUARE AND TALL, and the lower band floats over it. Over a dark ground
   with a floating object that reads fine; over three opaque white panels the
   support line and the CTA land on photographs. So above 1024 the accordion
   stage is shorter and sits higher, and it clears the band's top edge instead
   of being overlapped by it — measured, not estimated. */
html.theme-soda .soda-hero__stage[data-hero-mode="image_accordion"] {
    left: auto;
    right: 1.667vw;
    width: 50vw;
}

@media (min-width: 1024px) {
    html.theme-soda .soda-hero__stage[data-hero-mode="image_accordion"] {
        top: 42%;
        aspect-ratio: 5 / 4;
        max-height: 58vh;
    }
}

html.theme-soda .soda-accordion {
    position: absolute;
    inset: 0;
    display: flex;
    gap: .6vw;
    /* The stage is pointer-transparent so the copy beside it stays clickable;
       the accordion is the one thing inside it that is operable. */
    pointer-events: auto;
}

html.theme-soda .soda-accordion__panel {
    position: relative;
    display: flex;
    align-items: flex-end;
    flex: 1 1 0;
    min-width: 0;
    overflow: hidden;
    border-radius: var(--r-card);
    /* A white plane, exactly like every other surface a product sits on in this
       system — the imported renders come on white, so the panel and the
       photograph are one surface rather than a picture in a frame. */
    background: #fff;
    cursor: pointer;
    transition: flex-grow var(--dur-slow) var(--ease);
}

/* The open panel takes roughly three quarters of the row; the others stay as
   narrow previews rather than collapsing to slivers you cannot read. */
html.theme-soda .soda-accordion__input:checked + .soda-accordion__panel {
    flex-grow: 3;
}

html.theme-soda .soda-accordion__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* Cover, not contain: a narrow preview panel showing a whole letterboxed
       phone would be a stamp. The renders are centred, so the crop keeps the
       device and loses the white margin. */
    object-fit: cover;
    /* The renders are a rear/front PAIR on one square canvas, and the panels are
       portrait. Framing at 30% puts the rear device — the view that carries the
       finish, which is what these panels are choosing between — in the middle of
       an open panel, and keeps a body rather than an edge in a closed one. */
    object-position: 30% center;
}

html.theme-soda .soda-accordion__name {
    position: relative;
    z-index: 1;
    margin: .8vw;
    padding: .35em .7em;
    border-radius: var(--r-mark);
    /* The renders are white-ground, so the label needs its own dark chip rather
       than a scrim over the whole panel. */
    background: rgba(0, 0, 0, .78);
    color: #fff;
    font-size: var(--text-small);
    line-height: 1.2;
    white-space: nowrap;
    opacity: 0;
    transition: opacity var(--dur) var(--ease);
}

/* The name appears on the open panel, and on hover or keyboard focus of a
   closed one, so you can read what you are about to choose before choosing it. */
html.theme-soda .soda-accordion__input:checked + .soda-accordion__panel .soda-accordion__name,
html.theme-soda .soda-accordion__panel:hover .soda-accordion__name,
html.theme-soda .soda-accordion__input:focus-visible + .soda-accordion__panel .soda-accordion__name {
    opacity: 1;
}

/* The focus ring lives on the label, because the input it belongs to is clipped
   to a pixel and would show a ring nobody can see. */
html.theme-soda .soda-accordion__input:focus-visible + .soda-accordion__panel {
    outline: var(--focus-ring) solid #fff;
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    html.theme-soda .soda-accordion__panel,
    html.theme-soda .soda-accordion__name {
        transition: none;
    }
}

/* Below 1024 the stage becomes a centred square and the row would give each
   panel about 60 px. Two panels of preview is enough to show there is a choice
   without any of them being unreadable. */
@media (max-width: 1023.98px) {
    /* The shared mobile reframing turns the stage into a small centred square,
       which is right for one floating object and wasteful for a row of panels:
       at 390 it left three 36px slivers with 300px of empty ground under them.
       The accordion takes a portrait box instead, sized to the space that is
       actually free between the headline and the band. */
    html.theme-soda .soda-hero__stage[data-hero-mode="image_accordion"] {
        /* Size only. Below 1024 the shared rule puts the stage back in FLOW
           (position: relative), so top/left/transform here would be offsets
           from its static position rather than from the hero — the first
           attempt at re-centring it landed a 439px box at y -42 on a 390px
           viewport, clipped by the top of the page. Changing the box and
           letting the flow place it keeps the headline above and the band
           below, which is what the 3D stage already does here. */
        width: min(86vw, 26rem);
        aspect-ratio: 3 / 4;
        max-height: 52vh;
    }

    html.theme-soda .soda-accordion {
        gap: .5rem;
    }

    html.theme-soda .soda-accordion__input:checked + .soda-accordion__panel {
        flex-grow: 3;
    }

    html.theme-soda .soda-accordion__name {
        margin: .5rem;
        font-size: var(--text-label);
    }
}

/*
 * THE HANDOFF. `data-stage-state="live"` is written by hero-controller.js only
 * after createHeroScene() has resolved, which happens only after the model has
 * loaded, every texture has decoded and the scene has rendered one real frame.
 * No other state reveals the canvas, so a boot that never finishes, a model
 * that never arrives, a texture that never decodes and a lost WebGL context all
 * land on the same safe outcome: the poster, still there.
 */
html.theme-soda .soda-hero[data-stage-state="live"] .soda-hero__object {
    opacity: 0;
}

html.theme-soda .soda-hero[data-stage-state="live"] .soda-hero__canvas {
    opacity: 1;
    pointer-events: auto;
    cursor: grab;
}

html.theme-soda .soda-hero[data-stage-state="live"] .soda-hero__canvas:active {
    cursor: grabbing;
}

/* Reduced motion, and a restored context: arrive, do not fade. */
html.theme-soda .soda-hero[data-stage-instant] .soda-hero__object,
html.theme-soda .soda-hero[data-stage-instant] .soda-hero__canvas {
    transition: none;
}

/* ---------------------------------------------------------------- the frame */

html.theme-soda .soda-hero__frame {
    position: relative;
    z-index: 2;
    min-height: 100vh;
    min-height: 100dvh;
    /*
     * TRANSPARENT TO THE POINTER, and this is load-bearing.
     *
     * The frame is a full-viewport box stacked ABOVE the stage, so without this
     * it wins the hit test over the whole hero and the 3D subject can never be
     * dragged — the canvas is visible, interactive in every other respect, and
     * completely unreachable. Its own children take the pointer back below.
     */
    pointer-events: none;
}

html.theme-soda .soda-hero__title,
html.theme-soda .soda-hero__band {
    pointer-events: auto;
}

html.theme-soda .soda-hero__title {
    position: absolute;
    top: 9.028vw;
    left: var(--gutter);
    width: 41.875vw;
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-display);
    font-weight: 300;
    line-height: var(--leading-title);
    letter-spacing: var(--tracking-title);
    color: var(--vesper-chalk);
    /* Descender protection — see .soda-sec-title in base.css. */
    padding-bottom: .14em;
    margin-bottom: -.14em;
}

/* ---------------------------------------------------------- the bottom band */

html.theme-soda .soda-hero__band {
    position: absolute;
    right: var(--gutter);
    bottom: 2.153vw;
    left: var(--gutter);
    height: 14.514vw;
}

/* Uppercase, left, 27.5vw — Vesper's tagline slot. */
html.theme-soda .soda-hero__tagline {
    position: absolute;
    top: 0;
    left: 0;
    width: 27.5vw;
    margin: 0;
    font-size: var(--text-body);
    line-height: var(--leading-body);
    letter-spacing: .02em;
    text-transform: uppercase;
    color: var(--vesper-chalk);
}

/* Sentence case, right, 19.348vw — Vesper's support slot. */
html.theme-soda .soda-hero__support {
    position: absolute;
    top: 0;
    right: 0;
    width: 19.348vw;
    margin: 0;
    font-size: var(--text-body);
    line-height: var(--leading-body);
    color: rgba(253, 253, 253, .72);
}

/* --------------------------------------------------------- finish selector */

/*
 * Vesper's tag-row slot: bottom-left of the band, a row of small square units.
 * No pills, no shadow, no glow — a hairline box, a square chip, a label. The
 * current finish is marked by ink and by its border firming to the signal.
 */
html.theme-soda .soda-finish {
    position: absolute;
    top: 10.4vw;
    left: 0;
    display: flex;
    align-items: stretch;
    gap: .694vw;
}

html.theme-soda .soda-finish__opt {
    display: inline-flex;
    align-items: center;
    gap: .556vw;
    padding: .486vw .833vw;
    border: 1px solid rgba(253, 253, 253, .22);
    border-radius: var(--r-control);
    background: none;
    color: rgba(253, 253, 253, .66);
    font-family: var(--font-display);
    font-size: var(--text-body);
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
    /* Colour and border only. Nothing scales, nothing lifts. */
    transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

html.theme-soda .soda-finish__opt:hover {
    color: var(--vesper-chalk);
    border-color: rgba(253, 253, 253, .5);
}

html.theme-soda .soda-finish__opt.is-current {
    color: var(--vesper-chalk);
    border-color: var(--vesper-signal);
}

/* The finish's own colour, and the ONLY place it appears. Never an accent,
   never a surface, never behind text. */
html.theme-soda .soda-finish__chip {
    display: block;
    width: .833vw;
    height: .833vw;
    flex: none;
    border: 1px solid rgba(0, 0, 0, .35);
    border-radius: var(--r-mark);
}

/* The three real iPhone 17 Pro Max finishes (corrective pass 2026-08). */
html.theme-soda .soda-finish__chip--cosmic-orange { background: #c65f2e; }
html.theme-soda .soda-finish__chip--silver        { background: #e2e3e5; }
html.theme-soda .soda-finish__chip--deep-blue     { background: #2c3a55; }

/* --------------------------------------------------------------------- CTA */

/*
 * Vesper's Send Request: a filled block plus a SEPARATE SQUARE TILE beside it,
 * 4px apart, the tile as tall as the block. Never a pill with an inline circle.
 */
html.theme-soda .soda-hero__cta {
    position: absolute;
    top: 7.7vw;
    right: 0;
    display: inline-flex;
    align-items: stretch;
    gap: .278vw;
    text-decoration: none;
}

html.theme-soda .soda-hero__cta-label {
    display: flex;
    align-items: center;
    padding: 1.111vw 4.167vw;
    background: var(--vesper-chalk);
    color: #000;
    font-family: var(--font-display);
    font-size: var(--text-body);
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
    transition: background var(--dur) var(--ease);
}

html.theme-soda .soda-hero__cta-tile {
    display: grid;
    place-items: center;
    width: 3.542vw;
    background: var(--vesper-chalk);
    color: #000;
    font-size: var(--text-body);
    line-height: 1;
    transition: background var(--dur) var(--ease);
}

html.theme-soda .soda-hero__cta:hover .soda-hero__cta-label,
html.theme-soda .soda-hero__cta:focus-visible .soda-hero__cta-label,
html.theme-soda .soda-hero__cta:hover .soda-hero__cta-tile,
html.theme-soda .soda-hero__cta:focus-visible .soda-hero__cta-tile {
    background: var(--vesper-signal);
}

/* ---------------------------------------------------- hero below 1024px

   Vesper ADR-0029: the vw ladder stops and the artboard is replaced by an
   AUTHORED STACK — the offsets above are meaningless here, not merely small.

   The composition survives the translation because its intent does: the
   headline still leads, the object is still the largest thing on the screen,
   and the CTA is still the last thing before the fold. What changes is that the
   band stops being pinned and becomes flow, which is exactly what Vesper does
   to its own hero band.

   READABILITY DEVIATION FROM VESPER (deliberate, U Jabka-specific): Vesper
   authors body copy at 1.111vw, which is ~11px between 1024 and 1440. We floor
   body/UI text at 15px through --text-body in tokens.css, so nothing in this
   composition can fall below a readable size at any width.
   -------------------------------------------------------------------------- */

@media (max-width: 1023.98px) {
    /*
     * NO TRACK, NO STICKY, ON A PHONE.
     *
     * The dolly needs scroll travel the hero can afford to spend. A phone
     * cannot: the composition restacks into a column that already fills the
     * screen, so a 250vh track would be one-and-a-half screens of scrolling
     * past a held frame before the first section — and a fixed-height sticky
     * screen would clip the stacked column on a short viewport.
     *
     * So the track collapses to one screen and the frame stops sticking. The
     * vineyard is still drawn, still grows, and the subject still waits for it;
     * only the camera stops moving, because there is nothing to move it with.
     */
    html.theme-soda .soda-hero {
        --hero-track: 100vh;

        min-height: 100dvh;
    }

    html.theme-soda .soda-hero .soda-vine__frame {
        position: relative;
        top: auto;
        height: auto;
        min-height: 100vh;
        min-height: 100dvh;

        display: flex;
        flex-direction: column;
        /* The object sits between the headline and the band rather than behind
           them: at this width an object bleeding off-edge behind text is not
           dramatic, it is unreadable. */
        justify-content: flex-start;
        padding: calc(var(--header-band) + 1.5rem) var(--gutter) 2rem;
        gap: 1.5rem;
    }

    html.theme-soda .soda-hero__stage {
        position: relative;
        z-index: 1;
        order: 2;
        top: auto;
        left: auto;
        width: min(78vw, 26rem);
        margin: 0 auto;
        transform: none;
    }

    /*
     * THE COPY HAS TO OUTRANK THE GROUND HERE, EXPLICITLY.
     *
     * .soda-hero__frame is `display: contents` at this width, so the headline
     * and the band are flex items of the screen itself, in flow and unpositioned
     * — and unpositioned in-flow content paints BELOW positioned siblings, which
     * is exactly what the three background layers are. The desktop composition
     * never hits this because the frame keeps its own z-index: 2.
     *
     * The old beams veil hid the bug by being 95% transparent. The vineyard's
     * ground is opaque, so it would have painted straight over the headline.
     */
    html.theme-soda .soda-hero__title,
    html.theme-soda .soda-hero__band {
        position: relative;
        z-index: 1;
    }

    html.theme-soda .soda-hero__frame {
        display: contents;
    }

    html.theme-soda .soda-hero__title {
        position: static;
        order: 1;
        width: 100%;
        font-size: var(--text-display);
    }

    html.theme-soda .soda-hero__band {
        position: static;
        order: 3;
        display: flex;
        flex-direction: column;
        gap: 1rem;
        width: 100%;
        height: auto;
        margin-top: auto;
    }

    html.theme-soda .soda-hero__tagline,
    html.theme-soda .soda-hero__support {
        position: static;
        width: 100%;
    }

    html.theme-soda .soda-finish {
        position: static;
        flex-wrap: wrap;
        gap: .5rem;
    }

    html.theme-soda .soda-finish__opt {
        gap: .5rem;
        /* Touch target: 44px tall regardless of the label's own metrics. */
        min-height: 2.75rem;
        padding: .5rem .75rem;
        font-size: .875rem;
    }

    html.theme-soda .soda-finish__chip {
        width: .75rem;
        height: .75rem;
    }

    html.theme-soda .soda-hero__cta {
        position: static;
        gap: .25rem;
        width: 100%;
    }

    html.theme-soda .soda-hero__cta-label {
        flex: 1;
        justify-content: center;
        padding: .9375rem 1.5rem;
        font-size: 1rem;
    }

    html.theme-soda .soda-hero__cta-tile {
        width: 3.25rem;
        flex: none;
        font-size: 1rem;
    }
}

@media (max-width: 640px) {
    html.theme-soda .soda-hero .soda-vine__frame {
        padding: calc(var(--header-band) + 1rem) var(--gutter) 1.5rem;
        gap: 1.25rem;
    }

    html.theme-soda .soda-hero__stage {
        width: min(82vw, 18rem);
    }

    /* One finish per row would be four rows of chrome above the CTA; two
       columns keeps the selector to two rows and the CTA above the fold. */
    html.theme-soda .soda-finish {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: .5rem;
    }

    html.theme-soda .soda-finish__opt {
        justify-content: flex-start;
        font-size: .8125rem;
    }
}

/* ============================================ 2. THE WHITE VESPER PLATE

   docs/vesper/src/views/home/sections/financial-section.tsx, transcribed.

   An opaque WHITE card inset 24px from both viewport edges, square, hairline
   bordered, floating over the dark ground — and the dark ground stays visible
   around it. That surround is the composition, which is why the plate is not a
   full-width white page section.

     96.667vw = 1392px card width      2.222vw = 32px card padding
   ========================================================================== */

html.theme-soda .soda-plate {
    position: relative;
    z-index: 6;
    /* The ground the card floats over. Vesper's, not the surface theme's: the
       cards and the hero are one continuous dark plane. */
    background: var(--vesper-void);
    padding-block: var(--gutter);
}

/* Consecutive plates must not double their gutter into a 48px seam. */
html.theme-soda .soda-plate + .soda-plate {
    padding-top: 0;
}

/* The SURFACE — opaque white, square, one hairline, no shadow — is not
   declared here any more: .soda-card-w is a member of the .soda-plane group in
   components.css, which supplies it (and wins on specificity, so these four
   declarations were already dead). What is left is this card's own geometry. */
html.theme-soda .soda-card-w {
    width: var(--container);
    max-width: calc(100% - var(--gutter) * 2);
    margin-inline: auto;
    padding: 2.222vw;
}

html.theme-soda .soda-card-w__title {
    margin: 0 0 2.222vw;
    font-family: var(--font-display);
    font-size: var(--text-display);
    font-weight: 300;
    line-height: var(--leading-title);
    letter-spacing: var(--tracking-title);
    color: #000;
    /* Descender protection — see .soda-sec-title in base.css. */
    padding-bottom: .14em;
    margin-bottom: calc(2.222vw - .14em);
}

html.theme-soda .soda-card-w__foot {
    margin: 2.222vw 0 0;
    font-size: var(--text-body);
}

html.theme-soda .soda-card-w__foot a {
    color: #000;
    text-decoration: underline;
    text-underline-offset: .18em;
    text-decoration-color: rgba(0, 0, 0, .3);
    transition: text-decoration-color var(--dur) var(--ease);
}

html.theme-soda .soda-card-w__foot a:hover {
    text-decoration-color: #000;
}

/* ------------------------------------------------- the category selector

   SOURCE-FAITHFUL PORT of 21st @minhxthanh/components/interactive-selector
   (docs/component-prompts/homepage-category-carousel.md). Every timing below
   that reads .7s ease-in-out is the source's own `transition-all duration-700
   ease-in-out`, and every mechanic is the source's:

     flex      active panel flex-grow 7 : 1, driven by .is-active (ui.js
               moves it on click — the source's handleOptionClick);
     image     backgroundSize auto 120% -> auto 100% on activation, ported as
               a scale(1.2 -> 1) on the cover image;
     shade     the inset-shadow block rising bottom -40px -> 0;
     label     icon chip + main/sub, opacity 0 / translateX(25px) -> in;
     border    2px, #292929 at rest, #fff on the active panel;
     entrance  opacity 0 / translateX(-60px), staggered 180ms per panel
               (ui.js adds .is-entered; under reduced motion the row simply
               stands).

   NOT TAKEN, with permission: the cast box-shadows (depth here is a hairline
   and the moving shade), the react-icons glyphs (house glyphs instead) and
   the #222 page ground (the panels keep their own dark ground on the white
   plate, as the source's panels do on #222).

   NO-JS FALLBACK: without ui.js there is no click selection, so the hover /
   focus preview the previous implementation used remains — scoped under
   html:not(.js-soda) — and the server-marked panel stands open. With JS the
   source's click-driven behaviour replaces it exactly. */

html.theme-soda .soda-catsel {
    display: flex;
    align-items: stretch;
    gap: 0;
    margin: 0;
    padding: 0;
    list-style: none;
    /* The source's h-[400px], scaled to the plate (400/1440). */
    height: clamp(320px, 27.8vw, 420px);
}

html.theme-soda .soda-catsel__cell {
    display: flex;
    flex: 1 1 0%;
    min-width: 60px;
    margin: 0;
    transition: flex-grow .7s ease-in-out;
}

html.theme-soda .soda-catsel__cell:has(.is-active) {
    flex-grow: 7;
}

html.theme-soda .soda-catsel__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    padding: 0;
    border-radius: var(--r-none);
    /* The source's 2px border: dark at rest, white on the active panel. */
    border: 2px solid #292929;
    background: #101013;
    color: #fff;
    text-decoration: none;
    cursor: pointer;
    transition: border-color .7s ease-in-out;
}

html.theme-soda .soda-catsel__panel.is-active {
    border-color: #fff;
}

/* Keyboard parity: a focused panel opens exactly as a clicked one, and the
   focus ring is the panel's own border brightening. */
html.theme-soda .soda-catsel__cell:focus-within {
    flex-grow: 7;
}

html.theme-soda .soda-catsel__panel:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 3px;
}

/* NO-JS ONLY: hover previews a panel, the pinned one yields. With ui.js
   running the source's click mechanic owns the row and hover moves nothing. */
@media (hover: hover) {
    html.theme-soda:not(.js-soda) .soda-catsel:hover .soda-catsel__cell:has(.is-active) {
        flex-grow: 1;
    }

    html.theme-soda:not(.js-soda) .soda-catsel__cell:hover {
        flex-grow: 7;
    }
}

/* The cover image. The source zooms its background from 120% to 100% as the
   panel opens; object-fit cover + scale is the same picture. */
html.theme-soda .soda-catsel__art {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transform: scale(1.2);
    transition: transform .7s ease-in-out;
}

html.theme-soda .soda-catsel__panel.is-active .soda-catsel__art {
    transform: scale(1);
}

/* Accessories ships no poster, so it uses its own glyph rather than borrowing
   another category's artwork. */
html.theme-soda .soda-catsel__art--glyph {
    display: grid;
    place-items: center;
    color: rgba(255, 255, 255, .38);
    transform: none;
}

html.theme-soda .soda-catsel__art--glyph svg {
    width: 34%;
    height: 34%;
    max-width: 7rem;
}

/* The source's shadow block: a 120px band of inset darkness that sits 40px
   below the frame at rest and RISES INTO the active panel. */
html.theme-soda .soda-catsel__shade {
    position: absolute;
    left: 0;
    right: 0;
    bottom: -40px;
    height: 120px;
    pointer-events: none;
    box-shadow: inset 0 -120px 0px -120px #000, inset 0 -120px 0px -80px #000;
    transition: bottom .7s ease-in-out, box-shadow .7s ease-in-out;
}

html.theme-soda .soda-catsel__panel.is-active .soda-catsel__shade {
    bottom: 0;
    box-shadow: inset 0 -120px 120px -120px #000, inset 0 -120px 120px -80px #000;
}

/* The label row: the source's absolute bottom strip — icon chip always
   present, text revealed by activation. */
html.theme-soda .soda-catsel__label {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 1.25rem;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: .75rem;
    min-width: 0;
    padding: 0 1rem;
    pointer-events: none;
}

/* The source's chip, verbatim: 44px round, translucent dark, 10px backdrop
   blur, 2px #444 ring. */
html.theme-soda .soda-catsel__chip {
    display: grid;
    place-items: center;
    flex: none;
    width: 44px;
    height: 44px;
    border-radius: var(--r-pill);
    border: 2px solid #444;
    background: rgba(32, 32, 32, .85);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    color: #fff;
}

html.theme-soda .soda-catsel__chip svg {
    width: 24px;
    height: 24px;
}

html.theme-soda .soda-catsel__info {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    min-width: 0;
    /* The source's reveal: hidden and offset until the panel opens. */
    opacity: 0;
    transform: translateX(25px);
    transition: opacity .7s ease-in-out, transform .7s ease-in-out;
}

html.theme-soda .soda-catsel__panel.is-active .soda-catsel__info,
html.theme-soda:not(.js-soda) .soda-catsel__cell:hover .soda-catsel__info,
html.theme-soda .soda-catsel__cell:focus-within .soda-catsel__info {
    opacity: 1;
    transform: none;
}

html.theme-soda .soda-catsel__name {
    font-family: var(--font-display);
    font-size: 1.125rem;
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
    color: #fff;
}

html.theme-soda .soda-catsel__note {
    font-size: var(--text-small);
    line-height: 1.2;
    color: rgba(255, 255, 255, .72);
    white-space: nowrap;
}

/* THE ENTRANCE — the source's slideFadeIn, staged by ui.js at 180ms per
   panel: each panel starts hidden-left and .is-entered releases it. Until JS
   runs (or under reduced motion) nothing is hidden. */
html.theme-soda.js-soda .soda-catsel[data-soda-catsel-armed] .soda-catsel__panel {
    opacity: 0;
    transform: translateX(-60px);
    transition: opacity .7s ease-in-out, transform .7s ease-in-out,
        border-color .7s ease-in-out, flex-grow .7s ease-in-out;
}

html.theme-soda.js-soda .soda-catsel[data-soda-catsel-armed] .soda-catsel__panel.is-entered {
    opacity: 1;
    transform: none;
}

/* An unavailable category is a plain panel, never a link, and says so. */
html.theme-soda .soda-catsel__panel--upcoming .soda-catsel__note {
    color: rgba(255, 255, 255, .55);
}

@media (prefers-reduced-motion: reduce) {
    html.theme-soda .soda-catsel__cell,
    html.theme-soda .soda-catsel__panel,
    html.theme-soda .soda-catsel__art,
    html.theme-soda .soda-catsel__shade,
    html.theme-soda .soda-catsel__info {
        transition: none;
    }
}

/* -------------------------------------------------- the plate below 1024px

   The row cannot survive as four flex columns on a phone: at 390px each closed
   panel is under 50px, which is neither legible nor a touch target. It becomes
   a stack of full-width rows that keep the artwork and always show their label —
   the expansion mechanic has no meaning when every panel is already open.
   -------------------------------------------------------------------------- */

@media (max-width: 1023.98px) {
    html.theme-soda .soda-card-w {
        padding: 1.5rem;
    }

    html.theme-soda .soda-card-w__title {
        margin-bottom: calc(1.5rem - .14em);
    }

    html.theme-soda .soda-card-w__foot {
        margin-top: 1.5rem;
    }

    html.theme-soda .soda-catsel {
        flex-direction: column;
        height: auto;
        gap: .5rem;
    }

    html.theme-soda .soda-catsel__cell,
    html.theme-soda .soda-catsel__cell:has(.is-active),
    html.theme-soda .soda-catsel__cell:focus-within {
        flex: none;
    }

    html.theme-soda .soda-catsel__panel {
        flex-direction: row;
        align-items: center;
        gap: 1rem;
        min-height: 5.5rem;
        padding: .875rem 1rem;
    }

    /* Artwork becomes a fixed thumbnail beside the label rather than a bleed,
       and the desktop zoom means nothing here. */
    html.theme-soda .soda-catsel__art {
        position: static;
        flex: none;
        width: 3.5rem;
        height: 3.5rem;
        transform: none;
    }

    html.theme-soda .soda-catsel__art--glyph svg {
        width: 100%;
        height: 100%;
    }

    /* The stacked rows have no expansion, so the shade and the chip carry no
       information — the thumbnail already is the icon. */
    html.theme-soda .soda-catsel__shade,
    html.theme-soda .soda-catsel__chip {
        display: none;
    }

    /* Always readable: nothing is hidden behind an activation a stacked row
       does not have. */
    html.theme-soda .soda-catsel__label {
        position: static;
        flex: 1;
        padding: 0;
        pointer-events: none;
    }

    html.theme-soda .soda-catsel__info,
    html.theme-soda.js-soda .soda-catsel[data-soda-catsel-armed] .soda-catsel__panel {
        opacity: 1;
        transform: none;
    }

    html.theme-soda .soda-catsel__name {
        font-size: 1.25rem;
    }
}

@media (max-width: 640px) {
    html.theme-soda .soda-card-w {
        padding: 1.25rem;
    }

    html.theme-soda .soda-catsel__panel {
        min-height: 4.75rem;
        gap: .875rem;
    }

    html.theme-soda .soda-catsel__art {
        width: 3rem;
        height: 3rem;
    }

    html.theme-soda .soda-catsel__name {
        font-size: 1.125rem;
    }
}


/* ================================= 3. FILTER + FEATURED PRODUCTS (§3)

   Both live on the second white Vesper plate, so they inherit .soda-card-w.

   THE FILTER is the brief's grammar: labels on a hairline, the current one
   marked by ink weight and by the signal accent under it. No pills, no filled
   segments, no glow, no soft cards, no shadcn chrome.

     VŠE 6      NOVÉ 4      POUŽITÉ 2      SKLADEM 5
     ─────────────────────────────────────────────────
   -------------------------------------------------------------------------- */

html.theme-soda .soda-filter {
    display: flex;
    align-items: stretch;
    flex-wrap: wrap;
    gap: 0;
    border-bottom: 1px solid rgba(0, 0, 0, .15);
}

html.theme-soda .soda-filter__opt {
    display: inline-flex;
    align-items: baseline;
    gap: .486vw;
    padding: 0 1.667vw .833vw 0;
    border: 0;
    /* The active marker: a 2px rule that only the current option carries, sitting
       ON the row hairline rather than beside it. */
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    background: none;
    /* SQUARE, for the same reason as the nav item: the radius curled the 2px
       active rule up at its ends and nothing else on this control used it. */
    border-radius: var(--r-none);
    color: rgba(0, 0, 0, .55);
    font-family: var(--font-display);
    font-size: var(--text-body);
    font-weight: 400;
    line-height: 1.2;
    text-transform: uppercase;
    letter-spacing: .06em;
    cursor: pointer;
    transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

html.theme-soda .soda-filter__opt:hover {
    color: #000;
}

html.theme-soda .soda-filter__opt.is-current {
    color: #000;
    border-bottom-color: var(--vesper-signal);
}

/* The live count. Tabular so the labels do not shift width as it changes. */
html.theme-soda .soda-filter__count {
    font-size: var(--text-small);
    font-variant-numeric: tabular-nums;
    color: rgba(0, 0, 0, .4);
}

html.theme-soda .soda-filter__opt.is-current .soda-filter__count {
    color: rgba(0, 0, 0, .6);
}

/* -------------------------------------------------------- product gallery

   Gallery objects, not spreadsheet cells. The ruled grid this replaced put a
   hairline on all four sides of every product and made a page of phones read
   as a table of rows; the same information now sits on separated tiles with
   the media dominating and the type underneath it.

   What is taken from the 21st gallery grid is the RHYTHM — image-first, real
   gutters, one restrained corner, type below the media — not its skin. What
   stays Vesper: the white plane underneath, the hairline as the only edge, no
   shadow, no hover lift, no gradient, no badge soup.

   The card is ONE box. The media area deliberately has no border and no fill
   of its own, because the imported product renders already come on white and
   a second surface behind them would draw a box inside a box.
   -------------------------------------------------------------------------- */

html.theme-soda .soda-prod {
    /* The containing block for popLayout exits (below). */
    position: relative;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1.111vw;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* `display: flex` on the cell beats the UA's [hidden] rule — the same trap the
   carousel arrows document in catalog.css — so without this the filter set
   attributes that never visually hid anything. Diagnostics counted [hidden]
   and stayed green while the pixels ignored the filter entirely. */
html.theme-soda .soda-prod__cell[hidden] {
    display: none;
}

/* FILTER MOTION (the approved 21st gallery, AnimatePresence popLayout): an
   exiting card is popped out of the flow at its old coordinates — ui.js
   writes left/top/width/height — so survivors reflow at once and slide (FLIP)
   while it fades where it stood. [hidden] stays the source of truth; this
   class only keeps the pixels alive for the 300ms exit. */
html.theme-soda .soda-prod__cell.is-flip-out[hidden] {
    display: flex;
    position: absolute;
    margin: 0;
    pointer-events: none;
}

html.theme-soda .soda-prod__cell {
    display: flex;
    min-width: 0;
    margin: 0;
}

html.theme-soda .soda-prod__item {
    display: flex;
    flex-direction: column;
    width: 100%;
    min-width: 0;
    padding: .833vw .833vw 1.111vw;
    gap: .833vw;
    border: 1px solid rgba(0, 0, 0, .12);
    border-radius: var(--r-card);
    background: #fff;
}

html.theme-soda .soda-prod__media {
    position: relative;
    display: grid;
    place-items: center;
    /* Square, because every imported render is square. Contain rather than
       cover: cropping a product photograph to fit a tile is not an option. */
    aspect-ratio: 1 / 1;
    border-radius: var(--r-control);
    overflow: hidden;
}

/* The hover overlay: the reference's black/60 wash with title + one chip,
   fading in over 200ms. Its backdrop blur is NOT ported — the plane turns
   backdrop-filter off with the argument that applies verbatim here (over
   white-ground photos it costs a compositing pass for no visible effect) —
   and neither is its zoom icon: this card opens a product page, not a
   lightbox. pointer-events: none keeps every click on the card's link. */
html.theme-soda .soda-prod__overlay {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .55rem;
    padding: 1rem;
    text-align: center;
    background: rgba(0, 0, 0, .6);
    opacity: 0;
    transition: opacity .2s ease;
    pointer-events: none;
}

html.theme-soda .soda-prod__item:hover .soda-prod__overlay,
html.theme-soda .soda-prod__item:focus-within .soda-prod__overlay {
    opacity: 1;
}

html.theme-soda .soda-prod__overlay-name {
    color: #fff;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--text-body);
    line-height: 1.3;
}

/* The reference's secondary badge: light chip, dark text. */
html.theme-soda .soda-prod__overlay-tag {
    background: #fff;
    color: #14161a;
    padding: .25em .7em;
    border-radius: var(--r-mark);
    font-size: var(--text-label);
    line-height: 1.4;
}

@media (prefers-reduced-motion: reduce) {
    html.theme-soda .soda-prod__overlay {
        transition: none;
    }
}

html.theme-soda .soda-prod__media img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

html.theme-soda .soda-prod__device {
    width: 46%;
    height: auto;
    color: rgba(0, 0, 0, .25);
}

html.theme-soda .soda-prod__body {
    display: flex;
    flex-direction: column;
    flex: 1;
    gap: .556vw;
    min-width: 0;
}

html.theme-soda .soda-prod__name {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-body);
    font-weight: 500;
    line-height: 1.25;
    letter-spacing: 0;
    color: #000;
}

/* The whole cell is the target while only one real link exists in the
   accessibility tree. */
html.theme-soda .soda-prod__link {
    color: inherit;
    text-decoration: none;
}

html.theme-soda .soda-prod__link::after {
    content: '';
    position: absolute;
    inset: 0;
}

html.theme-soda .soda-prod__item {
    position: relative;
}

html.theme-soda .soda-prod__link:hover {
    text-decoration: underline;
    text-underline-offset: .18em;
}

/* Facts, not badges. Plain lines of text U Jabka actually holds. */
html.theme-soda .soda-prod__facts {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: .208vw;
    font-size: var(--text-small);
    line-height: 1.35;
    color: rgba(0, 0, 0, .6);
}

/* Price and availability, the pair a person compares across a grid. Pushed to
   the bottom so cards of unequal text length still line their prices up. */
html.theme-soda .soda-prod__foot {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .556vw;
    margin: auto 0 0;
    padding-top: .556vw;
}

html.theme-soda .soda-prod__price {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-stat);
    font-weight: 300;
    line-height: 1;
    letter-spacing: var(--tracking-title);
    font-variant-numeric: tabular-nums;
    color: #000;
    white-space: nowrap;
}

html.theme-soda .soda-prod__status {
    margin: 0;
    text-align: right;
    font-size: var(--text-small);
    line-height: 1.3;
    color: rgba(0, 0, 0, .6);
}

html.theme-soda .soda-prod__empty {
    margin: 1.667vw 0 0;
    font-size: var(--text-body);
    color: rgba(0, 0, 0, .6);
}

/* --------------------------------------------- filter + products below 1024 */

@media (max-width: 1023.98px) {
    html.theme-soda .soda-filter {
        /* The row scrolls rather than wrapping into two ragged lines, and the
           scrollbar is the only thing that may leave the card. */
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        overscroll-behavior-x: contain;
        -webkit-overflow-scrolling: touch;
    }

    html.theme-soda .soda-filter__opt {
        gap: .375rem;
        padding: 0 1.25rem .75rem 0;
        /* 44px touch target measured from the top of the card content. */
        min-height: 2.75rem;
        white-space: nowrap;
    }

    html.theme-soda .soda-filter__opt:focus-visible {
        /* Keep the keyboard ring inside the vertically clipped scroll row. */
        outline-offset: -2px;
    }

    html.theme-soda .soda-prod {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 1rem;
    }

    html.theme-soda .soda-prod__item {
        padding: .75rem .75rem 1rem;
        gap: .75rem;
    }

    html.theme-soda .soda-prod__body {
        gap: .375rem;
    }

    html.theme-soda .soda-prod__empty {
        margin-top: 1.25rem;
    }
}

@media (max-width: 640px) {
    html.theme-soda .soda-prod {
        grid-template-columns: minmax(0, 1fr);
        gap: .75rem;
    }

    /* A full-width square image per product wastes a phone screen; the card
       becomes a row with the render as a thumbnail beside the facts. It keeps
       its border, so it is still one gallery object rather than a table row. */
    html.theme-soda .soda-prod__item {
        flex-direction: row;
        align-items: flex-start;
        gap: 1rem;
        padding: .75rem;
    }

    html.theme-soda .soda-prod__media {
        flex: none;
        width: 6rem;
        aspect-ratio: 1 / 1;
    }

    html.theme-soda .soda-prod__foot {
        margin-top: .5rem;
        padding-top: .4rem;
    }

    /* A 6rem thumbnail cannot host the overlay, and these are touch widths. */
    html.theme-soda .soda-prod__overlay {
        display: none;
    }
}

@media (max-width: 420px) {
    html.theme-soda .soda-prod__item {
        gap: .75rem;
    }

    html.theme-soda .soda-prod__media {
        width: 5rem;
    }

    html.theme-soda .soda-prod__foot {
        flex-direction: column;
        align-items: flex-start;
        gap: .25rem;
    }

    html.theme-soda .soda-prod__status {
        text-align: left;
    }
}


/* The section MOOD GLOW is gone with the sections that carried it. It was a
   46vw radial halo behind each block below the hero, alternating sides. The
   Vesper plane is flat: an opaque white plane on a flat dark ground, with
   hairlines for structure and nothing tinted behind anything.

   The SECTION HEAD layout (.soda-home-head / .soda-home-foot) is gone for the
   same reason — the eyebrow + title + trailing-link head belonged to the old
   marketing sections. The cards carry their own head, and the trust band
   carries a centred title with no eyebrow, exactly as the reference does. */

/* ============================================ 6. TRUST BAND (§4, "Proč U Jabka")

   docs/vesper/src/views/home/section-galaxy.tsx, transcribed.

   Vesper's stat band is a hairline rule across the top, faint vertical
   separators between four columns, and one brighter short rule beside each
   value/label pair. Nothing is filled, nothing is rounded, nothing glows.

     2.222vw = 32px   1.667vw = 24px   1.111vw = 16px   .833vw = 12px

   THE GROUND IS THEMELESS, exactly like .soda-plate above it: the band is part
   of the continuous Vesper plane the hero and the white cards live on, not a
   surface-theme section, so it must not turn ivory under the light theme. Its
   ink is therefore written as literal white alphas rather than read from the
   theme tokens — the same decision tokens.css records for .soda-hero.
   -------------------------------------------------------------------------- */

html.theme-soda .soda-band {
    position: relative;
    z-index: 6;
    background: var(--vesper-void);
    color: #fff;
    padding: 4.444vw 0 3.333vw;
}

html.theme-soda .soda-band__inner {
    width: var(--container);
    max-width: calc(100% - var(--gutter) * 2);
    margin-inline: auto;
}

html.theme-soda .soda-band__title {
    margin: 0 auto;
    max-width: 46.319vw;
    /* Explicit, not inherited: base.css declares `color: var(--ink)` on every
       heading to beat the legacy sheet, and an explicit declaration on the
       element beats inheritance from .soda-band. Under the light theme --ink is
       near-black, which painted this title invisibly onto the Vesper ground. */
    color: #fff;
    font-family: var(--font-display);
    font-size: var(--text-display);
    font-weight: 300;
    line-height: var(--leading-title);
    letter-spacing: var(--tracking-title);
    text-align: center;
    /* Descender protection — see .soda-sec-title in base.css. */
    padding-bottom: .14em;
}

html.theme-soda .soda-band__support {
    margin: 1.667vw auto 0;
    max-width: 29.097vw;
    font-size: var(--text-body);
    line-height: var(--leading-body);
    text-align: center;
    color: rgba(255, 255, 255, .72);
}

/* The band itself. The top rule is the grid's border; the separators are the
   cells' own left borders, so they hang from that rule for the full height
   with no absolute positioning and no magic offsets. */
html.theme-soda .soda-band__stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    margin: 4.167vw 0 0;
    padding: 0;
    list-style: none;
    border-top: 1px solid rgba(255, 255, 255, .2);
}

html.theme-soda .soda-band__cell {
    padding: 2.222vw 1.667vw 0 0;
}

html.theme-soda .soda-band__cell + .soda-band__cell {
    padding-left: 1.667vw;
    border-left: 1px solid rgba(255, 255, 255, .2);
}

html.theme-soda .soda-stat {
    display: flex;
    flex-direction: column;
    gap: .833vw;
    padding-left: 1.111vw;
    border-left: 1px solid rgba(255, 255, 255, .8);
}

html.theme-soda .soda-stat__value {
    font-family: var(--font-display);
    font-size: var(--text-stat);
    font-weight: 300;
    line-height: 1.05;
    letter-spacing: var(--tracking-title);
    /* The value is the one place a mixed-width label can wrap badly; a soft
       balance keeps "Vlastní fotky" on two even lines instead of one orphan. */
    text-wrap: balance;
}

html.theme-soda .soda-stat__label {
    font-size: var(--text-small);
    line-height: 1.35;
    color: rgba(255, 255, 255, .7);
}

/* ------------------------------------------------- the band below 1024px

   Vesper drops the fixed column offsets and the separators outright below
   1024 (`max-lg:hidden` on every divider) and lets a real grid carry the
   rhythm. Four 11vw columns are 43px wide on a phone; nothing survives that.
   -------------------------------------------------------------------------- */

@media (max-width: 1023.98px) {
    html.theme-soda .soda-band {
        padding: 4rem 0 3rem;
    }

    html.theme-soda .soda-band__title,
    html.theme-soda .soda-band__support {
        max-width: none;
    }

    html.theme-soda .soda-band__support {
        margin-top: 1.25rem;
    }

    html.theme-soda .soda-band__stats {
        grid-template-columns: repeat(2, 1fr);
        gap: 1.5rem 1.25rem;
        margin-top: 2.5rem;
        padding-top: 1.25rem;
    }

    html.theme-soda .soda-band__cell,
    html.theme-soda .soda-band__cell + .soda-band__cell {
        padding: 0;
        border-left: 0;
    }

    html.theme-soda .soda-stat {
        gap: .3rem;
        padding-left: .625rem;
    }
}

@media (max-width: 640px) {
    html.theme-soda .soda-band__stats {
        grid-template-columns: 1fr;
        gap: 1.125rem;
    }
}

/* ================================ 7. THE UNIT CARD (§5 Výkup, §6 Jak to funguje)

   docs/vesper/src/views/home/sections/financial-section.tsx, transcribed.

   ONE component for both sections, because in Vesper they ARE one: a white
   plane inset from both viewport edges with a display title at the top, a
   second zone beside it, and bracket-numbered units — [ 01 ], [ 02 ] — sitting
   on the card's floor under a lot of air. Only the number of units differs
   (three for Výkup, four for the purchase process), which is why the row is a
   grid over `--unit-cols` rather than two near-identical layouts.

   The card body is .soda-card-w, unchanged, so the buyback and process cards
   are literally the same plane as the category selector and the featured
   products above them. That is the point: one system, not four lookalikes.

     2.222vw = 32px   8.333vw = 120px   1.667vw = 24px   1.111vw = 16px
   -------------------------------------------------------------------------- */

/* Title left, intro + CTA right — the reference's two-zone top, with the copy
   standing where its photograph does. 41.875vw / 27.361vw are the reference's
   own column widths. */
html.theme-soda .soda-card-w__head {
    display: grid;
    grid-template-columns: minmax(0, 41.875vw) minmax(0, 27.361vw);
    justify-content: space-between;
    gap: 2.222vw;
    align-items: start;
}

html.theme-soda .soda-card-w--units .soda-card-w__title {
    margin: 0;
    padding-bottom: .14em;
}

html.theme-soda .soda-card-w__aside {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1.667vw;
    font-size: var(--text-body);
    line-height: 1.45;
    color: rgba(0, 0, 0, .72);
}

html.theme-soda .soda-card-w__aside p {
    margin: 0;
}

/* A ruled link, not a button: the card carries no filled control, and a pill
   here would be the single roundest thing on the page. */
html.theme-soda .soda-card-w__cta {
    display: inline-flex;
    align-items: center;
    gap: .694vw;
    padding: .833vw 1.111vw;
    border: 1px solid rgba(0, 0, 0, .25);
    border-radius: var(--r-control);
    font-size: var(--text-body);
    line-height: var(--leading-body);
    color: #000;
    text-decoration: none;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

html.theme-soda .soda-card-w__cta:hover,
html.theme-soda .soda-card-w__cta:focus-visible {
    border-color: #000;
    background: rgba(0, 0, 0, .05);
}

html.theme-soda .soda-card-w__cta-mark {
    /* Optical, not semantic: the arrow must not drag the label's baseline. */
    line-height: 1;
}

/* ------------------------------------------------------------- the units */

html.theme-soda .soda-units {
    display: grid;
    grid-template-columns: repeat(var(--unit-cols, 3), minmax(0, 1fr));
    gap: 2.222vw;
    /* The air the reference puts between the title and the card floor. */
    margin: 8.333vw 0 0;
    padding: 0;
    list-style: none;
}

html.theme-soda .soda-unit {
    display: flex;
    flex-direction: column;
    gap: .833vw;
}

/* The bracket marker is the reference's, literal: "[ 01 ]" as text, at body
   size, in the same face as everything else. Not a badge, not a circle, not a
   coloured chip. */
html.theme-soda .soda-unit__no {
    font-size: var(--text-body);
    line-height: var(--leading-body);
    color: rgba(0, 0, 0, .55);
    font-variant-numeric: tabular-nums;
}

html.theme-soda .soda-unit__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-body);
    font-weight: 500;
    line-height: var(--leading-body);
    letter-spacing: 0;
    color: #000;
}

html.theme-soda .soda-unit__body {
    margin: 0;
    font-size: var(--text-body);
    line-height: 1.35;
    color: rgba(0, 0, 0, .66);
}

/* ------------------------------------------------- the unit card below 1024

   The reference abandons its absolute placement below 1024 and becomes a
   padded stack that grows with its content; ours does the same, and the units
   fall to two columns and then one rather than shrinking to unreadable.
   -------------------------------------------------------------------------- */

@media (max-width: 1023.98px) {
    html.theme-soda .soda-card-w__head {
        grid-template-columns: 1fr;
        gap: 1.25rem;
    }

    html.theme-soda .soda-card-w__aside {
        gap: 1.25rem;
    }

    html.theme-soda .soda-card-w__cta {
        gap: .5rem;
        padding: .75rem 1rem;
    }

    html.theme-soda .soda-units {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 1.5rem;
        margin-top: 2.5rem;
    }

    html.theme-soda .soda-unit {
        gap: .35rem;
    }
}

@media (max-width: 640px) {
    html.theme-soda .soda-units {
        grid-template-columns: 1fr;
        gap: 1.25rem;
        margin-top: 2rem;
    }
}

/* ============================================ 8. PROCESS (§6) — same card

   The purchase process is section 7's component with one more column. The old
   section's whole vocabulary — a 1px accent guide rail, a 9px accent dot inside
   a 5px accent halo per step, a mood glow behind the section and a ghosted
   numeral at 3.4rem — is gone, and none of it is replaced by an equivalent.
   -------------------------------------------------------------------------- */

html.theme-soda .soda-card-w--four {
    --unit-cols: 4;
}

/* ============================================================= 9. FAQ (§7)

   docs/vesper/src/views/home/sections/faq-section.tsx, transcribed.

   Same white plane as the three cards above; centred display title; a narrow
   accordion column (the reference's 42.778vw) with a hairline above the first
   row and under every row; a marker built from two 1px bars that rotates 45°
   from + into ×.

   These rules RESKIN the shared .soda-acc inside this card only. The component
   itself (components.css) still serves /faq, /jak-to-funguje and the other
   content pages on the surface theme, and must keep working there unchanged —
   which is why every override below is scoped under .soda-faq rather than
   rewritten upstream.

     2.222vw = 32px  4.167vw = 60px  1.667vw = 24px  1.389vw = 20px  .75vw = 11px
   -------------------------------------------------------------------------- */

html.theme-soda .soda-card-w--faq {
    padding-block: 2.222vw 3.056vw;
    text-align: center;
}

html.theme-soda .soda-card-w--faq .soda-card-w__title {
    margin: 0;
}

html.theme-soda .soda-card-w__foot--center {
    text-align: center;
}

html.theme-soda .soda-faq {
    width: 42.778vw;
    max-width: 100%;
    margin: 4.167vw auto 0;
    border-top: 1px solid rgba(0, 0, 0, .15);
    text-align: left;
}

/* The accordion reskin these rows use is not here: it belongs to the WHITE
   PLANE, not to this card, so it lives in components.css beside .soda-plane
   and serves /faq and the content pages too. What stays here is the column
   geometry: the narrow 42.778vw column above, and its collapse below. */

@media (max-width: 1023.98px) {
    html.theme-soda .soda-card-w--faq {
        padding-block: 2rem 2.5rem;
    }

    html.theme-soda .soda-faq {
        width: 100%;
        margin-top: 2rem;
    }

}

/* ===================================================== 10. RESPONSIVE

   What used to live here was the restack for components this file no longer
   renders: .soda-cat-grid / .soda-prod-grid / .soda-cat-card / .soda-seg (all
   replaced by the Vesper plates), and .soda-why / .soda-home-faq /
   .soda-buyback / .soda-process / .soda-home-head (all replaced by the band
   and the unit card). Each surviving component now authors its own sub-1024
   and sub-640 rules beside its desktop rules, which is where they belong.
   -------------------------------------------------------------------------- */

/* Reduced motion: the hero keeps every pixel of its content, and simply stops
   moving. The 3D layer is never even booted (see hero-controller.js). */
@media (prefers-reduced-motion: reduce) {
    /* The hero keeps every pixel of its content and simply stops moving. There
       is nothing ambient left to stop — no motes, no autoplay spin, no
       auto-rotation — so this only settles the object. */
    html.theme-soda .soda-hero__object,
    html.theme-soda .soda-hero__canvas { transition: none; }
}

/* Owner photography keeps its whole silhouette within a stable media box. */
html.theme-soda .soda-prod__media { grid-template: minmax(0, 1fr) / minmax(0, 1fr); background: #fff; }
html.theme-soda .soda-prod__media img { min-width: 0; min-height: 0; object-position: center; }
html.theme-soda .soda-prod__item:hover .soda-prod__media img,
html.theme-soda .soda-prod__item:focus-within .soda-prod__media img { transform: none; }
/* Owner category photography keeps its whole silhouette: `contain`, never
   `cover`, so a wide family shot is never centre-cropped into a tall panel. */
html.theme-soda img.soda-catsel__art { object-fit: contain; background: #fff; transform: none; }

/* The inset that keeps the picture clear of the panel's own label block belongs
   to the EXPANDING DESKTOP PANEL and only to it. Below 1024px the row becomes a
   stack whose thumbnail is a 3–3.5rem square (see the media queries above), and
   a 5rem bottom inset there exceeds the box and collapses the image to nothing —
   which is exactly what happened to the one card that had a photograph before
   this change, and would now have happened to three. */
@media (min-width: 1024px) {
    html.theme-soda img.soda-catsel__art { padding: 1rem 1rem 5rem; }
}

/* Assembly integration: same homepage typography/CTA layout; near-black stage.
   Progressive enhancement: without controller state the HTML stays visible. */
html.theme-soda .soda-hero[data-assembly-hero] { --hero-track:100dvh; background:#08090b; }
html.theme-soda .soda-hero[data-assembly-hero] .soda-vine__ground,
html.theme-soda .soda-hero[data-assembly-hero] .soda-vine__canvas,
html.theme-soda .soda-hero[data-assembly-hero] .soda-vine__veil { display:none; }
html.theme-soda .soda-hero[data-assembly-hero] .soda-vine__frame { position:relative; }
html.theme-soda .soda-loader.is-managed { animation:none; background:#08090b; transition:opacity .35s ease; }
html.theme-soda .soda-loader.is-managed.is-done { animation:none; opacity:0; pointer-events:none; }
html.theme-soda .soda-loader[hidden] { display:none; }
html.theme-soda .soda-hero__skip { position:fixed; z-index:10001; bottom:1.5rem; left:var(--gutter); border:1px solid #ffffff40; border-radius:var(--r-pill); padding:.7rem 1rem; background:#101114; color:#eee; font:inherit; font-size:.8rem; cursor:pointer; }
html.theme-soda .soda-hero__skip[hidden] { display:none; }
html.theme-soda .soda-hero__skip:focus-visible { outline:2px solid white; outline-offset:4px; }
html.theme-soda .soda-hero[data-assembly-hero] .soda-hero__title,
html.theme-soda .soda-hero[data-assembly-hero] .soda-hero__band { transition:opacity .38s ease,translate .38s ease; }
html.theme-soda .soda-hero[data-hero-phase="loading"] .soda-hero__title,
html.theme-soda .soda-hero[data-hero-phase="loading"] .soda-hero__band,
html.theme-soda .soda-hero[data-hero-phase="intro"] .soda-hero__title,
html.theme-soda .soda-hero[data-hero-phase="intro"] .soda-hero__band { opacity:0; translate:0 8px; }
html.theme-soda .soda-hero[data-hero-phase="intro"] .soda-hero__canvas { pointer-events:none; }
html.theme-soda .soda-hero[data-assembly-hero] .soda-hero__canvas { touch-action:pan-y pinch-zoom; }
@media (prefers-reduced-motion:reduce) {
 html.theme-soda .soda-hero[data-assembly-hero] .soda-hero__title,
 html.theme-soda .soda-hero[data-assembly-hero] .soda-hero__band { transition:none; }
}
html.theme-soda .soda-loader .soda-loader__count { font-size:.875rem; letter-spacing:.025em; line-height:1.4; }
html.theme-soda .soda-hero[data-hero-phase="loading"] .soda-hero__object,
html.theme-soda .soda-hero[data-hero-phase="intro"] .soda-hero__object { opacity:0; }
html.theme-soda .soda-hero[data-assembly-hero] .soda-hero__object { transform:translate(-4%,-6%) scale(.60); }
@media(max-width:1023px) { html.theme-soda .soda-hero[data-assembly-hero] .soda-hero__object { transform:scale(.93); } }

/* Duo: base layout also serves mobile; desktop composition is scoped below. */
html.theme-soda .soda-hero[data-duo-hero] { --hero-track: max(62rem, 100dvh); }
html.theme-soda .soda-hero[data-duo-hero] .soda-vine__frame {
    background:
        linear-gradient(180deg, #08090b66 0%, #08090b33 45%, #08090b 100%),
        url('../../img/soda/hero-background-21st.jpg') center center / cover no-repeat;
}
@media(max-width:1023px) {
    html.theme-soda .soda-hero[data-duo-hero] .soda-vine__frame {
        background:
            linear-gradient(180deg, #08090b80 0%, #08090b4d 50%, #08090b 100%),
            url('../../img/soda/hero-background-21st.jpg') center center / cover no-repeat;
    }
}
html.theme-soda [data-duo-hero] .soda-vine__frame,
html.theme-soda [data-duo-hero] .soda-hero__frame { min-height: max(62rem, 100dvh); }
html.theme-soda [data-duo-hero] .soda-hero__title {
    top: max(7rem, 8vw);
    width: 29vw;
    font-size: clamp(1.5rem, 2.7vw, 2.75rem);
}
html.theme-soda [data-duo-hero] .soda-hero__stage { top: 23%; left: 55%; width: 45vw; }
html.theme-soda [data-duo-hero] .soda-hero__object { transform: none; border-radius: 1rem; }
html.theme-soda .duo-countdown {
    position: absolute;
    top: 45%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(94%, 74rem);
    text-align: center;
    color: #f7f7f5;
    text-shadow: 0 2px 5px #08090b, 0 4px 40px #08090b;
    pointer-events: none;
}
html.theme-soda .duo-countdown__heading {
    margin: 0 0 1rem;
    font-size: clamp(.8rem, 1.4vw, 1.25rem);
    font-weight: 500;
    letter-spacing: .12em;
    text-transform: uppercase;
}
html.theme-soda .duo-countdown__numbers { display: flex; align-items: flex-start; justify-content: center; gap: clamp(.25rem, 1.3vw, 1.5rem); }
html.theme-soda .duo-countdown__number,
html.theme-soda .duo-countdown__separator {
    display: block;
    font-family: var(--font-display);
    font-size: clamp(3.25rem, 9.7vw, 9rem);
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
}
/* Each digit is an `overflow: hidden` roll-out window, and the countdown's
   inherited 40px text-shadow filled that window with ink and was clipped
   square, so every digit sat on a faint rectangle. The halo is re-applied as a
   filter, which composites after the clip and follows the glyphs. Unclipped
   parts (heading, separators, captions) keep the inherited shadow. */
html.theme-soda .duo-countdown__number {
    min-inline-size: 1.24em;
    text-shadow: none;
    filter: drop-shadow(0 2px 5px #08090b) drop-shadow(0 4px 22px #08090b);
}
html.theme-soda .duo-countdown__separator { flex: 0 0 .25em; inline-size: .25em; text-align: center; font-size: clamp(2.5rem, 7vw, 6rem); line-height: 1.45; font-weight: 300; opacity: .65; }
html.theme-soda .duo-countdown__caption { display: block; margin-top: .8rem; font-size: clamp(.75rem, 1.2vw, 1rem); color: #c4c6cb; }
html.theme-soda .duo-countdown__digit { display: inline-block; position: relative; inline-size: .62em; text-align: center; overflow: hidden; vertical-align: bottom; }
html.theme-soda .duo-countdown__digit-value { display: block; inline-size: 100%; }
html.theme-soda .duo-countdown__digit-previous { position: absolute; inset: 0; inline-size: 100%; pointer-events: none; }
html.theme-soda .soda-duo-controls {
    position: absolute;
    top: 62%;
    left: 50%;
    transform: translateX(-50%);
    width: min(40rem, 90%);
    margin: 0;
    padding: 1rem 1.25rem;
    border: 1px solid #ffffff24;
    border-radius: .75rem;
    background: #101216;
    color: #f7f7f5;
    pointer-events: auto;
}
html.theme-soda .soda-duo-controls:disabled { display: none; }
html.theme-soda .soda-duo-controls legend { padding: 0 .5rem; font-size: .85rem; color: #c4c6cb; }
html.theme-soda .soda-duo-controls__angle { display: flex; justify-content: space-between; gap: 1rem; font-size: .85rem; }
html.theme-soda .soda-duo-controls input[type="range"] { width: 100%; min-height: 2rem; accent-color: #dddfe3; }
html.theme-soda .soda-duo-controls__buttons { display: flex; flex-wrap: wrap; gap: .5rem; }
html.theme-soda .soda-duo-controls button,
html.theme-soda .soda-duo-controls summary { min-height: 2.75rem; cursor: pointer; font-size: .8rem; }
html.theme-soda .soda-duo-controls button { flex: 1; padding: .5rem .75rem; border: 1px solid #ffffff30; border-radius: .4rem; color: inherit; background: #ffffff08; }
html.theme-soda .soda-duo-controls button:hover { background: #ffffff18; }
html.theme-soda .soda-duo-controls :is(button,input,summary):focus-visible { outline: 2px solid #dbeafe; outline-offset: 3px; }
html.theme-soda .soda-duo-controls__wallpaper { margin-top: .6rem; font-size: .8rem; }
html.theme-soda .soda-duo-controls__wallpaper summary { min-height: 1.75rem; padding-block: .3rem; color: #c4c6cb; }
html.theme-soda .soda-duo-controls__wallpaper[open] { position: relative; z-index: 3; background: #101216; padding-bottom: .75rem; }
html.theme-soda .soda-duo-controls__wallpaper :is(label,input,span) { display: block; max-width: 100%; margin-top: .5rem; }
html.theme-soda .soda-finish__chip--white { background: #e7e9e8; }
html.theme-soda .soda-finish__chip--night { background: #303c49; }
html.theme-soda [data-duo-hero] .soda-hero__band { height: auto; display: grid; grid-template-columns: 1fr auto; gap: 1rem 2rem; }
html.theme-soda [data-duo-hero] .soda-hero__band :is(.soda-hero__tagline,.soda-hero__support,.soda-finish,.soda-hero__cta) { position: static; width: auto; }
html.theme-soda [data-duo-hero] .soda-hero__tagline { font-size: .85rem; }
html.theme-soda [data-duo-hero] .soda-hero__support { max-width: 24rem; font-size: .85rem; }
@media(max-width:1023px) {
    html.theme-soda .soda-hero[data-duo-hero] { --hero-track: auto; }
    html.theme-soda [data-duo-hero] .soda-vine__frame { min-height: 100dvh; height: auto; }
    html.theme-soda [data-duo-hero] .soda-hero__frame { min-height: 0; }
    html.theme-soda [data-duo-hero] .soda-hero__title { width: 100%; font-size: 1.6rem; }
    html.theme-soda [data-duo-hero] .duo-countdown { position: static; order: 2; transform: none; width: 100%; margin-block: 1rem; }
    html.theme-soda [data-duo-hero] .soda-hero__stage { order: 3; top: auto; left: auto; width: min(82vw, 30rem); }
    html.theme-soda [data-duo-hero] .soda-duo-controls { position: static; order: 4; transform: none; width: 100%; }
    html.theme-soda [data-duo-hero] .soda-hero__band { order: 5; display: flex; }
    html.theme-soda .duo-countdown__numbers { gap: .35rem; }
    html.theme-soda .duo-countdown__number { font-size: clamp(2.5rem, 10vw, 6rem); }
    html.theme-soda .duo-countdown__separator { font-size: clamp(1.75rem, 6.5vw, 4rem); }
}

/* Keep the approved mobile composition; only desktop moves these elements. */
@media (min-width: 1024px) {
    html.theme-soda [data-duo-hero] .soda-hero__stage {
        top: 54%;
        left: 50%;
        width: min(56vw, 50rem);
        transform: translate(-50%, -50%);
    }
    html.theme-soda .soda-hero[data-duo-hero] .soda-hero__object { transform: scale(.68); }
    html.theme-soda [data-duo-hero] .duo-countdown { top: 23%; left: 60%; width: min(64%, 58rem); }
    html.theme-soda .duo-countdown__number { font-size: clamp(3.25rem, 7.2vw, 7rem); }
    html.theme-soda .duo-countdown__separator { font-size: clamp(2.25rem, 5.5vw, 5.25rem); line-height: 1.35; }
    html.theme-soda [data-duo-hero] .soda-duo-controls {
        top: auto;
        right: var(--gutter);
        bottom: calc(2.153vw + 7rem);
        left: auto;
        transform: none;
        width: min(19rem, calc(100% - 2 * var(--gutter)));
        padding: .65rem .75rem;
    }
    html.theme-soda .soda-duo-controls legend { padding: 0 .35rem; font-size: .75rem; }
    html.theme-soda .soda-duo-controls__angle { gap: .65rem; font-size: .75rem; }
    html.theme-soda .soda-duo-controls__buttons { gap: .35rem; }
    html.theme-soda .soda-duo-controls :is(button,summary) { min-height: 2rem; font-size: .7rem; }
    html.theme-soda .soda-duo-controls button { padding: .35rem .4rem; }
    html.theme-soda .soda-duo-controls__wallpaper { margin-top: .35rem; font-size: .75rem; }
    html.theme-soda .soda-duo-controls__wallpaper summary { min-height: 2rem; }
}
