/* ==========================================================================
   U Jabka — Soda design system: CART + CHECKOUT (PR6)

   Page-scoped layout for resources/views/frontend/cart.php and
   resources/views/frontend/checkout.php. Everything is scoped under
   `html.theme-soda`, so a page that has not opted into the redesign can never
   pick it up.

   Only layout and page-specific recipes live here. Buttons, panels, badges,
   breadcrumb, notices, empty states and every FORM PRIMITIVE (soda-field,
   soda-label, soda-input, soda-textarea, soda-error, soda-hint, soda-choice,
   soda-qty) come from soda/components.css and are NOT restyled — a page agent
   that redefines a shared component is a defect.

   TONE: commerce is the calm end of the system. No glow, no hover lift on a
   line, no decorative gradients. The money and the availability are the only
   things allowed to be loud.

   CONTRAST: no text on these two pages uses --ink-3. Composited over --page-bg
   that token measures ~3.9:1, under the 4.5:1 WCAG AA floor for normal text,
   and everything here — SKU, battery, availability, the stock note, the "amount
   is for goods only" line — is information a buyer needs to decide or to
   understand the total. --ink-3 is kept only for non-text (the placeholder
   device tint, the neutral battery dot). This is a page-level decision; the
   token itself is integrator-owned and the same reading applies system-wide.

   CSP: this file carries every dynamic-looking style. There is not one inline
   style attribute in either template, and no rule here depends on JS running.
   ========================================================================== */

/* ------------------------------------------------------------- page frame */

html.theme-soda .soda-commerce {
    /* A working surface, not a stage: less air above than a marketing section
       so the first cart line is visible immediately. */
    padding-block: var(--page-intro-space) var(--section-gap);

    /*
     * THE DARK VESPER GROUND, shared by the cart, the checkout and the order
     * pages because .soda-commerce is their shared section. Deliberately one
     * rule rather than three copies: they are one surface with three contents,
     * and duplicating it per page would be three chances to drift.
     *
     * Scoped ink, for the same reason .soda-hero scopes its own: this ground is
     * Vesper's, not the surface theme's, and must not turn ivory. The working
     * content sits on .soda-plane above it.
     */
    background: var(--vesper-void);
    color: #fff;

    --ink: #ffffff;
    --ink-muted: rgba(255, 255, 255, .72);
    --ink-faint: rgba(255, 255, 255, .55);
    --ink-2: rgba(255, 255, 255, .72);
    --ink-3: rgba(255, 255, 255, .55);
    --line: rgba(255, 255, 255, .15);
    --line-hi: rgba(255, 255, 255, .3);
    --glass-border: rgba(255, 255, 255, .15);
    --panel: transparent;
    --panel-hi: rgba(255, 255, 255, .06);
    --hover-wash: rgba(255, 255, 255, .07);
    --focus: #ffffff;

    /*
     * BUTTON CHROME MUST BE SCOPED WITH THE INK, or it silently keeps the
     * SURFACE THEME's values while everything around it flips.
     *
     * Found by driving a real order through in a browser rather than by
     * reading the stylesheet: --btn-bg is rgba(255,255,255,.88) under the light
     * theme, so a plain .soda-btn on this dark ground painted a near-white pill
     * carrying the scope's white label — 1.0:1, invisible. tokens.css records
     * exactly this failure for the hero CTA; the same three tokens close it
     * here.
     *
     * --accent goes white for the same reason it goes ink on the plane: these
     * pages carry no brand colour, so the CTA's square tile is ink-on-white
     * like the hero's rather than an orange chip.
     */
    --btn-bg: rgba(0, 0, 0, .42);
    --btn-bg-hover: rgba(0, 0, 0, .68);
    --btn-border: rgba(255, 255, 255, .18);
    --accent: #ffffff;
    --accent-rgb: 255, 255, 255;
    --accent-ink: #000000;
}

/* base.css declares `color: var(--ink)` on every heading to beat the legacy
   sheet, and an explicit declaration beats the scope above. */
html.theme-soda .soda-commerce__head .soda-sec-title,
html.theme-soda .soda-commerce__head .soda-eyebrow {
    color: #fff;
}

html.theme-soda .soda-commerce__head .soda-lead {
    color: rgba(255, 255, 255, .72);
}

html.theme-soda .soda-commerce__head {
    max-width: 62ch;
    margin-bottom: clamp(1.6rem, 3vw, 2.4rem);
}

html.theme-soda .soda-commerce__head .soda-sec-title {
    font-size: clamp(1.9rem, 3.4vw, 3rem);
}

html.theme-soda .soda-commerce__head .soda-lead {
    max-width: 54ch;
    margin-top: .9rem;
    margin-bottom: 0;
}

html.theme-soda .soda-commerce__notice {
    margin-bottom: clamp(1.2rem, 2.4vw, 1.8rem);
}

html.theme-soda .soda-commerce__empty {
    margin-top: .4rem;
}

/* ----------------------------------------------------------------- layout */

/* Lines/form on the left, summary on the right. On narrow viewports the
   summary stacks UNDER the lines: on a phone the buyer scrolls the items and
   finds the total at the end, which is the reading order they expect. */
/*
 * ONE PLANE, TWO COLUMNS, DIVIDED BY A HAIRLINE — not two floating cards with a
 * gutter between them. Each column owns its padding.
 *
 * The rule lives on the MAIN column rather than the aside: the aside is sticky,
 * and a border on a sticky box stops where the box does instead of running the
 * height of the plane. Main is also the taller column on every page that uses
 * this layout (cart lines, checkout form, payment instructions).
 */
html.theme-soda .soda-commerce__layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
    gap: 0;
    align-items: stretch;
}

@media (max-width: 980px) {
    html.theme-soda .soda-commerce__layout {
        grid-template-columns: 1fr;
    }
}

html.theme-soda .soda-commerce__main {
    min-width: 0;
    padding: 2.222vw;
    border-right: 1px solid rgba(0, 0, 0, .15);
}

html.theme-soda .soda-commerce__aside {
    min-width: 0;
    padding: 2.222vw;
    position: sticky;
    top: clamp(5.5rem, 9vw, 7rem);
    align-self: start;
}

@media (max-width: 980px) {
    html.theme-soda .soda-commerce__main {
        padding: 1.5rem;
        border-right: 0;
        border-bottom: 1px solid rgba(0, 0, 0, .15);
    }

    html.theme-soda .soda-commerce__aside {
        padding: 1.5rem;
        position: static;
    }
}

/* The summary is already on the plane, so it must not also be a panel. */
html.theme-soda .soda-commerce__summary {
    padding: 0;
    border: 0;
    background: transparent;
}

html.theme-soda .soda-commerce__empty {
    padding: 2.222vw;
}

/* ---------------------------------------------------------------- summary */

html.theme-soda .soda-commerce__summary-title {
    font-size: 1.15rem;
    margin-bottom: 1.1rem;
}

html.theme-soda .soda-commerce__totals {
    margin: 0 0 1.1rem;
}

html.theme-soda .soda-commerce__totals > div {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: .55rem 0;
    border-bottom: 1px solid var(--line);
}

html.theme-soda .soda-commerce__totals dt {
    font-size: .85rem;
    color: var(--ink-2);
}

html.theme-soda .soda-commerce__totals dd {
    margin: 0;
    font-variant-numeric: tabular-nums;
}

html.theme-soda .soda-commerce__totals-grand {
    border-bottom: 0;
    padding-top: .8rem;
}

html.theme-soda .soda-commerce__totals-grand dt {
    font-family: var(--font-display);
    font-size: .95rem;
    font-weight: 600;
    color: var(--ink);
}

html.theme-soda .soda-commerce__cta {
    width: 100%;
    justify-content: center;
    text-align: center;
    margin-top: .4rem;
}

html.theme-soda .soda-commerce__note {
    margin: 1rem 0 0;
    font-size: .78rem;
    line-height: 1.55;
    color: var(--ink-2);
}

html.theme-soda .soda-commerce__clear {
    margin-top: 1.1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--line);
}

html.theme-soda .soda-commerce__summary .soda-notice {
    margin-bottom: 1rem;
}

/* ================================================================== CART */

/* RULED LINE ITEMS, not a stack of panels. The list is already on the plane;
   a bordered box per line would be the "generic rounded commerce card stack"
   the direction rules out, and the gutter between the boxes is what made it
   read as one. */
html.theme-soda .soda-cart__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 0;
}

html.theme-soda .soda-cart__list > li {
    border-bottom: 1px solid rgba(0, 0, 0, .15);
}

html.theme-soda .soda-cart__list > li:last-child {
    border-bottom: 0;
}

html.theme-soda .soda-cart__list .soda-panel {
    border: 0;
    border-radius: var(--r-none);
    background: transparent;
}

/* media | body | side. The media column collapses first, then the side column
   moves under the body — a cart line never becomes a horizontal scroller. */
html.theme-soda .soda-cart-line {
    display: grid;
    grid-template-columns: 78px minmax(0, 1fr) minmax(0, 13rem);
    gap: clamp(.9rem, 2vw, 1.4rem);
    padding: clamp(1rem, 2vw, 1.35rem);
    align-items: start;
}

@media (max-width: 760px) {
    html.theme-soda .soda-cart-line {
        grid-template-columns: 56px minmax(0, 1fr);
    }

    /* The side column drops to its own full-width row here. Its ALIGNMENT
       override cannot live in this block: `.soda-cart-line__side` declares
       `align-items` and `text-align` further down the file at the same
       specificity, and source order would silently win. It is restated next to
       that rule instead — see the media block below it. */
    html.theme-soda .soda-cart-line__side {
        grid-column: 1 / -1;
    }
}

/* An unavailable line is dimmed but never hidden: the buyer has to be able to
   read what went wrong and remove it. Opacity stays high enough that the
   warning text keeps its contrast. */
html.theme-soda .soda-cart-line--unavailable {
    border-color: rgba(252, 211, 77, .28);
}

html.theme-soda .soda-cart-line__media {
    display: grid;
    place-items: center;
    aspect-ratio: 1 / 1;
    border-radius: var(--r-control);
    /* Rounded now, so what sits inside it has to be clipped. */
    overflow: hidden;
    background: rgba(255, 255, 255, .04);
    border: 1px solid var(--line);
    color: var(--ink-3);
}

html.theme-soda .soda-cart-line__device {
    width: 62%;
    height: 62%;
    color: rgba(255, 255, 255, .13);
}

html.theme-soda .soda-cart-line__media--variant {
    color: var(--accent);
}

html.theme-soda .soda-cart-line__media--variant .ico {
    width: 26px;
    height: 26px;
}

/* A REAL PHOTOGRAPH in the same square box the sprite sat in. The owner's
   renders and unit photos arrive on white, so the frame turns white when it
   holds one — a dark tint behind a white cut-out reads as a grey card, not as a
   product. `contain`, never `cover`: a cart line is where a buyer checks that
   the picture is the device they chose, and cropping a used kus's own
   photograph would hide exactly what it is there to show. Explicit stretch is
   load-bearing — the frame centres its child by default, so a percentage size
   would otherwise resolve against the image's own intrinsic box. */
html.theme-soda .soda-cart-line__media--photo {
    place-items: stretch;
    background: #fff;
}

html.theme-soda .soda-cart-line__photo {
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    object-fit: contain;
    object-position: center;
}

html.theme-soda .soda-cart-line__body {
    min-width: 0;
}

html.theme-soda .soda-cart-line__body--bare {
    grid-column: span 2;
}

html.theme-soda .soda-cart-line__kind {
    margin: 0 0 .3rem;
    font-family: var(--font-display);
    font-size: .66rem;
    font-weight: 600;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--ink-2);
}

html.theme-soda .soda-cart-line__title {
    font-size: 1.05rem;
    letter-spacing: -.02em;
}

html.theme-soda .soda-cart-line__spec {
    margin: .3rem 0 0;
    font-size: .84rem;
    color: var(--ink-2);
}

html.theme-soda .soda-cart-line__meta {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem .9rem;
    margin: .7rem 0 0;
    padding: 0;
    font-size: .78rem;
    color: var(--ink-2);
}

/* Battery: a text chip with a state dot. Colour is never the only signal — the
   percentage is always spelled out next to it. Thresholds are the existing
   ones (green >= 90, yellow >= 85, orange below); this only paints them. */
html.theme-soda .soda-cart-line__battery {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
}

html.theme-soda .soda-cart-line__dot {
    width: 7px;
    height: 7px;
    /* Square, like every other marker in the system: Vesper's own list and
       button markers are tiny SQUARES (size-[0.139vw] bg-current), and a circle
       here was the last curved geometry left in the component layer. */
    border-radius: var(--r-mark);
    flex: none;
    background: var(--ink-3);
}

html.theme-soda .soda-cart-line__battery--green .soda-cart-line__dot { background: var(--success); }
html.theme-soda .soda-cart-line__battery--yellow .soda-cart-line__dot { background: var(--warning); }
html.theme-soda .soda-cart-line__battery--orange .soda-cart-line__dot { background: #fb923c; }

html.theme-soda .soda-cart-line__note {
    margin: .6rem 0 0;
    font-size: .78rem;
    color: var(--ink-2);
}

html.theme-soda .soda-cart-line__warning {
    margin: .8rem 0 0;
    font-size: .82rem;
}

html.theme-soda .soda-cart-line__side {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    text-align: right;
    gap: .55rem;
}

/* Once the side column is a full-width row under the body (<=760px) the price
   and the actions have to read from the same left edge as everything else in
   the line; right-aligning them there leaves a ragged, disconnected column.
   This block MUST stay after the base rule above — it restates the same two
   properties at the same specificity, so it only wins on source order. */
@media (max-width: 760px) {
    html.theme-soda .soda-cart-line__side {
        align-items: flex-start;
        text-align: left;
    }
}

html.theme-soda .soda-cart-line__side .soda-price {
    margin: 0;
}

html.theme-soda .soda-cart-line__qty {
    margin: 0;
    font-size: .78rem;
    color: var(--ink-2);
}

html.theme-soda .soda-cart-line__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: .5rem .8rem;
    margin-top: .15rem;
}

@media (max-width: 760px) {
    html.theme-soda .soda-cart-line__actions {
        justify-content: flex-start;
    }
}

html.theme-soda .soda-cart-line__actions form {
    margin: 0;
}

/* Destructive-but-reversible action: a quiet text button, never a filled one.
   It is a real <button> inside a real POST form, so it works with JS off.

   Deliberately --ink-2 rather than the --ink-3 used for tertiary meta: this is
   an interactive control, and --ink-3 (40% white) measures below 4.5:1 against
   --page-bg at this size. */
html.theme-soda .soda-cart-line__remove {
    padding: .3rem 0;
    border: 0;
    background: none;
    color: var(--ink-2);
    font-family: var(--font-display);
    font-size: .8rem;
    font-weight: 500;
    text-decoration: underline;
    text-underline-offset: .22em;
    cursor: pointer;
    transition: color .3s ease;
}

html.theme-soda .soda-cart-line__remove:hover {
    color: var(--ink);
}

html.theme-soda .soda-cart-line__qty-form {
    width: 100%;
    margin: .2rem 0 0;
    text-align: left;
}

html.theme-soda .soda-cart-line__qty-form .soda-label {
    display: block;
    margin-bottom: .35rem;
}

html.theme-soda .soda-cart-line__qty-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
}

html.theme-soda .soda-cart-line__qty-row .soda-input {
    padding-block: .55rem;
}

/* ============================================================== CHECKOUT

   21ST EXACT TRANSPLANT (UX-2) — source: checkout.md.

   The pre-transplant checkout geometry (one white plane split by a hairline,
   bracket legends, pill rail) is GONE. What replaces it is the source's own
   geometry, translated class-for-class out of Tailwind into hand-written CSS:

     shell          w-full mx-auto p-6 flex flex-col gap-6
     header         flex items-center justify-between; left column is
                    flex items-start gap-4 flex-col
     rail           flex items-center justify-start gap-4 sm:gap-6 py-4,
                    markers w-8 h-8 rounded-full border-2, connector w-8 h-0.5,
                    label text-sm font-medium hidden sm:block
     body           grid grid-cols-1 lg:grid-cols-3 gap-6,
                    main lg:col-span-2 flex flex-col gap-6, aside flex col gap-4
     card           rounded-xl border bg-card shadow-sm, flex col gap-6
     card header    p-6, h2 text-xl font-semibold flex items-center gap-2
     card content   p-6 pt-0, flex col gap-4 / gap-6
     card footer    p-6 pt-0, flex items-center (justify-between)

   Every number below is Tailwind's, taken from the --t21-* scale at the end of
   tokens.css. Nothing here is rounded to a Soda spacing step, and no font-size
   is shipped without the line-height Tailwind couples to it.

   WHAT DID NOT COME ACROSS, and why (the full ledger is in the run report):
   the source's card-number / CVV / expiry form, PayPal, Apple Pay, Google Pay
   and BNPL tiles, its promo row and its tax row have no U Jabka authority
   behind them, so they are omitted rather than faked. The rail runs to FOUR
   steps because the checkout has four; its marker and connector geometry is
   the source's, unchanged.
   ========================================================================== */

/* ------------------------------------------------------------- utilities */

/* Text that belongs in the accessibility tree and not in the picture. */
html.theme-soda .soda-t21-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ------------------------------------------------------------ icon sizes */

/* lucide-react renders at the Tailwind box the source states. These are the
   only sizes the three purchase-flow sources ask for. */
html.theme-soda .soda-t21-ico3 {
    width: .75rem;
    height: .75rem;
    flex: none;
}

html.theme-soda .soda-t21-ico4 {
    width: var(--t21-size-4);
    height: var(--t21-size-4);
    flex: none;
}

html.theme-soda .soda-t21-ico5 {
    width: var(--t21-size-5);
    height: var(--t21-size-5);
    flex: none;
}

html.theme-soda .soda-t21-ico12 {
    width: var(--t21-size-12);
    height: var(--t21-size-12);
    flex: none;
}

/* ---------------------------------------------------------------- buttons */

/*
 * shadcn Button, ported rather than routed through .soda-btn: the shared
 * component carries Soda's own padding, radius and arrow disc, and bending it
 * per page would restyle a component this page does not own. These classes add
 * a second, source-shaped control and leave .soda-btn untouched.
 *
 *   base      inline-flex items-center justify-center gap-2 whitespace-nowrap
 *             rounded-md text-sm font-medium, h-10 px-4 py-2
 *   size lg   h-11 px-8
 *   size sm   h-9 px-3
 *   outline   border border-input bg-background hover:bg-accent
 *   ghost     hover:bg-accent
 */
html.theme-soda .soda-t21-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--t21-2);
    white-space: nowrap;
    height: 2.5rem;
    padding: .5rem var(--t21-4);
    border: var(--t21-border) solid transparent;
    border-radius: var(--t21-radius-md);
    background: var(--accent);
    color: var(--accent-ink);
    font-family: var(--font-display);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--t21-dur-tw) var(--t21-ease-tw),
                border-color var(--t21-dur-tw) var(--t21-ease-tw),
                color var(--t21-dur-tw) var(--t21-ease-tw);
}

html.theme-soda .soda-t21-btn:hover {
    background: var(--btn-bg-hover);
    color: var(--accent-ink);
}

html.theme-soda .soda-t21-btn:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
}

html.theme-soda .soda-t21-btn[disabled],
html.theme-soda .soda-t21-btn[aria-disabled='true'] {
    opacity: .5;
    pointer-events: none;
}

html.theme-soda .soda-t21-btn--lg {
    height: 2.75rem;
    padding-inline: var(--t21-8);
}

html.theme-soda .soda-t21-btn--sm {
    height: 2.25rem;
    padding-inline: var(--t21-3);
}

/* order-submited.md states its CTA height literally: w-full h-12. */
html.theme-soda .soda-t21-btn--h12 {
    height: var(--t21-size-12);
    padding-inline: var(--t21-6);
}

html.theme-soda .soda-t21-btn--block {
    width: 100%;
}

html.theme-soda .soda-t21-btn--outline {
    border-color: var(--line-hi);
    background: transparent;
    color: var(--ink);
}

html.theme-soda .soda-t21-btn--outline:hover {
    background: var(--hover-wash);
    color: var(--ink);
}

html.theme-soda .soda-t21-btn--ghost {
    border-color: transparent;
    background: transparent;
    color: var(--ink);
}

html.theme-soda .soda-t21-btn--ghost:hover {
    background: var(--hover-wash);
    color: var(--ink);
}

/*
 * THE FINAL ACTION IS THE SHOP'S PRIMARY BUTTON, not a green one.
 *
 * The source's `bg-green-600 hover:bg-green-700` was carried over as the
 * plane's semantic --success. On the storefront it was the only green surface
 * anywhere: every other primary action (Do košíku, Pokračovat k pokladně, the
 * filter submit) is black with white text, so the one button that completes the
 * order read as a foreign control. It now takes the plain .soda-t21-btn
 * treatment — --accent / --accent-ink, which the plane resolves to black on
 * white — and keeps only its layout role (the class still places it in the nav
 * row and the [disabled] / loading / focus states are the shared ones).
 */

/* ------------------------------------------------------------------ badge */

/* shadcn Badge, variant secondary:
   inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs
   font-semibold, plus the source's own `flex items-center gap-1`. */
html.theme-soda .soda-t21-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--t21-1);
    padding: .125rem .625rem;
    border: var(--t21-border) solid transparent;
    border-radius: var(--t21-radius-full);
    background: rgba(255, 255, 255, .12);
    color: #fff;
    font-family: var(--font-display);
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
    font-weight: 600;
    white-space: nowrap;
}

/* -------------------------------------------------------------- page shell */

/* w-full mx-auto p-6 flex flex-col gap-6 */
html.theme-soda .soda-t21-checkout {
    width: 100%;
    margin-inline: auto;
    padding: var(--t21-6);
    display: flex;
    flex-direction: column;
    gap: var(--t21-6);
}

/* flex items-center justify-between */
html.theme-soda .soda-t21-checkout__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--t21-4);
}

/* flex items-start gap-4 flex-col */
html.theme-soda .soda-t21-checkout__headmain {
    display: flex;
    align-items: flex-start;
    flex-direction: column;
    gap: var(--t21-4);
}

/* flex flex-col gap-2 */
html.theme-soda .soda-t21-checkout__titles {
    display: flex;
    flex-direction: column;
    gap: var(--t21-2);
}

/* h1 text-2xl font-bold flex items-center gap-2 */
html.theme-soda .soda-t21-checkout__title {
    display: flex;
    align-items: center;
    gap: var(--t21-2);
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--t21-fs-2xl);
    line-height: var(--t21-lh-2xl);
    font-weight: 700;
    letter-spacing: normal;
    color: #fff;
}

/* p text-muted-foreground text-sm */
html.theme-soda .soda-t21-checkout__sub {
    margin: 0;
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: rgba(255, 255, 255, .72);
}

/* -------------------------------------------------------------- step rail */

/* flex items-center justify-start gap-4 sm:gap-6 py-4 */
html.theme-soda .soda-t21-rail ol {
    list-style: none;
    margin: 0;
    padding: var(--t21-4) 0;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    flex-wrap: wrap;
    gap: var(--t21-4);
}

@media (min-width: 640px) {
    html.theme-soda .soda-t21-rail ol {
        gap: var(--t21-6);
    }
}

/* The per-step wrapper: flex items-center gap-2 (marker+label group, then the
   connector). */
html.theme-soda .soda-t21-rail li {
    display: flex;
    align-items: center;
    gap: var(--t21-2);
}

/* The inner `flex items-center gap-2` group. It is a real button because
   checkout.js makes reached steps navigable; the source's div is not
   interactive, which is a no-React difference, not a visual one. */
html.theme-soda .soda-checkout__rail-step {
    display: flex;
    align-items: center;
    gap: var(--t21-2);
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    cursor: default;
}

html.theme-soda .soda-checkout__rail-step:not(:disabled) {
    cursor: pointer;
}

html.theme-soda .soda-checkout__rail-step:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
}

/* flex items-center justify-center w-8 h-8 rounded-full border-2
   transition-colors */
html.theme-soda .soda-checkout__rail-marker {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: var(--t21-size-8);
    height: var(--t21-size-8);
    border: var(--t21-border-2) solid var(--line-hi);
    border-radius: var(--t21-radius-full);
    color: var(--ink-2);
    transition: background-color var(--t21-dur-tw) var(--t21-ease-tw),
                border-color var(--t21-dur-tw) var(--t21-ease-tw),
                color var(--t21-dur-tw) var(--t21-ease-tw);
}

/* currentStep >= step: bg-primary border-primary. The source writes the glyph
   colour as `text-white`; on this ground --accent IS white, so the glyph takes
   --accent-ink for the same "inverted against the filled disc" result. */
html.theme-soda .soda-checkout__rail-step.is-active .soda-checkout__rail-marker,
html.theme-soda .soda-checkout__rail-step.is-done .soda-checkout__rail-marker {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--accent-ink);
}

/* currentStep > step swaps the step icon for a Check. */
html.theme-soda .soda-checkout__rail-check {
    display: none;
}

html.theme-soda .soda-checkout__rail-step.is-done .soda-checkout__rail-icon {
    display: none;
}

html.theme-soda .soda-checkout__rail-step.is-done .soda-checkout__rail-check {
    display: block;
}

/* text-sm font-medium hidden sm:block — the source really does drop the labels
   under 640px, so the port does too. The marker glyph (icon vs check) and
   aria-current keep the state readable without them. */
html.theme-soda .soda-checkout__rail-label {
    display: none;
    font-family: var(--font-display);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    font-weight: 500;
    color: var(--ink-2);
}

@media (min-width: 640px) {
    html.theme-soda .soda-checkout__rail-label {
        display: block;
    }
}

html.theme-soda .soda-checkout__rail-step.is-active .soda-checkout__rail-label,
html.theme-soda .soda-checkout__rail-step.is-done .soda-checkout__rail-label {
    color: var(--ink);
}

/* w-8 h-0.5, bg-primary once the step is behind the visitor. */
html.theme-soda .soda-t21-rail__conn {
    flex: none;
    width: var(--t21-size-8);
    height: .125rem;
    background: var(--line-hi);
}

html.theme-soda .soda-checkout__rail-step.is-done + .soda-t21-rail__conn {
    background: var(--accent);
}

/* State must never ride on colour alone. The source has no textual state at
   all, so the word stays in the accessibility tree and out of the picture. */
html.theme-soda .soda-checkout__rail-done {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* -------------------------------------------------------------- body grid */

/* grid grid-cols-1 lg:grid-cols-3 gap-6 */
html.theme-soda .soda-t21-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--t21-6);
}

@media (min-width: 1024px) {
    html.theme-soda .soda-t21-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* lg:col-span-2 flex flex-col gap-6 */
html.theme-soda .soda-t21-grid__main {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--t21-6);
}

/*
 * In the source the step Cards are direct children of that column. Here they
 * are wrapped in the ONE form the server has always received, so the form
 * itself carries the column's `flex flex-col gap-6` — otherwise the four cards
 * stack edge to edge whenever they are all on screen at once (no JavaScript,
 * or a validation re-render). The hidden CSRF and idempotency-key inputs are
 * display:none and contribute no gap.
 */
html.theme-soda .soda-checkout__form {
    display: flex;
    flex-direction: column;
    gap: var(--t21-6);
}

@media (min-width: 1024px) {
    html.theme-soda .soda-t21-grid__main {
        grid-column: span 2 / span 2;
    }
}

/* flex flex-col gap-4 */
html.theme-soda .soda-t21-grid__side {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--t21-4);
}

/* ------------------------------------------------------------------- card */

/*
 * rounded-xl border bg-card text-card-foreground shadow-sm.
 *
 * `bg-card` is .soda-plane here: the white Vesper surface and its whole token
 * scope, which is what "card" means everywhere else in this system. Only the
 * radius and the shadow are the source's.
 */
html.theme-soda .soda-plane.soda-t21-card {
    border-radius: var(--t21-radius-xl);
    box-shadow: var(--t21-shadow-sm);
}

/* CardHeader `p-6`, holding the source's `h2 text-xl font-semibold flex
   items-center gap-2`. The fieldset legend IS that heading, so the grouping
   semantics and the visual header are the same element. */
html.theme-soda .soda-checkout__group {
    margin: 0;
    padding: 0;
    border: 0;
    min-width: 0;
}

html.theme-soda .soda-t21-cardhead {
    display: flex;
    align-items: center;
    gap: var(--t21-2);
    float: none;
    width: 100%;
    margin: 0;
    padding: var(--t21-6);
    font-family: var(--font-display);
    font-size: var(--t21-fs-xl);
    line-height: var(--t21-lh-xl);
    font-weight: 600;
    letter-spacing: normal;
    text-transform: none;
    color: var(--ink);
}

html.theme-soda .soda-t21-cardhead:focus {
    outline: none;
}

html.theme-soda .soda-t21-cardhead:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
}

/*
 * CardContent is `p-6 pt-0` and the Card's own `gap-6` sits between it and the
 * header, i.e. 1.5rem of header padding + 1.5rem of gap. Reproduced as
 * margin-top rather than a flex gap so the fieldset's legend keeps its normal
 * block flow in every engine.
 */
html.theme-soda .soda-t21-cardbody {
    margin-top: var(--t21-6);
    padding: 0 var(--t21-6) var(--t21-6);
    display: flex;
    flex-direction: column;
    gap: var(--t21-4);
}

/* The source uses gap-6 inside the payment and review steps. */
html.theme-soda .soda-t21-cardbody--gap6 {
    gap: var(--t21-6);
}

/* CardFooter: `p-6 pt-0` + the same gap-6 above it. */
html.theme-soda .soda-checkout__nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--t21-4);
    margin-top: var(--t21-6);
    padding: 0 var(--t21-6) var(--t21-6);
}

/* Step 1's footer holds one button, `className="ml-auto"`. The final step's
   primary action takes the same right-hand seat when the Back button is not
   there (no JavaScript: every panel is on screen at once, so there is nothing
   to go back to). */
html.theme-soda .soda-checkout__nav .soda-checkout__next,
html.theme-soda .soda-checkout__nav > .soda-t21-btn--confirm {
    margin-left: auto;
}

/* Without JavaScript every nav button stays [hidden] except the submit, so the
   first three footers would otherwise contribute 3rem of empty card. */
html.theme-soda .soda-checkout__nav:not(:has(> *:not([hidden]))) {
    display: none;
}

/* At phone widths (as narrow as 320px) the footer's padding leaves too little
   row width for "Zpět" beside a long primary label like "Pokračovat na
   rekapitulaci" -- the source's .whitespace-nowrap button then pushes past
   the card edge. Below sm the pair stacks full-width instead, and the label
   is allowed to wrap rather than overflow if a translation ever runs long. */
@media (max-width: 639.98px) {
    html.theme-soda .soda-checkout__nav {
        flex-direction: column;
        align-items: stretch;
    }

    html.theme-soda .soda-checkout__nav > .soda-t21-btn {
        width: 100%;
        margin-left: 0;
        height: auto;
        min-height: 2.75rem;
        white-space: normal;
        text-align: center;
    }
}

/* The step/delivery visibility contract rides on the `hidden` attribute, and
   several building blocks (.soda-field is display:flex) would otherwise
   override the UA's [hidden] rule. Inside the checkout form, hidden means
   hidden — full stop. */
html.theme-soda .soda-checkout__form [hidden] {
    display: none !important;
}

/* ----------------------------------------------------------------- fields */

/*
 * Source field: `flex flex-col gap-2` holding a Label and an Input size lg.
 * These are MODIFIERS on the shared primitives, never a redefinition of them:
 * the field, label and input components keep their own rules in
 * components.css and only this page's copies carry the source's geometry.
 *
 * The column itself is stated HERE and not borrowed from .soda-field, which is
 * the one thing a modifier must not assume. A field that carries only
 * .soda-t21-field — as the four structured address inputs do — was otherwise a
 * plain block: `gap` applies to nothing, and the label sat flush against its
 * input at 0px where every other field on the card has 8px. Measured before
 * the fix: bare .soda-t21-field label-to-input 0px, paired .soda-field
 * .soda-t21-field 8px. Restating `flex column` costs the paired case nothing —
 * .soda-field already declares exactly that — and makes the modifier true on
 * its own.
 */
html.theme-soda .soda-t21-field {
    display: flex;
    flex-direction: column;
    gap: var(--t21-2);
    margin-bottom: 0;
}

/* shadcn Label: text-sm font-medium leading-none. */
html.theme-soda .soda-t21-label {
    font-size: var(--t21-fs-sm);
    line-height: 1;
    font-weight: 500;
    letter-spacing: normal;
    color: var(--ink);
}

/* Input, size lg: h-11, rounded-md, px-3, text-sm. */
html.theme-soda .soda-t21-input {
    height: 2.75rem;
    padding: 0 var(--t21-3);
    border-radius: var(--t21-radius-md);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
}

html.theme-soda .soda-t21-textarea {
    min-height: 5.5rem;
    padding: .625rem var(--t21-3);
    border-radius: var(--t21-radius-md);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
}

/* `leftIcon={<User className="h-4 w-4" />}`: the glyph sits inside the control
   box and the text starts clear of it. */
html.theme-soda .soda-t21-inputwrap {
    position: relative;
    display: block;
}

html.theme-soda .soda-t21-inputwrap .soda-t21-input {
    padding-left: 2.25rem;
}

html.theme-soda .soda-t21-inputicon {
    position: absolute;
    top: 50%;
    left: var(--t21-3);
    margin-top: -.5rem;
    color: var(--ink-3);
    pointer-events: none;
}

/* grid grid-cols-1 md:grid-cols-2 gap-4 */
html.theme-soda .soda-t21-fieldgrid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--t21-4);
}

@media (min-width: 768px) {
    html.theme-soda .soda-t21-fieldgrid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* grid grid-cols-1 md:grid-cols-3 gap-4 — the source's City / State / ZIP row,
   which the delivery address reuses verbatim for Mesto / PSC / Stat. */
html.theme-soda .soda-t21-fieldgrid--3 {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--t21-4);
}

@media (min-width: 768px) {
    html.theme-soda .soda-t21-fieldgrid--3 {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/*
 * SelectTrigger, size lg, className="text-sm" — the source sets the country /
 * state control at the same box as its Input size lg, so the two sit on one
 * baseline in the three-column row. Only the box is stated here; the border,
 * ground and focus ring stay the shared .soda-select's.
 */
html.theme-soda .soda-t21-select {
    height: 2.75rem;
    padding: 0 var(--t21-3);
    border-radius: var(--t21-radius-md);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
}

/* ------------------------------------------------------- delivery address */

/*
 * The source's address block is `flex flex-col gap-4` holding a full-width
 * Address field over the City / State / ZIP row. U Jabka's four structured
 * inputs (street, city, zip, country) are composed by the CONTROLLER into the
 * one multiline `delivery_address` CheckoutService has always validated, so
 * this is a presentation grid over an unchanged server contract.
 *
 * The wrapper keeps data-checkout-address-field: checkout.js shows and hides
 * it per delivery method, and the error summary anchors a `delivery_address`
 * failure here.
 */
html.theme-soda .soda-checkout__address {
    display: flex;
    flex-direction: column;
    gap: var(--t21-4);
    margin: 0;
}

/* THE TWO ADDRESS BLOCKS AND THE TOGGLE BETWEEN THEM.

   checkout.md has one address group and no sub-headings, because it sells to
   one address. Two groups need naming or the visitor cannot tell which is
   which, so each carries a quiet label rather than a second <h2>: the card
   already has its heading, and a heading per group would compete with it and
   break the source's type hierarchy. This is the Label treatment the source
   uses over its own field groups. */
html.theme-soda .soda-checkout__addr-legend {
    margin: 0;
    font-size: var(--t21-fs-sm);            /* text-sm                      */
    line-height: var(--t21-lh-sm);
    font-weight: 500;                       /* font-medium                  */
    color: var(--ink);
}

/* The toggle sits between the two groups and reads as one control, so the
   whole row is the label: a checkbox with a separate caption invites a click
   that lands on nothing. gap-2 is the source's own spacing between a control
   and its text. */
html.theme-soda .soda-checkout__same-address {
    margin: 0;
}

html.theme-soda .soda-checkout__same-address-label {
    display: flex;
    align-items: center;
    gap: var(--t21-2);                      /* gap-2 = .5rem                */
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
    cursor: pointer;
}

html.theme-soda .soda-checkout__same-address-label input {
    flex: none;
    cursor: pointer;
}

/* Drawn, for the reason the catalogue's filter panel records: these boxes sit
   on .soda-plane (opaque white) while tokens.css puts `color-scheme: dark` on
   the document, so the UA painted an unchecked box near-black and the
   accent-tinted tick (--accent is #000 here) barely differed from it. Both
   states are stated instead: white ground with an ink outline when empty, ink
   ground with a white tick when chosen. The tick is a background image because
   an <input> is a replaced element and does not reliably render ::before. The
   control stays the real, submitted, `required` checkbox. */
html.theme-soda .soda-t21-checkout :is(.soda-choice, .soda-checkout__same-address-label) input[type='checkbox'] {
    appearance: none;
    -webkit-appearance: none;
    /* Light scheme for whatever the UA still paints here (its default ring),
       so none of it arrives dressed for the dark ground. */
    color-scheme: light;
    flex: none;
    width: var(--t21-size-4);
    height: var(--t21-size-4);
    border: var(--t21-border) solid var(--ink);
    border-radius: var(--r-mark);
    background: #fff;
    background-repeat: no-repeat;
    background-position: center;
    background-size: 100% 100%;
    cursor: pointer;
    transition: background-color var(--t21-dur-tw) var(--t21-ease-tw),
                border-color var(--t21-dur-tw) var(--t21-ease-tw);
}

html.theme-soda .soda-t21-checkout :is(.soda-choice, .soda-checkout__same-address-label) input[type='checkbox']:checked {
    border-color: var(--ink);
    background-color: var(--ink);
    /* Same-origin asset, not a data: URI — the storefront CSP allows img-src
       'self' only. */
    background-image: url("../../icons/checkout-check-white.svg");
}

/* A failed consent is marked on the box as well as in the message under it,
   because the message is what the summary links to and the box is what has to
   be found. aria-invalid is what the server sets; :invalid is not used — an
   unticked required box is "invalid" from first paint. */
html.theme-soda .soda-t21-checkout :is(.soda-choice, .soda-checkout__same-address-label) input[type='checkbox'][aria-invalid='true'] {
    border-color: var(--error);
}

html.theme-soda .soda-t21-checkout :is(.soda-choice, .soda-checkout__same-address-label) input[type='checkbox']:disabled {
    border-color: rgba(0, 0, 0, .28);
    background-color: rgba(0, 0, 0, .06);
    cursor: not-allowed;
}

html.theme-soda .soda-t21-checkout :is(.soda-choice, .soda-checkout__same-address-label) input[type='checkbox']:disabled:checked {
    background-color: rgba(0, 0, 0, .45);
}

/* The page's own ring, written in full (`--focus-ring` is a WIDTH): a
   width-only `outline` shorthand resets outline-style to none and would leave
   this control with no indicator at all. Offset so the ring never sits on the
   outline it has to be told apart from. */
html.theme-soda .soda-t21-checkout :is(.soda-choice, .soda-checkout__same-address-label) input[type='checkbox']:focus-visible {
    outline: var(--focus-ring) solid var(--focus);
    outline-offset: var(--focus-offset);
}

/* The block's own hint and error sit under the grid, not inside a cell. */
html.theme-soda .soda-checkout__address > .soda-hint,
html.theme-soda .soda-checkout__address > .soda-error {
    margin: 0;
}

/* --------------------------------------------------------------- groups */

/* `flex flex-col gap-3 border-t pt-4` — the source's separated sub-block. The
   first group of a card has nothing above it to be separated from. */
html.theme-soda .soda-t21-group {
    display: flex;
    flex-direction: column;
    gap: var(--t21-3);
    border-top: var(--t21-border) solid var(--line);
    padding-top: var(--t21-4);
}

html.theme-soda .soda-t21-group--first {
    border-top: 0;
    padding-top: 0;
}

/* `<Label className="text-base font-medium">` above a group of choices. */
html.theme-soda .soda-t21-grouplabel {
    display: block;
    font-family: var(--font-display);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    font-weight: 500;
    color: var(--ink);
}

/* ------------------------------------------------------- shipping options */

/* `p-3 border rounded-ele cursor-pointer transition-colors`, stacked in a
   `flex flex-col gap-4` list. */
html.theme-soda .soda-checkout__choices {
    display: flex;
    flex-direction: column;
    gap: var(--t21-4);
    margin: 0;
}

html.theme-soda .soda-t21-ship {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--t21-3);
    padding: var(--t21-3);
    border: var(--t21-border) solid var(--line);
    border-radius: var(--t21-radius-lg);
    background: transparent;
    cursor: pointer;
    transition: background-color var(--t21-dur-tw) var(--t21-ease-tw),
                border-color var(--t21-dur-tw) var(--t21-ease-tw);
}

html.theme-soda .soda-t21-ship:hover {
    border-color: var(--line);
    background: var(--hover-wash);
}

/* selectedShipping === method.id → border-primary bg-primary/5 */
html.theme-soda .soda-t21-ship:has(input:checked) {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb), .05);
}

/* The left half: `flex items-center gap-3` with the dot first. */
html.theme-soda .soda-t21-ship__main {
    display: flex;
    align-items: center;
    gap: var(--t21-3);
    min-width: 0;
}

/*
 * `w-4 h-4 rounded-full border-2`, filled when selected. It is the REAL radio,
 * not a decorative div: the form must submit without JavaScript. Drawn for the
 * same reason as the checkbox above — white when empty, ink with a white centre
 * when chosen, so the two states differ by fill and not by hue.
 */
html.theme-soda .soda-t21-checkout :is(.soda-t21-ship, .soda-t21-method) input[type='radio'] {
    appearance: none;
    -webkit-appearance: none;
    color-scheme: light;
    flex: none;
    width: var(--t21-size-4);
    height: var(--t21-size-4);
    margin: 0;
    border: var(--t21-border-2) solid var(--line-hi);
    border-radius: var(--t21-radius-full);
    background: #fff;
    cursor: pointer;
    transition: background-color var(--t21-dur-tw) var(--t21-ease-tw),
                border-color var(--t21-dur-tw) var(--t21-ease-tw);
}

html.theme-soda .soda-t21-checkout :is(.soda-t21-ship, .soda-t21-method) input[type='radio']:checked {
    border-color: var(--accent);
    background: var(--accent);
    box-shadow: inset 0 0 0 3px #fff;
}

html.theme-soda .soda-t21-checkout :is(.soda-t21-ship, .soda-t21-method) input[type='radio']:disabled {
    border-color: rgba(0, 0, 0, .28);
    background: rgba(0, 0, 0, .06);
    cursor: not-allowed;
}

html.theme-soda .soda-t21-checkout :is(.soda-t21-ship, .soda-t21-method) input[type='radio']:disabled:checked {
    background: rgba(0, 0, 0, .45);
}

/* The ring written in full: `--focus-ring` is a WIDTH, and a width-only
   `outline` shorthand resets outline-style to none — no indicator at all. */
html.theme-soda .soda-t21-checkout :is(.soda-t21-ship, .soda-t21-method) input[type='radio']:focus-visible {
    outline: var(--focus-ring) solid var(--focus);
    outline-offset: var(--focus-offset);
}

/*
 * FORCED COLORS: the drawn box is handed back to the UA. A system-colour paint
 * has a checked state of its own, whereas the painted one relies on backgrounds
 * and a background-image tick, both of which are dropped here. Focus, disabled
 * and the `required`/aria-invalid semantics are the control's own and survive.
 */
@media (forced-colors: active) {
    html.theme-soda .soda-t21-checkout :is(.soda-choice, .soda-checkout__same-address-label) input[type='checkbox'],
    html.theme-soda .soda-t21-checkout :is(.soda-t21-ship, .soda-t21-method) input[type='radio'] {
        appearance: auto;
        -webkit-appearance: auto;
        forced-color-adjust: auto;
        width: auto;
        height: auto;
        border: 0;
        border-radius: 0;
        background: none;
        background-image: none;
        box-shadow: none;
    }

    /* The checked/disabled/invalid paints are more specific than the rule
       above, so they are unset here too. */
    html.theme-soda .soda-t21-checkout :is(.soda-choice, .soda-checkout__same-address-label) input[type='checkbox']:is(:checked, :disabled, :disabled:checked, [aria-invalid='true']),
    html.theme-soda .soda-t21-checkout :is(.soda-t21-ship, .soda-t21-method) input[type='radio']:is(:checked, :disabled, :disabled:checked) {
        border: 0;
        background: none;
        background-image: none;
        box-shadow: none;
    }
}

html.theme-soda .soda-t21-ship .soda-choice__title {
    display: block;
    font-family: var(--font-display);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    font-weight: 500;
    color: var(--ink);
}

html.theme-soda .soda-t21-ship .soda-choice__desc {
    display: block;
    margin-top: 0;
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
}

/* `<div className="font-semibold">${method.price}</div>` */
html.theme-soda .soda-choice__meta {
    display: block;
    flex: none;
    font-family: var(--font-display);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    font-weight: 600;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------- payment method tiles */

/* grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 */
html.theme-soda .soda-t21-methods {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--t21-3);
}

@media (min-width: 640px) {
    html.theme-soda .soda-t21-methods {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1024px) {
    html.theme-soda .soda-t21-methods {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* `flex items-center gap-3 p-4 border-2 rounded-ele transition-colors
   text-left`, selected → border-primary bg-primary/5. Launch offers exactly
   one method and submits no payment field, so the tile is permanently the
   selected one and carries no input to tamper with. */
html.theme-soda .soda-t21-method {
    display: flex;
    align-items: center;
    gap: var(--t21-3);
    padding: var(--t21-4);
    border: var(--t21-border-2) solid var(--accent);
    border-radius: var(--t21-radius-lg);
    background: rgba(var(--accent-rgb), .05);
    text-align: left;
}

html.theme-soda .soda-t21-method__icon {
    color: var(--accent);
}

html.theme-soda .soda-t21-method__title {
    display: block;
    font-family: var(--font-display);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    font-weight: 500;
    color: var(--ink);
}

html.theme-soda .soda-t21-method__sub {
    display: block;
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
    color: var(--ink-2);
}

/*
 * The selected-method panel: `border-t pt-6` then a tinted `p-4` box with a
 * `h-5 w-5` icon, an `h4 font-medium` and a `p text-sm mt-1`.
 *
 * The source tints each panel to its brand (blue-50/200/900/700 for the bank
 * transfer). This surface carries no brand colour at all — every plane rule in
 * the system is monochrome — so the tint is the plane's own neutral wash and
 * the text keeps full-contrast ink. Geometry, hierarchy and icon size are the
 * source's, unchanged.
 */
html.theme-soda .soda-t21-methodinfo {
    border-top: var(--t21-border) solid var(--line);
    padding-top: var(--t21-6);
}

html.theme-soda .soda-t21-methodinfo__box {
    display: flex;
    align-items: flex-start;
    gap: var(--t21-3);
    padding: var(--t21-4);
    border: var(--t21-border) solid var(--line);
    border-radius: var(--t21-radius-lg);
    background: var(--surface-2);
}

html.theme-soda .soda-t21-methodinfo__icon {
    margin-top: .125rem;
    color: var(--ink);
}

html.theme-soda .soda-t21-methodinfo__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    font-weight: 500;
    color: var(--ink);
}

html.theme-soda .soda-t21-methodinfo__text {
    margin: var(--t21-1) 0 0;
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
}

/* --------------------------------------------------------- pickup point */

/*
 * THE PACKETA BLOCK. It sits immediately under the delivery-method list and it
 * is the next required action, so it is an ACCENTED block, not a hint.
 *
 * Its geometry is the source's, borrowed from the two places checkout.md
 * accents something: the payment tile's selected treatment (`p-4 border-2
 * rounded-ele border-primary bg-primary/5`) for the frame, and the
 * selected-method info panel (`flex items-start gap-3` with an `h-5 w-5` icon
 * over an `h4 font-medium` and a `text-sm` line) for the inside. Nothing here
 * is a new spacing step.
 *
 * THREE STATES, all driven by the FROZEN data hooks so the rules cannot drift
 * from the markup or from the widget integration:
 *
 *   nothing chosen  the default. Accent frame, accent wash, and the select
 *                   control is promoted to the primary fill even if the markup
 *                   still carries .soda-t21-btn--outline (see below).
 *   chosen          [data-packeta-selected] is un-hidden. The frame quiets to
 *                   the ordinary hairline over the plane's neutral wash, and
 *                   the control reads as a secondary "Zmenit".
 *   error/missing   [data-packeta-missing], [data-packeta-widget-error] or a
 *                   server-rendered .soda-error is un-hidden. The frame takes
 *                   --error, matching how aria-invalid is drawn on every other
 *                   control in this system. The strings themselves are
 *                   untouched.
 *
 * checkout.js scrolls this block into view and focuses [data-packeta-select]
 * when the visitor picks the Packeta method; scroll-margin-top keeps it clear
 * of the fixed header, which is the same 6rem the content pages use.
 */
/* Packeta appends its iframe outside the checkout, under body. Keep the
   provider's picker above the page even when no overlay positioning arrives
   with it. Its inline pixel dimensions must not outlive a viewport resize.
   Closing the provider widget removes the iframe and releases this lock. */
html.theme-soda iframe#packeta-widget:not([hidden]) {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: block;
    width: 100% !important;
    height: 100% !important;
    height: 100dvh !important;
    max-width: none;
    max-height: none;
    margin: 0;
    border: 0;
    background: #fff;
}

html.theme-soda:has(iframe#packeta-widget:not([hidden])),
html.theme-soda:has(iframe#packeta-widget:not([hidden])) body {
    overflow: hidden;
}

html.theme-soda .soda-checkout__packeta {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--t21-3);
    margin: 0;
    padding: var(--t21-4);
    border: var(--t21-border-2) solid var(--accent);
    border-radius: var(--t21-radius-lg);
    background: rgba(var(--accent-rgb), .05);
    scroll-margin-top: 6rem;
    transition: border-color var(--t21-dur-tw) var(--t21-ease-tw),
                background-color var(--t21-dur-tw) var(--t21-ease-tw);
}

/* The wrapper still carries .soda-field for the shared form semantics; its own
   column gap and trailing margin would fight the block's grammar. */
html.theme-soda .soda-field.soda-checkout__packeta {
    margin-bottom: 0;
}

/* `flex items-start gap-3` — icon, then the text column. */
html.theme-soda .soda-checkout__packeta-head {
    display: flex;
    align-items: flex-start;
    gap: var(--t21-3);
    min-width: 0;
}

html.theme-soda .soda-checkout__packeta-icon {
    margin-top: .125rem;
    color: var(--accent);
}

html.theme-soda .soda-checkout__packeta-body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--t21-1);
}

/* `h4 font-medium` */
html.theme-soda .soda-checkout__packeta-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    font-weight: 500;
    color: var(--ink);
}

/* `p text-sm` — the prompt in the empty state. */
html.theme-soda .soda-checkout__packeta-prompt {
    margin: 0;
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
}

html.theme-soda .soda-checkout__packeta-selected {
    margin: 0;
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink);
    overflow-wrap: anywhere;
}

html.theme-soda .soda-checkout__packeta-selected strong {
    font-family: var(--font-display);
    font-weight: 600;
}

/* The control keeps its own width and never stretches to the block. */
html.theme-soda .soda-checkout__packeta-btn,
html.theme-soda .soda-checkout__packeta [data-packeta-select] {
    align-self: flex-start;
    margin: 0;
}

/* The block's own messages: hint, error and the noscript line all sit under
   the control at the block's gap, never inside the head row. */
html.theme-soda .soda-checkout__packeta > .soda-hint,
html.theme-soda .soda-checkout__packeta > .soda-error,
html.theme-soda .soda-checkout__packeta > noscript > .soda-hint {
    margin: 0;
}

/*
 * CHOSEN — [data-packeta-selected] carries the confirmed name, so the block has
 * nothing left to demand and steps back to an ordinary bordered panel.
 */
html.theme-soda .soda-checkout__packeta:has([data-packeta-selected]:not([hidden])) {
    border-width: var(--t21-border);
    border-color: var(--line);
    background: var(--surface-2);
}

html.theme-soda .soda-checkout__packeta:has([data-packeta-selected]:not([hidden])) .soda-checkout__packeta-icon {
    color: var(--ink-2);
}

/* The prompt is the empty state's line; once a point is chosen it is noise. */
html.theme-soda .soda-checkout__packeta:has([data-packeta-selected]:not([hidden])) .soda-checkout__packeta-prompt {
    display: none;
}

/*
 * ERROR / MISSING — the frame takes --error, exactly as aria-invalid does on
 * every input. The verbatim strings are the markup's and are not restyled.
 */
html.theme-soda .soda-checkout__packeta:has([data-packeta-missing]:not([hidden])),
html.theme-soda .soda-checkout__packeta:has([data-packeta-widget-error]:not([hidden])),
html.theme-soda .soda-checkout__packeta:has(> .soda-error:not([hidden])) {
    border-width: var(--t21-border-2);
    border-color: var(--error);
    /* NOT the accent wash the empty state uses: with the same ground, error and
       "nothing chosen yet" differed only by a border colour, which is the one
       cue a red-green colour vision deficiency is least likely to carry. */
    /* color-mix off the real --error token, so the wash follows the theme
       instead of pinning a colour this file made up. */
    background: color-mix(in srgb, var(--error) 8%, transparent);
}

html.theme-soda .soda-checkout__packeta:has([data-packeta-missing]:not([hidden])) .soda-checkout__packeta-icon,
html.theme-soda .soda-checkout__packeta:has([data-packeta-widget-error]:not([hidden])) .soda-checkout__packeta-icon,
html.theme-soda .soda-checkout__packeta:has(> .soda-error:not([hidden])) .soda-checkout__packeta-icon {
    color: var(--error);
}

/*
 * THE SELECT CONTROL IS PRIMARY WHILE NOTHING IS CHOSEN.
 *
 * The contract asks for a primary, not an outline, button in the empty state.
 * The markup inherited from the previous pass still writes
 * `.soda-t21-btn--outline` on it, and an outline button inside an accented
 * block reads as a footnote — which is the exact failure this correction
 * exists to fix. Rather than depend on a class being removed elsewhere, the
 * promotion is stated here and scoped to the one state that wants it, so it
 * cannot leak to the chosen state or to any other outline button.
 */
html.theme-soda .soda-checkout__packeta:not(:has([data-packeta-selected]:not([hidden]))) [data-packeta-select].soda-t21-btn--outline {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--accent-ink);
}

html.theme-soda .soda-checkout__packeta:not(:has([data-packeta-selected]:not([hidden]))) [data-packeta-select].soda-t21-btn--outline:hover {
    background: var(--accent);
    color: var(--accent-ink);
    opacity: .9;
}

/* ---------------------------------------------------------- review step */

/* Each review block is `flex flex-col gap-2`: an `h3 font-medium` over a
   `text-sm text-muted-foreground p-3 bg-accent rounded-ele` box. */
html.theme-soda .soda-checkout__review {
    display: flex;
    flex-direction: column;
    gap: var(--t21-6);
    margin: 0;
}

html.theme-soda .soda-checkout__review > div {
    display: flex;
    flex-direction: column;
    gap: var(--t21-2);
}

html.theme-soda .soda-checkout__review dt {
    font-family: var(--font-display);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    font-weight: 500;
    letter-spacing: normal;
    text-transform: none;
    color: var(--ink);
}

html.theme-soda .soda-checkout__review dd {
    margin: 0;
    padding: var(--t21-3);
    border-radius: var(--t21-radius-lg);
    background: var(--surface-2);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
    overflow-wrap: anywhere;
}

/* `flex items-start gap-2 border-t pt-4` with a `text-sm leading-relaxed`
   label. leading-relaxed is 1.625. */
html.theme-soda .soda-checkout__terms {
    margin: 0;
    padding-top: var(--t21-4);
    border-top: var(--t21-border) solid var(--line);
}

html.theme-soda .soda-checkout__terms .soda-choice {
    align-items: flex-start;
    gap: var(--t21-2);
    padding: 0;
    border: 0;
    background: transparent;
}

html.theme-soda .soda-checkout__terms .soda-choice:hover {
    background: transparent;
}

html.theme-soda .soda-checkout__terms .soda-choice input {
    margin-top: .2rem;
}

html.theme-soda .soda-checkout__terms .soda-choice__text {
    font-size: var(--t21-fs-sm);
    line-height: 1.625;
    color: var(--ink);
}

/* `<Button variant="link" className="p-0 h-auto text-sm">` */
html.theme-soda .soda-checkout__terms .soda-choice__text a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: .18em;
}

/* --------------------------------------------------------- error summary */

/* Announced on render (role="alert" in the markup) after the POST round-trip.
   Each item is a real in-page link to the control that failed. The source has
   no server round-trip and therefore no equivalent; this block is U Jabka's
   own and keeps its shape. */
html.theme-soda .soda-checkout__errors {
    margin-bottom: 0;
}

html.theme-soda .soda-checkout__errors h2 {
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    margin-bottom: var(--t21-2);
    color: var(--error);
}

html.theme-soda .soda-checkout__errors ul {
    margin: 0;
    padding-left: 1.1rem;
    display: grid;
    gap: var(--t21-1);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
}

html.theme-soda .soda-checkout__errors a {
    color: var(--error);
    text-decoration: underline;
    text-underline-offset: .2em;
}

/* ----------------------------------------------------------- order summary */

/* `<Card className="flex flex-col gap-5">`: CardHeader p-6, gap-5, CardContent
   p-6 pt-0 flex flex-col gap-4. */
html.theme-soda .soda-t21-summary__head {
    display: flex;
    align-items: center;
    gap: var(--t21-2);
    margin: 0;
    padding: var(--t21-6);
    font-family: var(--font-display);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    font-weight: 600;
    letter-spacing: normal;
    color: var(--ink);
}

html.theme-soda .soda-t21-summary__body {
    margin-top: var(--t21-5);
    padding: 0 var(--t21-6) var(--t21-6);
    display: flex;
    flex-direction: column;
    gap: var(--t21-4);
}

/* The item list: `flex flex-col gap-4`, each row `flex gap-3`. */
html.theme-soda .soda-t21-summary__items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--t21-4);
}

html.theme-soda .soda-t21-item {
    display: flex;
    gap: var(--t21-3);
}

html.theme-soda .soda-t21-item--unavailable {
    opacity: .8;
}

/* `relative w-12 h-12 flex-shrink-0` with a `w-full h-full object-cover
   rounded-md` image. The cart carries no product photography — it has never
   had any — so the box holds the same sprite glyph the cart lines use, at the
   source's exact box size and radius. */
html.theme-soda .soda-t21-item__media {
    position: relative;
    flex: none;
    width: var(--t21-size-12);
    height: var(--t21-size-12);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--t21-radius-md);
    background: var(--surface-2);
    color: var(--ink-3);
}

html.theme-soda .soda-t21-item__media svg {
    width: auto;
    height: 1.75rem;
}

html.theme-soda .soda-t21-item__media .ico {
    width: var(--t21-size-5);
    height: var(--t21-size-5);
}

/* The source's `rounded-md` IMAGE, now that the summary has a real one to put
   there. The box is 3rem square and the photograph is fitted inside it, so the
   recapitulation shows the same device the cart line and the catalogue tile
   show. The rounding lives on the photograph itself, not on the box: the
   quantity chip is positioned OUTSIDE the box, so the box must not start
   clipping its children. */
html.theme-soda .soda-t21-item__media--photo {
    background: #fff;
}

html.theme-soda .soda-t21-item__photo {
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    object-fit: contain;
    object-position: center;
    border-radius: var(--t21-radius-md);
}

/* Badge: `absolute -top-1 -right-1 text-xs min-w-5 h-5 flex items-center
   justify-center`, default (primary) variant. */
html.theme-soda .soda-t21-item__qty {
    position: absolute;
    top: calc(var(--t21-1) * -1);
    right: calc(var(--t21-1) * -1);
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: var(--t21-5);
    height: var(--t21-5);
    padding-inline: var(--t21-1);
    border-radius: var(--t21-radius-full);
    background: var(--accent);
    color: var(--accent-ink);
    font-family: var(--font-display);
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* `flex-1 min-w-0` */
html.theme-soda .soda-t21-item__body {
    flex: 1;
    min-width: 0;
}

/* `p text-sm font-medium truncate` */
html.theme-soda .soda-t21-item__name {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    font-weight: 500;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The source's second row is `flex items-center gap-2` holding the unit price
   and, when there is one, a struck-through original. U Jabka has no struck
   price (the direction forbids it) and no per-line unit price in this view, so
   the same row carries the spec and the availability truth the checkout recap
   is required to repeat. */
html.theme-soda .soda-t21-item__meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--t21-2);
    margin: 0;
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
    color: var(--ink-2);
}

html.theme-soda .soda-t21-item__body .soda-error {
    margin: var(--t21-1) 0 0;
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
}

/* `<div className="text-sm font-semibold">` */
html.theme-soda .soda-t21-item__total {
    flex: none;
    font-family: var(--font-display);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    font-weight: 600;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* `flex flex-col gap-2 border-t pt-4`, rows `flex justify-between text-sm`,
   grand total `flex justify-between font-semibold text-lg border-t pt-2`.
   There is no promo row, no discount row, no shipping row and no tax row:
   U Jabka prices are VAT-inclusive, delivery is free and there is no promo
   authority, so those four source rows have nothing to report. */
html.theme-soda .soda-t21-totals {
    display: flex;
    flex-direction: column;
    gap: var(--t21-2);
    margin: 0;
    padding-top: var(--t21-4);
    border-top: var(--t21-border) solid var(--line);
}

html.theme-soda .soda-t21-totals > div {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--t21-4);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink);
}

html.theme-soda .soda-t21-totals dt {
    font-size: inherit;
    color: inherit;
}

html.theme-soda .soda-t21-totals dd {
    margin: 0;
    font-variant-numeric: tabular-nums;
}

html.theme-soda .soda-t21-totals__grand {
    padding-top: var(--t21-2);
    border-top: var(--t21-border) solid var(--line);
    font-family: var(--font-display);
    font-size: var(--t21-fs-lg);
    line-height: var(--t21-lh-lg);
    font-weight: 600;
}

html.theme-soda .soda-t21-totals__grand dt {
    font-family: var(--font-display);
    font-size: inherit;
    font-weight: 600;
    color: var(--ink);
}

/* The one line of launch truth the source has no slot for. It gets its OWN
   class rather than reusing .soda-commerce__note: that one is the cart's, and
   a second rule on the same selector would silently restyle the cart page. */
html.theme-soda .soda-t21-summary__note {
    margin: 0;
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
    color: var(--ink-2);
}

/* ------------------------------------------------------------ trust card */

/* `<CardContent className="p-4">` with `flex items-center gap-3 text-sm`, a
   `h-5 w-5` icon, a `font-medium` line and a muted line under it. The source's
   copy claims SSL encryption; this one states what is actually true of this
   checkout, which is that no payment credential is entered at all. */
html.theme-soda .soda-t21-trust {
    display: flex;
    align-items: center;
    gap: var(--t21-3);
    padding: var(--t21-4);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
}

html.theme-soda .soda-t21-trust__icon {
    color: var(--success);
}

html.theme-soda .soda-t21-trust__title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 500;
    color: var(--ink);
}

html.theme-soda .soda-t21-trust__text {
    margin: 0;
    color: var(--ink-2);
}

/* ---------------------------------------------------------------- payinfo */

/* U Jabka's own closing note. The source has no equivalent, so it takes the
   plainest card shape available: CardContent `p-6`. */
html.theme-soda .soda-checkout__payinfo {
    padding: var(--t21-6);
}

html.theme-soda .soda-checkout__payinfo h2 {
    margin: 0 0 var(--t21-2);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    color: var(--ink);
}

html.theme-soda .soda-checkout__payinfo p {
    margin: 0 0 var(--t21-2);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
    max-width: 62ch;
}

html.theme-soda .soda-checkout__payinfo p:last-child {
    margin-bottom: 0;
}

html.theme-soda .soda-checkout__payinfo a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: .18em;
}

/* ------------------------------------------- primary commerce action (cart) */

/*
 * THE PRIMARY COMMERCE CONTROL IS AN INK BLOCK.
 *
 * It used to be an accent TINT — rgba(accent, .08) behind ink — which on the
 * white plane resolved to an 8% black wash under white button text and was
 * unreadable. On a surface with no accent, the single most important action on
 * the page is the one solid, filled thing on it. This governs the CART's
 * continue action; the checkout's own controls are the transplanted buttons
 * above.
 */
html.theme-soda .soda-commerce__cta:not([disabled]) {
    border-color: #000;
    background: #000;
    color: #fff;
}

html.theme-soda .soda-commerce__cta:not([disabled]):hover {
    border-color: #1c1f24;
    background: #1c1f24;
}

/* ------------------------------------------------------- confirmation lines */

/* The confirmation branch is NOT in any authorised source — checkout.md has no
   post-order screen — so it keeps the ruled presentation it was reviewed with.
   These rules serve it and the order pages only. */
html.theme-soda .soda-checkout__lines {
    list-style: none;
    margin: 0 0 1.2rem;
    padding: 0;
    display: grid;
    gap: .1rem;
}

html.theme-soda .soda-checkout__line {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: .9rem;
    padding: .8rem 0;
    border-bottom: 1px solid var(--line);
}

html.theme-soda .soda-checkout__line:last-child {
    border-bottom: 0;
}

html.theme-soda .soda-checkout__line-main {
    min-width: 0;
}

html.theme-soda .soda-checkout__line-name {
    margin: 0;
    font-family: var(--font-display);
    font-size: .92rem;
    font-weight: 600;
    letter-spacing: -.01em;
}

html.theme-soda .soda-checkout__line-spec {
    margin: .2rem 0 0;
    font-size: .76rem;
    line-height: 1.45;
    color: var(--ink-2);
}

html.theme-soda .soda-checkout__line-main .soda-error {
    margin: .35rem 0 0;
    font-size: .76rem;
}

html.theme-soda .soda-checkout__line-side {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: .2rem;
    text-align: right;
    flex: none;
}

html.theme-soda .soda-checkout__line-qty {
    font-size: .76rem;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
}

html.theme-soda .soda-checkout__line-side .soda-price {
    font-size: .98rem;
}

/* ------------------------------------------------------------ confirmation */

html.theme-soda .soda-checkout__success {
    max-width: 62rem;
}

html.theme-soda .soda-checkout__success .soda-sec-title {
    font-size: clamp(1.9rem, 3.4vw, 3rem);
}

html.theme-soda .soda-checkout__success .soda-lead {
    max-width: 54ch;
    margin-top: .9rem;
}

html.theme-soda .soda-checkout__facts {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: .8rem;
    margin: clamp(1.4rem, 3vw, 2rem) 0 1.2rem;
}

@media (max-width: 620px) {
    html.theme-soda .soda-checkout__facts {
        grid-template-columns: 1fr;
    }
}

/*
 * BANK DETAILS ARE RULED BLOCKS, not boxes.
 *
 * These carry the account, the IBAN, the variable symbol and the amount — the
 * four values a buyer retypes into their bank. A hairline above each block and
 * nothing else keeps them serious and scannable.
 */
html.theme-soda .soda-checkout__facts > div {
    padding: .85rem 0 0;
    border: 0;
    border-top: 1px solid var(--line);
    border-radius: var(--r-none);
    background: transparent;
}

html.theme-soda .soda-checkout__facts dt {
    font-size: .7rem;
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-2);
}

html.theme-soda .soda-checkout__facts dd {
    margin: .35rem 0 0;
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--ink);
    overflow-wrap: anywhere;
    font-variant-numeric: tabular-nums;
}

html.theme-soda .soda-checkout__reservation {
    margin-bottom: 1.4rem;
}

/* ------------------------------------------------ launch128 — sale terms
 *
 * Condition, VAT regime and warranty are FACTS, not promotion: they take the
 * same muted small type the rest of the line meta uses, and they get no badge,
 * no colour and no icon of their own. The § 90 sentence is a sentence, so it
 * gets a line to itself rather than being squeezed into the chip row.
 *
 * Nothing here is a new visual vocabulary — every value is an existing token,
 * and the black/white system and the responsive behaviour are untouched.
 */
html.theme-soda .soda-t21-item__note {
    margin: .35rem 0 0;
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
    color: var(--ink-2);
}

/* The ordered-goods block on the confirmation page: the order's own frozen
   snapshot, in the same ruled-row grammar as the cart lines. */
html.theme-soda .soda-checkout__ordered {
    margin: 0 0 1.4rem;
    padding: clamp(1.1rem, 2.4vw, 1.6rem);
}

html.theme-soda .soda-checkout__ordered-list {
    list-style: none;
    margin: 1rem 0 0;
    padding: 0;
}

html.theme-soda .soda-checkout__ordered-item {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    padding: .85rem 0;
    border-top: 1px solid var(--line);
}

html.theme-soda .soda-checkout__ordered-name {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--ink);
}

html.theme-soda .soda-checkout__ordered-spec,
html.theme-soda .soda-checkout__ordered-terms,
html.theme-soda .soda-checkout__ordered-note {
    margin: .3rem 0 0;
    font-size: .78rem;
    line-height: 1.45;
    color: var(--ink-2);
}

html.theme-soda .soda-checkout__ordered-side {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: .3rem;
    text-align: right;
    font-size: .8rem;
    color: var(--ink-2);
    white-space: nowrap;
}

/* The agreed 12-month shortening. It is an ordinary notice, deliberately not an
   alarm: it states an agreed term, it is not a warning about one. */
html.theme-soda .soda-checkout__used-agreement {
    margin: 1rem 0 0;
    font-size: .8rem;
    line-height: 1.5;
}

/* Below the summary breakpoint the ordered rows stack, like the cart lines. */
@media (max-width: 620px) {
    html.theme-soda .soda-checkout__ordered-item {
        flex-direction: column;
        gap: .45rem;
    }

    html.theme-soda .soda-checkout__ordered-side {
        flex-direction: row;
        align-items: baseline;
        gap: .7rem;
        text-align: left;
    }
}

html.theme-soda .soda-checkout__lines-title {
    font-size: 1rem;
    margin-bottom: .4rem;
}

html.theme-soda .soda-checkout__success .soda-btn {
    margin-top: .6rem;
}

/* ============================================================
   WS-1 — payment-waiting page and checkout payment instructions
   ============================================================
   The QR is an inline <svg> (the storefront CSP allows no data: image
   source), so it has no intrinsic pixel size and MUST be constrained here or
   it fills its container. A white plate under it is deliberate: the code is
   drawn black-on-white and needs that contrast in the dark theme too. */

html.theme-soda .soda-payment__deadline {
    margin: var(--t21-4) 0 0;
}

html.theme-soda .soda-payment__note,
html.theme-soda .soda-payment__qr-note,
html.theme-soda .soda-payment__poll-note {
    color: var(--ink-2);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
}

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

html.theme-soda .soda-qr {
    display: block;
    width: 100%;
    max-width: 15rem;
    height: auto;
    margin: .4rem auto 1rem;
    border-radius: var(--r-card);
    background: #fff;
}

html.theme-soda .soda-checkout__payinfo-note {
    margin-top: 1.2rem;
}

/* ===================================== ORDER + PAYMENT PAGES (§8)

   The order confirmation, the payment instructions and every terminal order
   state sit on the same white .soda-plane over the same dark Vesper ground as
   the cart and the checkout. Their content is the most consequential text on
   the site — an amount, an account number and a deadline — so it is set plainly
   and ruled, never decorated.
   -------------------------------------------------------------------------- */

html.theme-soda .soda-checkout__success,
html.theme-soda .soda-payment__result {
    padding: 2.222vw;
}

/* Already on a plane: the payment block closes the confirmation on a rule
   rather than opening a second box inside the first. */
html.theme-soda .soda-checkout__payment {
    margin-top: clamp(1.6rem, 3vw, 2.4rem);
    padding-top: clamp(1.6rem, 3vw, 2.4rem);
    border-top: 1px solid var(--line);
}

html.theme-soda .soda-checkout__payment .soda-qr {
    max-width: 12rem;
}

html.theme-soda .soda-payment__instructions,
html.theme-soda .soda-payment__qr {
    padding: 0;
    border: 0;
    background: transparent;
}

/* The amount and the variable symbol are what a buyer retypes; they get the
   display face and tabular figures so a digit cannot be misread. */
html.theme-soda .soda-checkout__facts .soda-price {
    font-family: var(--font-display);
    font-size: var(--text-stat);
    font-weight: 300;
    line-height: 1.1;
    letter-spacing: var(--tracking-title);
}

/* ------------------------------------------------------------------------
   21ST EXACT TRANSPLANT — the payment stepper (source: payment-popup.md).

   The source is a vertical stepper card:

     shell      w-screen max-w-md mx-auto
     card       rounded-xl border bg-card text-card-foreground shadow-sm
     header     p-6, flex items-center justify-between,
                h3 font-semibold leading-none tracking-tight text-lg
                + a text-xs font-semibold pill on a green/10 ground
     body       p-6 pt-0, <ol className="space-y-2">
     step       flex items-start gap-4; left rail is a relative h-8 w-8 box
                holding CheckCircle2 h-8 w-8 (done) or Circle h-8 w-8 with the
                number centred; the ACTIVE number becomes an h-6 w-6 m-1 filled
                primary disc with inverted text and the circle scales to 1.1
                over .2s; connector `mt-2 h-8 w-px`
     right      h4 font-medium + p text-sm muted, ChevronDown h-4 w-4 rotating
                180 while active; the active panel expands height 0 -> auto and
                opacity 0 -> 1 over .4s easeInOut, with pt-4

   The four steps are U Jabka's REAL lifecycle, not the source's registration
   demo, and both renderable stages are fully server-rendered; order-payment.js
   only flips the same classes and attributes, so these rules ARE the
   animation. State never rides on colour alone — the marker glyph changes and
   each title carries a visually-hidden textual state.
   ------------------------------------------------------------------------ */

/* w-screen max-w-md mx-auto — the source really does hold this card to 28rem,
   so the payment contract inside it stacks instead of sitting side by side. */
html.theme-soda .soda-plane.soda-paysteps {
    width: 100%;
    max-width: var(--t21-max-md);
    margin-inline: auto;
    padding: 0;
    border-radius: var(--t21-radius-xl);
    box-shadow: var(--t21-shadow-sm);
}

/* flex flex-col space-y-1.5 p-6 → one row, so only the padding is load-bearing */
html.theme-soda .soda-paysteps__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--t21-3);
    padding: var(--t21-6);
}

/* h3 font-semibold leading-none tracking-tight text-lg */
html.theme-soda .soda-paysteps__headtitle {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--t21-fs-lg);
    line-height: 1;
    letter-spacing: -.025em;
    font-weight: 600;
    color: var(--ink);
}

/*
 * text-xs font-semibold text-green-500 bg-green-500/10 px-2 py-1 rounded-full.
 *
 * green-500 (#22c55e) as TEXT on a light ground is 1.9:1 and unreadable, so
 * the pill uses the plane's semantic --success at the same 10% ground. The
 * shape, the size, the weight and the ratio are the source's.
 */
html.theme-soda .soda-paysteps__status {
    flex: none;
    padding: var(--t21-1) var(--t21-2);
    border-radius: var(--t21-radius-full);
    background: rgba(11, 92, 59, .1);
    color: var(--success);
    font-family: var(--font-display);
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
    font-weight: 600;
    white-space: nowrap;
}

/* p-6 pt-0 */
html.theme-soda .soda-paysteps__body {
    padding: 0 var(--t21-6) var(--t21-6);
}

/* <ol className="space-y-2"> */
html.theme-soda .soda-paysteps__list {
    list-style: none;
    margin: 0;
    padding: 0;
}

html.theme-soda .soda-paysteps__step {
    overflow: hidden;
}

html.theme-soda .soda-paysteps__step + .soda-paysteps__step {
    margin-top: var(--t21-2);
}

/* flex items-start gap-4 */
html.theme-soda .soda-paysteps__row {
    display: flex;
    align-items: flex-start;
    gap: var(--t21-4);
}

/* flex flex-col items-center */
html.theme-soda .soda-paysteps__gutter {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex: none;
}

/* relative flex h-8 w-8 items-center justify-center */
html.theme-soda .soda-paysteps__marker {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--t21-size-8);
    height: var(--t21-size-8);
}

/* Circle h-8 w-8 text-muted-foreground, text-primary while active; the wrapper
   animates scale 1 -> 1.1 over .2s. Framer turns a transition carrying only a
   `duration` into a tween, whose default ease is easeOut -- so the scale takes
   the ease-out token, not the in-out one. The Circle's COLOUR carries no
   transition utility in the source, so it snaps; only transform animates. */
html.theme-soda .soda-paysteps__circle {
    display: block;
    width: var(--t21-size-8);
    height: var(--t21-size-8);
    color: var(--ink-2);
    transform: scale(1);
    transition: transform var(--t21-dur-fast) var(--t21-ease-out);
}

html.theme-soda .soda-paysteps__step.is-active .soda-paysteps__circle {
    color: var(--accent);
    transform: scale(1.1);
}

/* absolute inset-0 flex items-center justify-center text-sm font-semibold
   text-muted-foreground; active adds text-primary-foreground bg-primary
   rounded-full h-6 w-6 m-1 — with inset-0 still applied, .25rem of margin plus
   a 1.5rem box is exactly the 2rem marker, so it centres precisely. */
html.theme-soda .soda-paysteps__num {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-display);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    font-weight: 600;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
}

html.theme-soda .soda-paysteps__step.is-active .soda-paysteps__num {
    width: var(--t21-size-6);
    height: var(--t21-size-6);
    margin: var(--t21-1);
    border-radius: var(--t21-radius-full);
    background: var(--accent);
    color: var(--accent-ink);
}

/* CheckCircle2 h-8 w-8 text-green-500, entering on a spring
   (stiffness 400, damping 20) from scale 0. */
html.theme-soda .soda-paysteps__check {
    display: none;
    width: var(--t21-size-8);
    height: var(--t21-size-8);
    color: var(--success);
}

html.theme-soda .soda-paysteps__step.is-done .soda-paysteps__circle,
html.theme-soda .soda-paysteps__step.is-done .soda-paysteps__num {
    display: none;
}

html.theme-soda .soda-paysteps__step.is-done .soda-paysteps__check {
    display: block;
    animation: soda-t21-pop var(--t21-dur-300) var(--t21-ease-spring) both;
}

@keyframes soda-t21-pop {
    from {
        opacity: 0;
        transform: scale(0);
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}

/* mt-2 h-8 w-px bg-border */
html.theme-soda .soda-paysteps__rail {
    display: block;
    width: 1px;
    height: var(--t21-size-8);
    margin-top: var(--t21-2);
    background: var(--line);
}

/* flex-1 */
html.theme-soda .soda-paysteps__main {
    flex: 1;
    min-width: 0;
}

/* flex items-center justify-between */
html.theme-soda .soda-paysteps__head-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--t21-2);
}

/* h4 font-medium. The source colours only the COMPLETED title
   (text-muted-foreground); active and upcoming both keep the card foreground,
   so no rule invents a third state here. */
html.theme-soda .soda-paysteps__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--t21-fs-base);
    line-height: var(--t21-lh-base);
    font-weight: 500;
    letter-spacing: normal;
    color: var(--ink);
}

html.theme-soda .soda-paysteps__step.is-done .soda-paysteps__title {
    color: var(--ink-2);
}

/* p text-sm text-muted-foreground */
html.theme-soda .soda-paysteps__desc {
    margin: 0;
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
}

/* ChevronDown h-4 w-4 text-muted-foreground transition-transform,
   rotate-180 while active. Tailwind's transition-transform is 150ms. */
html.theme-soda .soda-paysteps__chevron {
    flex: none;
    color: var(--ink-2);
    transform: rotate(0deg);
    transition: transform .15s var(--t21-ease-in-out);
}

html.theme-soda .soda-paysteps__step.is-active .soda-paysteps__chevron {
    transform: rotate(180deg);
}

/* Screen-reader-only textual state: "hotovo" / "probiha" / "ceka". */
html.theme-soda .soda-paysteps__srstate {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/*
 * The active step's content: height 0 -> auto with opacity 0 -> 1 over .4s
 * easeInOut. height:auto is not animatable, so the collapse is the
 * grid-template-rows 0fr -> 1fr technique, exactly as the brief requires.
 *
 * The `hidden` ATTRIBUTE stays the single state carrier the server renders and
 * order-payment.js flips, so a flipped page and a reloaded page are byte-equal
 * in state. Its UA `display:none` is overridden here so the box can animate;
 * `visibility: hidden` then keeps a collapsed panel out of the accessibility
 * tree and out of the tab order, which display:none was doing before.
 */
html.theme-soda .soda-paysteps__panel {
    display: grid;
    grid-template-rows: 1fr;
    opacity: 1;
    transition: grid-template-rows var(--t21-dur-collapse) var(--t21-ease-in-out),
                opacity var(--t21-dur-collapse) var(--t21-ease-in-out);
}

html.theme-soda .soda-paysteps__panel[hidden] {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    visibility: hidden;
    transition: grid-template-rows var(--t21-dur-collapse) var(--t21-ease-in-out),
                opacity var(--t21-dur-collapse) var(--t21-ease-in-out),
                visibility 0s linear var(--t21-dur-collapse);
}

html.theme-soda .soda-paysteps__panel > * {
    min-height: 0;
    overflow: hidden;
}

/* <div className="pt-4">{step.content}</div> */
html.theme-soda .soda-paysteps__panel-inner {
    padding-top: var(--t21-4);
}

/* The panel's own running text only — a notice or a note inside it keeps the
   component rules it came with. */
html.theme-soda .soda-paysteps__panel-inner > p:not([class]) {
    margin: 0 0 var(--t21-3);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
}

/* The payment contract keeps every canonical fact it always carried; inside a
   28rem card it simply stacks. */
html.theme-soda .soda-paysteps__contract {
    display: flex;
    flex-direction: column;
    gap: var(--t21-4);
}

html.theme-soda .soda-paysteps__facts {
    grid-template-columns: minmax(0, 1fr);
    margin: 0;
}

/* ------------------------------------------------------------------------
   21ST EXACT TRANSPLANT — order submitted (source: order-submited.md).

   Rendered ONLY for an order the server reports as PAID, and reached either by
   loading a paid page or by the polling module flipping the same attribute the
   server would have rendered. Nothing here is ever minted client-side.

     card     w-full max-w-sm rounded-xl border bg-card text-card-foreground
              shadow-lg p-6 sm:p-8
     inner    flex flex-col items-center space-y-6 text-center
     icon     CheckCircle2 h-12 w-12 text-green-500
     title    h2 text-2xl font-semibold
     details  w-full space-y-4 pt-4; each row flex items-center justify-between
              border-b pb-4 text-sm text-muted-foreground; the LAST row is
              border-none pb-0; the total row is font-bold text-card-foreground
              with a text-lg value
     cta      w-full pt-4, Button w-full h-12
     motion   container opacity 0 -> 1 + scale .95 -> 1 over .4s easeInOut with
              staggerChildren .1s; each child opacity 0 -> 1 and y 20 -> 0 on a
              spring
   ------------------------------------------------------------------------ */

html.theme-soda .soda-plane.soda-t21-done {
    width: 100%;
    max-width: var(--t21-max-sm);
    margin-inline: auto;
    padding: var(--t21-6);
    border-radius: var(--t21-radius-xl);
    box-shadow: var(--t21-shadow-lg);
}

@media (min-width: 640px) {
    html.theme-soda .soda-plane.soda-t21-done {
        padding: var(--t21-8);
    }
}

html.theme-soda .soda-t21-done__inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

/* space-y-6 */
html.theme-soda .soda-t21-done__inner > * + * {
    margin-top: var(--t21-6);
}

html.theme-soda .soda-t21-done__icon {
    display: block;
    color: var(--success);
}

/* h2 text-2xl font-semibold */
html.theme-soda .soda-t21-done__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--t21-fs-2xl);
    line-height: var(--t21-lh-2xl);
    font-weight: 600;
    letter-spacing: normal;
    color: var(--ink);
}

/* w-full space-y-4 pt-4 */
html.theme-soda .soda-t21-done__details {
    width: 100%;
    margin: 0;
    padding-top: var(--t21-4);
}

html.theme-soda .soda-t21-done__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--t21-4);
    padding-bottom: var(--t21-4);
    border-bottom: var(--t21-border) solid var(--line);
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
    text-align: left;
}

html.theme-soda .soda-t21-done__row + .soda-t21-done__row {
    margin-top: var(--t21-4);
}

html.theme-soda .soda-t21-done__row dt {
    margin: 0;
}

html.theme-soda .soda-t21-done__row dd {
    margin: 0;
    text-align: right;
    overflow-wrap: anywhere;
    font-variant-numeric: tabular-nums;
}

/* index === details.length - 1 → border-none pb-0 */
html.theme-soda .soda-t21-done__row--last {
    padding-bottom: 0;
    border-bottom: 0;
}

/* item.isBold → font-bold text-card-foreground, value text-lg */
html.theme-soda .soda-t21-done__row--strong {
    font-weight: 700;
    color: var(--ink);
}

html.theme-soda .soda-t21-done__row--strong dd {
    font-family: var(--font-display);
    font-size: var(--t21-fs-lg);
    line-height: var(--t21-lh-lg);
}

/* w-full pt-4 */
html.theme-soda .soda-t21-done__cta {
    width: 100%;
    padding-top: var(--t21-4);
}

/*
 * The entry animation. Framer's container variant is opacity 0 -> 1 and
 * scale .95 -> 1 over .4s easeInOut; its children are opacity 0 -> 1 and
 * y 20px -> 0 on the default spring (stiffness 100, damping 10, mass 1),
 * staggered by .1s.
 *
 * The spring shape is the shared --t21-ease-spring token. Its DURATION is not
 * in tokens.css — a spring has none — so .6s is hardcoded here as the settling
 * time of that spring, and is reported as a deviation.
 */
html.theme-soda .soda-t21-done:not([hidden]) {
    animation: soda-t21-done-in var(--t21-dur-clip) var(--t21-ease-in-out) both;
}

html.theme-soda .soda-t21-done:not([hidden]) .soda-t21-done__inner > * {
    animation: soda-t21-done-item .6s var(--t21-ease-spring) both;
}

html.theme-soda .soda-t21-done:not([hidden]) .soda-t21-done__inner > *:nth-child(1) {
    animation-delay: 0s;
}

html.theme-soda .soda-t21-done:not([hidden]) .soda-t21-done__inner > *:nth-child(2) {
    animation-delay: var(--t21-stagger);
}

html.theme-soda .soda-t21-done:not([hidden]) .soda-t21-done__inner > *:nth-child(3) {
    animation-delay: calc(var(--t21-stagger) * 2);
}

html.theme-soda .soda-t21-done:not([hidden]) .soda-t21-done__inner > *:nth-child(4) {
    animation-delay: calc(var(--t21-stagger) * 3);
}

/* The compact card gained a supporting line under the title, so the CTA is the
   fifth child. Without this it fell off the end of the ladder and animated at
   delay 0 -- arriving BEFORE the rows above it, which is the one order the
   source's staggerChildren exists to prevent. */
html.theme-soda .soda-t21-done:not([hidden]) .soda-t21-done__inner > *:nth-child(5) {
    animation-delay: calc(var(--t21-stagger) * 4);
}

@keyframes soda-t21-done-in {
    from {
        opacity: 0;
        transform: scale(.95);
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}

@keyframes soda-t21-done-item {
    from {
        opacity: 0;
        transform: translateY(20px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ------------------------------------------------------------------------
   THE PAYMENT MODAL — the waiting experience for a placed bank-transfer
   order (sources: payment-popup.md for the waiting card, order-submited.md
   for the paid card and for every number in the entry animation).

   THE WHOLE POINT OF THIS BLOCK IS THAT IT IS A PRESENTATION, NOT A STATE.

   The confirmation branch server-renders BOTH sections — waiting visible,
   [data-payment-modal-done] hidden — so the flip the polling module performs
   shows exactly what the server would have rendered on a reload. Nothing about
   the payment is minted here.

     <div class="soda-paymodal" data-payment-modal
          data-payment-status-url data-payment-state data-payment-poll-seconds>
       <div class="soda-paymodal__backdrop" data-payment-modal-backdrop></div>
       <div class="soda-paymodal__dialog" role="dialog" aria-modal="true"
            aria-labelledby="paymodal-title" data-payment-modal-dialog>
         <section data-payment-modal-waiting>…</section>
         <section data-payment-modal-done hidden>…</section>
       </div>
     </div>

   NO-JS IS THE DEFAULT, NOT A FALLBACK. Everything below the "with JavaScript"
   rule is plain block flow: no fixed positioning, no backdrop, no capped
   height, no hidden overflow, no animation. EVERY rule that turns this into an
   overlay is keyed on html.js-soda, which ui.js sets synchronously. With
   scripting off the same markup is an ordinary card on the confirmation page
   and the visitor reads the account, the symbol, the QR and the deadline
   exactly as before.

   CLOSING IS A DEMOTION, NOT A HIDE, and the difference matters because the
   payment facts LIVE IN THIS BLOCK. An earlier revision closed by hiding the
   root, which took the account number, the variable symbol and the QR away
   from anyone who pressed Escape, with no way back. Dismissing now drops the
   promotion class and payment-modal.js returns the node to where the server
   put it, so it becomes exactly the in-flow instruction sheet a visitor
   without JavaScript reads. Nothing is lost by closing. Closing does not stop
   the polling either — that is the module's business, and no rule here can
   affect it.
   ------------------------------------------------------------------------ */

/* Without JavaScript: an ordinary in-page block, separated from the
   confirmation above it by the card gap the rest of the page uses. */
html.theme-soda .soda-paymodal {
    display: block;
    margin-top: var(--t21-6);
}

/* `hidden` still wins over the overlay's flex, for the one case that sets it:
   a server render with nothing to show. It is NOT the close mechanism, and
   data-payment-modal-open='false' deliberately does not appear here — that
   attribute marks a DEMOTED block, which must stay readable. */
html.theme-soda .soda-paymodal[hidden] {
    display: none !important;
}

/* The backdrop is pure decoration for the overlay presentation. With no
   JavaScript there is no overlay, so there is nothing for it to dim. */
html.theme-soda .soda-paymodal__backdrop {
    display: none;
}

/*
 * THE DIALOG IS THE CARD, and it carries .soda-plane in the markup — so the
 * white surface, the hairline and the whole light token scope are the system's
 * own, and only the radius and the elevation are stated here. rounded-xl is
 * what every other transplanted card on this surface uses.
 *
 * The elevation is stated TWICE on purpose. In the page — no JavaScript — this
 * is an ordinary card sitting among the confirmation's other cards, so it takes
 * their shadow-sm. Lifted into an overlay it takes shadow-xl, one step above
 * order-submited.md's shadow-lg, because it is then floating over a dimmed
 * page rather than resting in one. No source contains a modal, so that step up
 * is the one judgement call in this block and is reported as such.
 *
 * It gets NO padding of its own. The stepper card inside it already carries
 * payment-popup.md's p-6 header and p-6 body, and adding a second p-6 around
 * that would double the gutter. What the section rules below do instead is put
 * every non-card child on the SAME 24px gutter the stepper defines, so the
 * whole dialog reads as one card with one margin.
 */
html.theme-soda .soda-paymodal__dialog {
    display: block;
    width: 100%;
    border-radius: var(--t21-radius-xl);
    box-shadow: var(--t21-shadow-sm);
}

/* Only ever one card edge inside the overlay: a plane held by a plane keeps its
   surface and its token scope but drops the frame that would draw a box inside
   a box. */
html.theme-soda .soda-paymodal__dialog.soda-plane .soda-plane {
    border-color: transparent;
    box-shadow: none;
}

/* Both sections stack their blocks; `:not(.soda-plane)` because the paid card
   IS its section — order-submited.md's card is the whole thing, and it brings
   its own centred, max-w-sm, padded layout. Only one section is ever visible;
   the other keeps its `hidden` attribute, server-rendered. */
html.theme-soda .soda-paymodal [data-payment-modal-waiting]:not(.soda-plane),
html.theme-soda .soda-paymodal [data-payment-modal-done]:not(.soda-plane) {
    display: flex;
    flex-direction: column;
    gap: var(--t21-4);
    padding-bottom: var(--t21-6);
}

/*
 * The stepper card provides the gutter for itself. Everything else in the
 * section — the activity line, the deadline notice, the fallback footer — is
 * inset to the same 24px so nothing runs to the dialog's edge. margin, not
 * padding, so a .soda-notice keeps its own box intact.
 */
html.theme-soda .soda-paymodal [data-payment-modal-waiting]:not(.soda-plane) > *:not(.soda-plane) {
    margin-inline: var(--t21-6);
}

html.theme-soda .soda-paymodal [data-payment-modal-waiting][hidden],
html.theme-soda .soda-paymodal [data-payment-modal-done][hidden] {
    display: none;
}

/*
 * The close control. It is a JS-only affordance — without scripting there is no
 * overlay to dismiss and the markup keeps it [hidden] — so it is laid out as an
 * ordinary right-aligned block above the stepper rather than floated over it.
 * Absolute positioning would land it exactly on payment-popup.md's status pill,
 * which sits at the top right of the stepper header.
 */
html.theme-soda .soda-paymodal__close {
    display: flex;
    margin: var(--t21-4) var(--t21-6) 0 auto;
}

html.theme-soda .soda-paymodal__close[hidden] {
    display: none;
}

/* The fallback footer: the standalone payment page's link and the "closing this
   page cancels nothing" note, stacked at the source's gap-3. */
html.theme-soda .soda-paymodal__foot {
    display: flex;
    flex-direction: column;
    gap: var(--t21-3);
}

html.theme-soda .soda-paymodal__foot > p {
    margin: 0;
}

/* ------------------------------------------------ with JavaScript: overlay */

/*
 * Fixed, scrolling overlay. `margin: auto` on the dialog rather than
 * `align-items: center` on the overlay: a centred flex item whose box is
 * taller than the scroller has its TOP clipped and unreachable, and the top of
 * this dialog carries the amount and the account number.
 */
html.theme-soda .soda-paymodal.is-promoted {
    position: fixed;
    inset: 0;
    z-index: 1100;
    display: flex;
    margin-top: 0;
    padding: var(--t21-4);
    overflow-y: auto;
    overscroll-behavior: contain;
}

html.theme-soda .soda-paymodal.is-promoted .soda-paymodal__backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .6);
}

/*
 * max-w-md is payment-popup.md's own shell width, and it also clears
 * order-submited.md's max-w-sm card, so one number holds both states and the
 * dialog does not resize when the payment lands.
 *
 * The height cap is the viewport less the overlay's own padding, so the dialog
 * scrolls inside itself and the overlay never has to. dvh first where it is
 * supported, because a mobile URL bar must not cut the deadline off.
 */
html.theme-soda .soda-paymodal.is-promoted .soda-paymodal__dialog {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: var(--t21-max-md);
    margin: auto;
    box-shadow: var(--t21-shadow-xl);
    max-height: calc(100vh - (var(--t21-4) * 2));
    max-height: calc(100dvh - (var(--t21-4) * 2));
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* An optional lock for the module: the page beneath carries the same facts, so
   a locked background is a nicety, not a correctness requirement. */
html.theme-soda.js-soda body.soda-paymodal-locked {
    overflow: hidden;
}

/*
 * ENTRY — order-submited.md's own numbers: the container fades in from
 * opacity 0 and scale .95 over .4s on the SYMMETRIC easeInOut, and its
 * children follow on the .1s stagger. The backdrop rides the same .4s so the
 * dim and the card arrive together.
 *
 * The done card brings its own entry animation from the same source; only the
 * waiting section is staggered here, so a payment that lands never plays two
 * entry animations over one card.
 */
html.theme-soda .soda-paymodal.is-promoted .soda-paymodal__backdrop {
    animation: soda-paymodal-fade var(--t21-dur-clip) var(--t21-ease-in-out) both;
}

html.theme-soda .soda-paymodal.is-promoted .soda-paymodal__dialog {
    animation: soda-paymodal-in var(--t21-dur-clip) var(--t21-ease-in-out) both;
}

html.theme-soda .soda-paymodal.is-promoted [data-payment-modal-waiting] > * {
    animation: soda-paymodal-item .6s var(--t21-ease-spring) both;
}

html.theme-soda .soda-paymodal.is-promoted [data-payment-modal-waiting] > *:nth-child(1) {
    animation-delay: 0s;
}

html.theme-soda .soda-paymodal.is-promoted [data-payment-modal-waiting] > *:nth-child(2) {
    animation-delay: var(--t21-stagger);
}

html.theme-soda .soda-paymodal.is-promoted [data-payment-modal-waiting] > *:nth-child(3) {
    animation-delay: calc(var(--t21-stagger) * 2);
}

html.theme-soda .soda-paymodal.is-promoted [data-payment-modal-waiting] > *:nth-child(4) {
    animation-delay: calc(var(--t21-stagger) * 3);
}

/* The source's staggerChildren is unbounded; the ladder runs to six because
   that is more blocks than the waiting section can plausibly carry (stepper,
   facts, QR, deadline, and a link). A seventh child would simply arrive with
   the first — visible, on time, and unstaggered. */
html.theme-soda .soda-paymodal.is-promoted [data-payment-modal-waiting] > *:nth-child(5) {
    animation-delay: calc(var(--t21-stagger) * 4);
}

html.theme-soda .soda-paymodal.is-promoted [data-payment-modal-waiting] > *:nth-child(6) {
    animation-delay: calc(var(--t21-stagger) * 5);
}

@keyframes soda-paymodal-fade {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

@keyframes soda-paymodal-in {
    from {
        opacity: 0;
        transform: scale(.95);
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}

@keyframes soda-paymodal-item {
    from {
        opacity: 0;
        transform: translateY(20px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* --------------------------------------------------- waiting activity ring */

/*
 * AN INDETERMINATE RING, AND DELIBERATELY NOT A COUNTDOWN.
 *
 * payment-popup.md's demo runs a 60-second commit timer. A bank transfer
 * clears in hours, so a ticking 60s dial here would state something false
 * about the visitor's money. The real deadline is the reservation deadline and
 * it is already on the page as text. This ring says one honest thing — "we are
 * still watching for your payment" — and says nothing about when.
 *
 * It carries no state of its own: [data-payment-state] on the modal root is
 * the state, the ring is only visible while that state is a waiting one.
 * The spin is scoped to [data-payment-poll="active"], which the module owns.
 *
 * The 1.2s rotation is a legibility choice, not a source number, and is
 * reported as a deviation. The poll interval is NOT used as the rotation
 * period: at the server's poll_after_seconds a ring would appear stalled, and
 * a stalled ring next to "waiting for payment" reads as a broken page.
 */
html.theme-soda .soda-payring,
html.theme-soda [data-payment-ring] {
    display: inline-block;
    flex: none;
    width: var(--t21-size-5);
    height: var(--t21-size-5);
    border: var(--t21-border-2) solid var(--line-hi);
    border-top-color: var(--accent);
    border-radius: var(--t21-radius-full);
}

/* IT SPINS ONLY WHILE SOMETHING REALLY IS POLLING.
   payment-modal.js writes data-payment-poll="active" for exactly as long as it
   is asking our own status endpoint, and "stopped" the instant the watcher
   stops. Animating unconditionally would keep claiming live monitoring on a
   page where nothing is watching any more — a spinner is a factual claim, and
   this one is now tied to the only thing that can make it true. */
html.theme-soda [data-payment-poll="active"] .soda-payring,
html.theme-soda [data-payment-poll="active"] .soda-paywait__ring,
html.theme-soda [data-payment-poll="active"] [data-payment-ring] {
    animation: soda-payring-spin 1.2s linear infinite;
    border-color: rgba(var(--accent-rgb), .3);
    border-top-color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
    html.theme-soda [data-payment-poll="active"] .soda-payring,
    html.theme-soda [data-payment-poll="active"] .soda-paywait__ring,
    html.theme-soda [data-payment-poll="active"] [data-payment-ring] {
        animation: none;
    }
}

/* THE WAITING INDICATOR LEADS THE ACTIVE STEP.

   It used to be a 20px ring on the same baseline as a sentence at the very
   bottom of the dialog, which is where a spinner goes to be ignored. The
   customer opens this dialog to find out whether the shop is still waiting, so
   that answer is the first thing in the panel and is sized to be read as the
   subject rather than as trim.

   It stays INDETERMINATE. payment-popup.md's own demo shows a sixty-second
   commit countdown; a bank transfer clears in hours, so a ticking number here
   would invent a deadline the shop does not control. The real deadline is the
   reservation deadline, stated once as text below. */
/* MODAL-ONLY DENSITY. These override the standalone payment page's roomier
   treatment, and only inside the dialog: that page has a whole viewport to
   breathe in, this one is a 28rem card that has to show the QR without a
   scroll. The saving comes from LAYOUT, not from shrinking type -- the facts
   pair up two to a row instead of stacking four deep, and the QR is capped at
   a size that is still comfortably scannable. */
html.theme-soda .soda-paymodal .soda-paysteps__facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--t21-3);
}

/* Below the source's sm breakpoint the dialog is ~358px wide, and two columns
   split an account number across a line ("7784277412/201" / "0"). A payment
   fact that has to be transcribed digit for digit must never wrap mid-value,
   so the pairs stack again -- the desktop saving is not worth an unreadable
   account number on the device most people pay from. */
@media (max-width: 639.98px) {
    html.theme-soda .soda-paymodal .soda-paysteps__facts {
        grid-template-columns: minmax(0, 1fr);
    }
}

html.theme-soda .soda-paymodal .soda-payment__qr svg {
    /* Plenty for a phone camera at arm's length; the module grid is what
       matters, not the pixel size. */
    width: 100%;
    max-width: 13rem;
    height: auto;
}

html.theme-soda .soda-paymodal .soda-payment__qr {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--t21-2);
}

html.theme-soda .soda-paymodal .soda-payment__qr-note {
    margin: 0;
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
    color: var(--ink-3);
    text-align: center;
}

html.theme-soda .soda-paywait {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--t21-2);
    padding: 0 0 var(--t21-3);
    text-align: center;
}

html.theme-soda .soda-paywait__ring {
    display: block;
    /* 3.5rem: dominant next to text-sm body copy, and still smaller than the
       QR beneath it, which is the thing the customer actually acts on. */
    width: 3.5rem;
    height: 3.5rem;
    border: 3px solid var(--line-hi);
    border-top-color: var(--accent);
    border-radius: var(--t21-radius-full);
}

html.theme-soda .soda-paywait__text {
    max-width: 30ch;
    margin: 0;
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
}

/* One line, and quiet: it is a constraint, not the subject. */
/* The one supporting line under the success title. */
html.theme-soda .soda-t21-done__lead {
    margin: 0;
    font-size: var(--t21-fs-sm);
    line-height: var(--t21-lh-sm);
    color: var(--ink-2);
}

html.theme-soda .soda-paymodal__deadline {
    margin: 0;
    font-size: var(--t21-fs-xs);
    line-height: var(--t21-lh-xs);
    color: var(--ink-3);
    text-align: center;
}

@keyframes soda-payring-spin {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}

/* ------------------------------------------------------------ reduced motion

   THERE IS DELIBERATELY NO prefers-reduced-motion BLOCK HERE.

   base.css already states the whole contract, globally and with !important:

       @media (prefers-reduced-motion: reduce) {
           html.theme-soda *, ::before, ::after {
               animation-duration: .001ms !important;
               animation-iteration-count: 1 !important;
               transition-duration: .001ms !important;
               scroll-behavior: auto !important;
           }
       }

   A local block here could only either repeat that or lose to it, and a rule
   that loses to an !important override while LOOKING like it decides something
   is worse than no rule at all. Verified by forcing the query on and measuring:

     - the modal dialog, its backdrop and the staggered waiting children all
       carry animation-fill-mode: both, so a 1 us run lands them on their final
       frame — opacity 1, scale 1, translateY 0. Measured opacity: 1;
     - the paid card and its four staggered children do the same;
     - the stepper panel's collapse transition is zeroed, so a flipped panel
       simply appears open;
     - the waiting ring runs its rotation exactly once, for 1 us, and then
       returns to its base style: a STATIC ring. That is the honest reduced-
       motion form of an indeterminate indicator — still visible, still the
       only thing on the card that says "we are watching", and not moving.

   Nothing here needs a countdown, a fallback pulse or a second animation to
   survive that, which is why almost nothing is written.

   THE ONE THING base.css DOES NOT COVER IS THE STAGGER DELAY, and it is the
   reason the block below exists at all. Its override zeroes animation-DURATION
   and iteration-count; animation-DELAY is untouched. A staggered child is
   therefore still held at its `from` frame — opacity 0, translated 20px — for
   the whole of its delay, and only then snaps in. Measured with the query
   forced on: the paid card's fourth child, the "Zobrazit objednavku" button,
   sat at opacity 0 for its full .3s. A CTA that is invisible for a third of a
   second after the card announces the payment is exactly the motion a visitor
   asked not to be shown, so the delays are zeroed too. Nothing else is
   repeated here. */

/* The `:nth-child(n)` is load-bearing, not decoration: the per-child delays it
   cancels are themselves written with `:nth-child(N)`, and a media query adds
   no specificity of its own. Matched weight plus source order is what makes
   this win. */
@media (prefers-reduced-motion: reduce) {
    html.theme-soda .soda-t21-done:not([hidden]) .soda-t21-done__inner > *:nth-child(n),
    html.theme-soda .soda-paymodal.is-promoted [data-payment-modal-waiting] > *:nth-child(n) {
        animation-delay: 0s;
    }
}

/* --------------------------------------------------------- narrow screens */

@media (max-width: 980px) {
    html.theme-soda .soda-checkout__success,
    html.theme-soda .soda-payment__result {
        padding: 1.5rem;
    }
}

/* NO RESPONSIVE PADDING OR HEADER STACKING HERE, DELIBERATELY.
   An earlier revision shrank every card's p-6 to 1rem below 640px and stacked
   the header column-wise. Neither source contains such a rule: checkout.md's
   header is a flat `flex items-center justify-between` at every width, its
   padding utilities carry no responsive variant, and its only breakpoints are
   sm:640 (rail gap + labels), md:768 (field grids) and lg:1024 (body grid,
   payment tiles). payment-popup.md is flat p-6 inside a max-w-md shell.
   Inventing a phone layout is the reinterpretation this transplant exists to
   avoid, so the source's geometry is carried to every width. */
