/* ============================================
   HOMEPAGE FEATURE 5 — Store
   Modular CSS for the dynamically-injected merchandise section.
   Mobile-first responsive grid with full-screen store modal,
   product detail modal, cart drawer, order form, and success state.

   ARCHITECTURE:
   - Uses design tokens (CSS custom properties) from h0-homepage.css
   - Mobile-first with breakpoints at 640px, 768px, 1024px, 1440px
   - Store modal: z-index 190 (full-screen browse overlay)
   - Modals use z-index 200 (detail, cart, order) and 300 (success)
   - Skeleton loaders for perceived performance
   - prefers-reduced-motion respected throughout
   - Touch-device optimizations for mobile
   - AWS Console Dark Mode color system

   SECTIONS:
   1. Section Container & Grid
   2. Product Cards
   3. Quantity Controls
   4. Browse Actions
   5. Cart Summary Bar (sticky)
   6. Store Modal — Full-Screen Browse Overlay
   7. Product Detail Modal
   8. Cart Modal & Table
   9. Order Form Modal
   10. Success Overlay
   11. Empty State & Skeleton
   12. Responsive Overrides
   13. Touch Device Overrides
   14. Reduced Motion
   ============================================ */

/* ----------------------------------------
   Section Container
   Matches page-wide rhythm: 4xl padding
   + container-width divider at the top.
   ---------------------------------------- */
.section-merchandise {
    position: relative;
    padding: var(--spacing-4xl) 0;
    background: var(--color-bg-primary);
}

.section-merchandise::before {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: calc(100% - 2 * var(--container-padding));
    max-width: var(--container-max);
    height: 1px;
    background: var(--color-border);
}

/* ----------------------------------------
   Product Grid — Mobile-first (2 cols)
   ---------------------------------------- */
.merch-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--spacing-md);
}

.merch-grid:empty {
    display: none;
}

@media (min-width: 640px) {
    .merch-grid {
        grid-template-columns: repeat(3, 1fr);
        gap: var(--spacing-lg);
    }
}

@media (min-width: 1024px) {
    .merch-grid {
        grid-template-columns: repeat(4, 1fr);
        gap: var(--spacing-xl);
    }
}

/* ----------------------------------------
   Product Card
   ---------------------------------------- */
.merch-card {
    position: relative;
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--color-bg-surface);
    box-shadow: var(--shadow-sm);
    cursor: pointer;
    transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}

.merch-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
}

.merch-card:active {
    transform: translateY(-1px);
}

/* ----------------------------------------
   Shared Image Frame
   Wraps every storefront product image (cards, carousel
   slides, cart thumbnails) in a consistent loading shell:
     - Centered CSS spinner shown while the image downloads
     - Fade-in transition on load (`is-loaded` class)
     - Graceful placeholder icon when no URL is available
       OR the image fails to load (`is-error` class)
   All variants differ only in size/aspect via modifier
   classes below. Spinner + placeholder animate from pure
   CSS — no extra network or JS work per image.
   ---------------------------------------- */
.merch-img-frame {
    position: relative;
    overflow: hidden;
    background: var(--color-bg-alt);
    display: flex;
    align-items: center;
    justify-content: center;
}

.merch-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity var(--transition-base);
    z-index: 2;
}

.merch-img-frame.is-loaded:not(.is-error) .merch-img { opacity: 1; }
.merch-img-frame.is-error .merch-img { display: none; }

.merch-img-spinner {
    position: absolute;
    width: 28px;
    height: 28px;
    border: 2px solid rgba(255, 255, 255, 0.18);
    border-top-color: var(--color-gold);
    border-radius: 50%;
    animation: merch-img-spin 0.8s linear infinite;
    transition: opacity var(--transition-fast);
    z-index: 1;
    pointer-events: none;
}

.merch-img-frame.is-loaded .merch-img-spinner,
.merch-img-frame.is-error .merch-img-spinner,
.merch-img-frame--empty .merch-img-spinner { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
    .merch-img-spinner { animation: none; }
}

.merch-img-placeholder {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-text-tertiary);
    font-size: 2rem;
    opacity: 0;
    transition: opacity var(--transition-fast);
    pointer-events: none;
    z-index: 1;
}

.merch-img-frame--empty .merch-img-placeholder,
.merch-img-frame.is-error .merch-img-placeholder { opacity: 0.55; }

@keyframes merch-img-spin {
    to { transform: rotate(360deg); }
}

/* Card image variant — 1:1 tile inside a product card. */
.merch-img-frame--card {
    width: 100%;
    aspect-ratio: 1 / 1;
}

/* Detail modal slide variant — fills carousel slide width. */
.merch-img-frame--slide {
    width: 100%;
    aspect-ratio: 1 / 1;
}

/* Cart row thumbnail variant — fixed 44×44 tile. */
.merch-img-frame--cart {
    width: 44px;
    height: 44px;
    border-radius: var(--radius-xs);
    flex-shrink: 0;
}
.merch-img-frame--cart .merch-img-spinner {
    width: 18px;
    height: 18px;
    border-width: 2px;
}
.merch-img-frame--cart .merch-img-placeholder { font-size: 1rem; }

.merch-card-body {
    padding: var(--spacing-sm) var(--spacing-md) var(--spacing-md);
}

.merch-card-name {
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    font-weight: var(--fw-semibold);
    color: var(--color-text-primary);
    margin: 0 0 var(--spacing-xs);
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.merch-card-price {
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    font-weight: var(--fw-medium);
    color: var(--color-gold);
    margin: 0 0 var(--spacing-sm);
}

/* Inline quick-add controls on the card */
.merch-card-actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
}

.merch-card-add-btn {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-xs);
    padding: var(--spacing-xs) var(--spacing-sm);
    border: 1px solid var(--color-border-gold);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-gold);
    font-family: var(--font-body);
    font-size: var(--font-size-xs);
    font-weight: var(--fw-medium);
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast);
}

.merch-card-add-btn:hover {
    background: var(--color-gold);
    color: var(--color-bg-dark);
}

/* Visual feedback states (set briefly by JS after addToCart).
   Uses an Amazon-inspired confirm blue rather than the system
   `--color-success` green so the cue reads as “action
   acknowledged” (the universal e-commerce affordance) and stays
   visually distinct from form-success states elsewhere on the
   site. The color is component-scoped via `--merch-add-confirm-*`
   so re-skinning is a single-place change. */
.merch-card-add-btn--added,
.merch-card-add-btn--added:hover {
    --merch-add-confirm-bg: #3b82f6;
    --merch-add-confirm-fg: #ffffff;

    background: var(--merch-add-confirm-bg);
    border-color: var(--merch-add-confirm-bg);
    color: var(--merch-add-confirm-fg);
    pointer-events: none;
}

.merch-card-add-btn--max,
.merch-card-add-btn--max:hover {
    background: var(--color-bg-elevated);
    border-color: var(--color-border);
    color: var(--color-text-tertiary);
    pointer-events: none;
}

/* ----------------------------------------
   Quantity Controls (shared)
   ---------------------------------------- */
.merch-qty-control {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    margin: var(--spacing-md) 0;
}

.merch-qty-btn {
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg-surface);
    color: var(--color-text-primary);
    font-size: var(--font-size-sm);
    cursor: pointer;
    transition: background var(--transition-fast), border-color var(--transition-fast);
}

.merch-qty-btn:hover {
    background: var(--color-bg-alt);
    border-color: var(--color-border-hover);
}

.merch-qty-value {
    min-width: 2rem;
    text-align: center;
    font-family: var(--font-body);
    font-size: var(--font-size-base);
    font-weight: var(--fw-semibold);
    color: var(--color-text-primary);
}

/* ----------------------------------------
   Browse Actions — "Browse All Products" Button
   Appears below the featured grid when products exist.
   Opens full-screen store modal.
   ---------------------------------------- */
.merch-actions {
    text-align: center;
    margin-top: var(--spacing-xl);
}

.merch-actions[hidden] {
    display: none;
}

.merch-shop-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-md) var(--spacing-2xl);
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    font-weight: var(--fw-semibold);
    color: var(--color-text-primary);
    background: transparent;
    border: 2px solid var(--color-gold);
    border-radius: var(--radius-full);
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast), transform var(--transition-fast);
}

.merch-shop-btn:hover {
    background: var(--color-gold);
    color: var(--color-bg-dark);
    transform: scale(1.03);
}

.merch-shop-btn:active {
    transform: scale(0.97);
}

/* ----------------------------------------
   Cart Summary Bar (fixed bottom)

   Chrome-less design: a single centered "View Cart" pill that floats
   above the page. There is no background, blur, or border on the bar
   itself — only the pill is visible — so the bar can never visually
   overlap or clip sibling fixed elements (e.g. the liquid-glass nav
   FAB pinned bottom-right). Pointer events are scoped to the pill
   only, so the rest of the bar's footprint stays click-through.
   ---------------------------------------- */
.merch-cart-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 150;
    padding: var(--spacing-sm) var(--spacing-md);
    /* Honor iOS home-indicator safe area */
    padding-bottom: calc(var(--spacing-sm) + env(safe-area-inset-bottom, 0px));
    /* Click-through gutter; pill re-enables pointer events below. */
    pointer-events: none;
    transform: translateY(100%);
    transition: transform var(--transition-base);
}

.merch-cart-bar:not([hidden]) {
    transform: translateY(0);
}

/* Reserve enough bottom padding for the floating pill so it never
   overlaps the Contact section's submit button. The token is sized
   for the pill itself; no need to track the bar at runtime now that
   the bar has no visible chrome. Tweak in one place. */
:root {
    --merch-cart-bar-clearance: 5rem;
}

body.merch-cart-bar-active {
    padding-bottom: var(--merch-cart-bar-clearance);
}

.merch-cart-bar-inner {
    max-width: var(--container-max);
    margin: 0 auto;
    display: flex;
    align-items: center;
    /* Center the lone View Cart pill horizontally. */
    justify-content: center;
}

.merch-cart-bar-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    padding: var(--spacing-xs) var(--spacing-lg);
    border: none;
    border-radius: var(--radius-full);
    background: var(--color-gold);
    color: var(--color-bg-dark);
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    /* Re-enable pointer events on the only interactive surface in the bar. */
    pointer-events: auto;
    /* Subtle elevation so the pill reads as floating chrome over content. */
    box-shadow: var(--shadow-md);
    transition: background var(--transition-fast), transform var(--transition-fast);
}

.merch-cart-bar-btn:hover {
    background: var(--color-gold-hover);
    transform: translateY(-2px);
}

/* Compact pill that shows the live item count inside the View Cart button.
   Uses the bar's dark surface tone so it reads against the gold pill. */
.merch-cart-bar-btn-badge {
    min-width: 1.25rem;
    padding: 0 var(--spacing-xs);
    border-radius: var(--radius-full);
    background: var(--color-bg-dark);
    color: var(--color-gold);
    font-size: var(--font-size-xs);
    font-weight: var(--fw-bold);
    text-align: center;
    line-height: 1.4;
}

/* ----------------------------------------
   Store Modal — Full-Screen Browse Overlay
   Opens via "Browse All Products" button.
   98% viewport coverage with scrollable product grid.
   z-index: 190 (below detail/cart at 200, success at 300).
   ---------------------------------------- */
.merch-store {
    position: fixed;
    inset: 0;
    z-index: 190;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base), visibility var(--transition-base);
}

.merch-store.active {
    opacity: 1;
    visibility: visible;
}

.merch-store-backdrop {
    position: absolute;
    inset: 0;
    background: var(--color-backdrop-light);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
}

.merch-store-content {
    position: relative;
    width: 98vw;
    height: 98vh;
    height: 98dvh;
    background: var(--color-bg-primary);
    border-radius: var(--radius-lg);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    box-shadow: var(--shadow-lg);
    transform: scale(0.96) translateY(12px);
    transition: transform var(--transition-base);
}

.merch-store.active .merch-store-content {
    transform: scale(1) translateY(0);
}

.merch-store-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--spacing-lg) var(--spacing-xl);
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}

.merch-store-title {
    font-family: var(--font-heading);
    font-size: var(--font-size-xl);
    font-weight: var(--fw-semibold);
    color: var(--color-text-primary);
    margin: 0;
}

.merch-store-count {
    font-size: var(--font-size-xs);
    color: var(--color-text-secondary);
    margin-top: 2px;
}

.merch-store-header-actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
}

.merch-store-cart-btn {
    position: relative;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--color-bg-elevated);
    color: var(--color-text-primary);
    font-size: var(--font-size-base);
    border: none;
    cursor: pointer;
    transition: background var(--transition-fast), transform var(--transition-fast);
}

.merch-store-cart-btn[hidden] {
    display: none;
}

.merch-store-cart-btn:hover {
    background: var(--color-bg-alt);
    transform: scale(1.05);
}

.merch-store-cart-btn:active {
    transform: scale(0.95);
}

.merch-store-cart-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    border-radius: var(--radius-full);
    background: var(--color-gold);
    color: var(--color-bg-dark);
    font-family: var(--font-body);
    font-size: 0.65rem;
    font-weight: var(--fw-bold);
    line-height: 18px;
    text-align: center;
}

/* ----------------------------------------
   Shared close-button theme for every merch modal.
   All close buttons (store, detail, cart, order) share
   the same subtle-red hover/focus color via these tokens
   so visual language stays consistent. Sizes differ per
   modal and are set on each individual selector below. */
.merch-store-close,
.merch-detail-close,
.merch-cart-close,
.merch-order-close {
    --close-fg:        var(--color-error);
    --close-fg-hover:  var(--color-error);
    --close-bg:        var(--color-error-bg);
    --close-bg-hover:  var(--color-error-border);
}

.merch-store-close {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-full);
    background: var(--close-bg);
    color: var(--close-fg);
    font-size: var(--font-size-lg);
    border: none;
    cursor: pointer;
    flex-shrink: 0;
    transition: background var(--transition-fast), color var(--transition-fast), transform var(--transition-fast);
}

.merch-store-close:hover {
    background: var(--close-bg-hover);
    color: var(--close-fg-hover);
    transform: scale(1.05);
}

.merch-store-close:active {
    transform: scale(0.95);
}

.merch-store-body {
    flex: 1;
    overflow-y: auto;
    padding: var(--spacing-xl);
}

/* ----------------------------------------
   Store Category Sections
   Each category gets its own heading + grid so shoppers can scan
   a large catalog by organizational theme. Populated at runtime
   by h5-f1-store.js → buildGroupedStoreHTML().
   ---------------------------------------- */
.merch-store-category {
    display: block;
}

.merch-store-category + .merch-store-category {
    margin-top: var(--spacing-2xl, 2.5rem);
}

.merch-store-category-heading {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--spacing-md, 1rem);
    margin: 0 0 var(--spacing-lg, 1.5rem);
    padding-bottom: var(--spacing-sm, 0.5rem);
    border-bottom: 1px solid var(--color-border, #414d5c);
    font-family: var(--font-heading, "Inter", sans-serif);
    font-size: var(--font-size-lg, 1.125rem);
    font-weight: var(--fw-semibold, 600);
    color: var(--color-text-primary, #d1d5db);
    letter-spacing: 0.01em;
}

.merch-store-category-name {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm, 0.5rem);
}

.merch-store-category-count {
    font-size: var(--font-size-sm, 0.875rem);
    font-weight: var(--fw-regular, 400);
    color: var(--color-text-secondary, #8d9baa);
    white-space: nowrap;
}

/* ----------------------------------------
   Store Category Filler — "More products coming soon"

   Rendered by h5-f1-store.js as the trailing
   tile of any category whose product count would leave empty
   columns on the first grid row (count < max grid cols, currently
   5). Sized via `grid-column: calc(var(--merch-count) + 1) / -1`
   so it stretches across the unused trailing cells in the same
   row as the cards — a quiet, italic teaser, no chrome.

   Hidden on mobile (≤639px): with 2-col stacking and a single
   item, the message would be cramped to a single cell. Above
   each breakpoint the filler shows only when the category count
   is strictly less than that breakpoint's column count, so a
   row that already fills exactly at the larger breakpoint hides
   the filler again automatically.
   ---------------------------------------- */
.merch-store-filler {
    display: none;
    grid-column: calc(var(--merch-count, 1) + 1) / -1;
    align-items: center;
    justify-content: center;
    min-height: 100%;
    padding: var(--spacing-md, 1rem);
    color: var(--color-text-tertiary, #6b7280);
    font-family: var(--font-body);
    font-size: var(--font-size-sm, 0.875rem);
    font-style: italic;
    letter-spacing: 0.02em;
    text-align: center;
    -webkit-user-select: none;
    user-select: none;
}

/* Reveal at each breakpoint only while the category has fewer
   products than that breakpoint's column count. The grid carries
   both `data-count="N"` (drives visibility via exact-match
   attribute selectors) and inline `--merch-count: N` (drives the
   filler's `grid-column` span calc). JS only emits the filler
   for counts 1..4, so attribute selectors below stay tightly
   scoped to the values that can actually appear. */
@media (min-width: 640px) {
    /* 3-col grid: show when count is 1 or 2 */
    .merch-store-grid[data-count="1"] .merch-store-filler,
    .merch-store-grid[data-count="2"] .merch-store-filler {
        display: flex;
    }
}

@media (min-width: 1024px) {
    /* 4-col grid: also show when count is 3 */
    .merch-store-grid[data-count="3"] .merch-store-filler {
        display: flex;
    }
}

@media (min-width: 1440px) {
    /* 5-col grid: also show when count is 4 */
    .merch-store-grid[data-count="4"] .merch-store-filler {
        display: flex;
    }
}

/* Store modal grid — 5 cols on extra-wide screens for denser browsing */
@media (min-width: 1440px) {
    .merch-store-grid {
        grid-template-columns: repeat(5, 1fr);
    }
}

/* ----------------------------------------
   Product Detail Modal
   ---------------------------------------- */
.merch-detail {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base), visibility var(--transition-base);
}

.merch-detail.active {
    opacity: 1;
    visibility: visible;
}

.merch-detail-backdrop {
    position: absolute;
    inset: 0;
    background: var(--color-backdrop);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}

.merch-detail-content {
    /* Responsive sizing tokens — single source of truth for the
       product detail modal. Override per-breakpoint below.

       `--merch-detail-max-height` uses the dynamic viewport unit
       (`dvh`) so the modal shrinks/grows as iOS Safari's URL bar
       collapses and re-expands. Static `vh` previously made the
       modal taller than the visible viewport, clipping the
       "Add to Cart" button at the bottom. The `vh` value is kept
       as a fallback for browsers that don't yet support `dvh`. */
    --merch-detail-width: 92%;
    --merch-detail-max-width: 560px;
    --merch-detail-max-height: 92vh;
    --merch-detail-max-height: 92dvh;

    /* Scroll-viewport breathing room. Symmetric tunable padding so the
       image is never flush against the modal's top edge and the action
       stack is never flush against the bottom. Defaults to zero on
       desktop (image already nicely framed at standard sizes); mobile
       overrides bump these in one place. The bottom value composes
       with the iOS home-indicator safe area so we never paint behind
       system chrome. */
    --merch-detail-content-pt: 0px;
    --merch-detail-content-pb: 0px;

    position: relative;
    width: var(--merch-detail-width);
    max-width: var(--merch-detail-max-width);
    max-height: var(--merch-detail-max-height);
    background: var(--color-bg-surface);
    border-radius: var(--radius-md);
    overflow-y: auto;
    /* Prevent iOS rubber-band scroll from leaking to the page
       behind the modal, and reserve space for the home-indicator
       safe area so the final action (Add to Cart) is never
       hidden behind system chrome. */
    overscroll-behavior: contain;
    padding-top: var(--merch-detail-content-pt);
    padding-bottom: calc(
        var(--merch-detail-content-pb) + env(safe-area-inset-bottom, 0px)
    );
    transform: scale(0.95);
    transition: transform var(--transition-base);
}

/* Mobile — promote the modal to a full-screen iOS-style sheet so the
   sticky Previous / Next nav row truly hugs the viewport bottom instead
   of pinning to the modal's bottom edge with a strip of backdrop
   showing below it. All four tokens are overridden in one place so the
   centered-card look on desktop stays untouched. `border-radius: 0`
   removes the rounded corners that read as wasted edge space at full
   width. */
@media (max-width: 639px) {
    .merch-detail-content {
        --merch-detail-width: 100%;
        --merch-detail-max-width: 100%;
        --merch-detail-max-height: 100vh;
        --merch-detail-max-height: 100dvh;
        /* Force the modal to fill the viewport on mobile. Without
           this the box is only `max-height: 100dvh` and shrinks to
           its content, leaving the flex-centered wrapper to reveal
           backdrop above and below — which is what makes the sticky
           Prev/Next row appear to float with empty dark space
           beneath it. `min-height` (with `vh` fallback) guarantees
           the scrollport reaches the viewport's bottom edge so
           `bottom: 0` actually pins there. */
        min-height: 100vh;
        min-height: 100dvh;
        border-radius: 0;
    }
}

/* Tablet — two-column layout engages at 640px (see .merch-detail-body),
   so give the modal more breathing room once that kicks in. */
@media (min-width: 640px) {
    .merch-detail-content {
        --merch-detail-max-width: 880px;
    }
}

/* Desktop — at this breakpoint the product detail modal becomes a
   feature surface: a full 90% of the viewport, capped only by `100vw`
   so it never spills off-screen. The width and max-width tokens are
   driven from `--merch-detail-width-desktop` and
   `--merch-detail-max-width-desktop` so a future redesign (or a
   per-tenant theme) can resize the surface in one place. */
@media (min-width: 1024px) {
    .merch-detail-content {
        --merch-detail-width-desktop: 90%;
        --merch-detail-max-width-desktop: 100vw;
        --merch-detail-width: var(--merch-detail-width-desktop);
        --merch-detail-max-width: var(--merch-detail-max-width-desktop);
    }
}

/* Large desktop — same 90% rule. We deliberately do NOT cap the
   width here: the user asked for a full 90% surface on desktop,
   and clamping at a fixed pixel value would shrink the modal back
   on ultra-wide displays. Readability is preserved because the
   info column inside is content-sized (see `.merch-detail-info`). */
@media (min-width: 1440px) {
    .merch-detail-content {
        --merch-detail-max-width: 100vw;
    }
}

.merch-detail.active .merch-detail-content {
    transform: scale(1);
}

/* Close button — anchored to the bottom-right of the image area
   (rather than the top-right of the modal) so it remains within
   reach on iOS Safari, where the collapsing URL bar would clip
   a top-anchored button on first paint. The image-wrap is the
   positioning context (see `.merch-detail-image-wrap` below).

   Offsets and the contrast-boosting drop shadow are token-driven
   so a future redesign can override them per breakpoint without
   duplicating the rule. */
.merch-detail-close {
    --close-offset: var(--spacing-md);

    position: absolute;
    bottom: var(--close-offset);
    right: var(--close-offset);
    z-index: 3; /* sit above carousel nav arrows + counter */
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: var(--radius-full);
    background: var(--close-bg);
    color: var(--close-fg);
    font-size: var(--font-size-lg);
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
    transition: background var(--transition-fast), color var(--transition-fast);
}

.merch-detail-close:hover {
    background: var(--close-bg-hover);
    color: var(--close-fg-hover);
}

.merch-detail-body {
    display: flex;
    flex-direction: column;
    /* Row mode (≥640px) uses `align-items: stretch` so the info
       column matches the (sticky, square) image column's height —
       this is what lets the action stack anchor to the bottom of
       the column via `margin-top: auto` on `.merch-qty-control`,
       producing the magazine-style "details up top, actions at
       image-bottom" balance. The image column is kept pinned to
       the top via its own `align-self: flex-start` so a long
       description never stretches the photo. */
}

@media (min-width: 640px) {
    .merch-detail-body {
        flex-direction: row;
        align-items: stretch;
    }
}

.merch-detail-image-wrap {
    /* Positioning context for the absolutely-positioned close button
       (anchored to the bottom-right of the image area). */
    position: relative;
    flex-shrink: 0;
    width: 100%;
    background: var(--color-bg-alt);
}

@media (min-width: 640px) {
    .merch-detail-image-wrap {
        width: 50%;
        /* Keep the image in view while a long description scrolls. */
        position: sticky;
        top: 0;
        align-self: flex-start;
    }
}

/* ----------------------------------------
   Detail Modal Carousel
   Horizontal slide track (translated in JS) with prev/next
   arrows, dot navigation, counter, and touch swipe.
   ---------------------------------------- */
.merch-detail-carousel {
    position: relative;
    overflow: hidden;
    width: 100%;
    touch-action: pan-y; /* allow vertical scroll but intercept horizontal swipe */
}

.merch-detail-carousel-track {
    display: flex;
    width: 100%;
    transition: transform var(--transition-base);
    will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
    .merch-detail-carousel-track { transition: none; }
}

.merch-detail-slide {
    flex: 0 0 100%;
    min-width: 0;
    position: relative;
    overflow: hidden;
    /* Cap repaint to this slide's box so the neighboring slide's
       blurred backdrop can't dirty pixels outside its own bounds
       while the track is mid-translate. */
    contain: paint;
}

/* Slide frame stretches to fill the slide width; spinner +
   placeholder inherit from .merch-img-frame shared styles.
   Frame becomes transparent so the blurred backdrop layer
   (rendered behind it) shows through the letterbox bands. */
.merch-detail-slide .merch-img-frame {
    width: 100%;
    background: transparent;
    position: relative;
    z-index: 1;
}

/* ----------------------------------------
   Letterbox Backdrop — blurred copy of the slide image painted
   behind the contained image so unused top/bottom or left/right
   space feels color-matched and intentional rather than blank.

   Cost: zero extra network. The backdrop's `background-image`
   reuses the same signed URL the foreground `<img>` already
   loaded, so the browser serves it from cache. JS sets the URL
   once per slide via the `--mz-bg` CSS variable on the slide.

   Hidden (opacity: 0) when the slide is zoomed because the
   magnified foreground image dominates the frame and the
   blurred copy would compete with it. The fade is
   intentionally fast (--transition-fast) so on zoom exit the
   heavy blurred layer settles quickly and never overlaps the
   user's first scroll attempt — a longer transition here
   measurably delays scroll input on desktop while the
   compositor is repainting.
   ---------------------------------------- */
.merch-detail-slide-backdrop {
    position: absolute;
    inset: 0;
    z-index: 0;
    background-image: var(--mz-bg, none);
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
    /* Slight upscale hides the blur's edge softening.
       `translate3d(0,0,0)` promotes this layer to its own GPU
       texture so the (expensive) blur rasterizes once and is
       composited as a cached bitmap while the parent track
       translates between slides — without this, each frame of
       the slide transition re-rasterizes both the outgoing and
       incoming blur layers and the swipe feels choppy. */
    transform: translate3d(0, 0, 0) scale(1.15);
    will-change: transform;
    /* 16px is the visual sweet spot — blur cost scales
       super-linearly with radius, so dropping from 28→16 is a
       large compositing win for a barely-perceptible change. */
    filter: blur(16px) saturate(1.15);
    opacity: 0.55;
    pointer-events: none;
    transition: opacity var(--transition-fast, 0.15s) ease;
}

.merch-detail-slide.is-zoomed .merch-detail-slide-backdrop {
    opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
    .merch-detail-slide-backdrop { transition: none; }
}

/* ----------------------------------------
   Detail-slide image — full-bleed by default, zoomable.

   `object-fit: contain` ensures customers see the entire
   image regardless of its aspect ratio (no cropping inside
   the 1:1 frame); the blurred backdrop above fills the
   letterbox bands.

   Tap/click toggles zoom; on touch devices a two-finger
   pinch enters zoom continuously and a one-finger drag pans
   while zoomed (handled in h5-f1-store.js).
   While zoomed the image switches to `cover` and is scaled
   by `--mz-scale` with `transform-origin` driven by pointer
   position (`--mz-x`, `--mz-y`). `touch-action: none` keeps
   the pan/pinch gesture from being hijacked by the carousel
   swipe handler — JS also bails the swipe handler while
   zoomed for belt-and-braces.

   No transition on the transform: the zoomed image must track
   pinch/pan in real time, and on exit a snap-back is both
   visually crisper and avoids holding a promoted compositor
   layer alive during a 300ms animation, which on desktop made
   the next scroll input feel briefly stuck while the GPU
   finished settling. `will-change` is scoped to the zoomed
   class only so the layer is created on enter and released
   immediately on exit.
   ---------------------------------------- */
/* ----------------------------------------
   Detail-slide image — full-bleed by default, zoomable.

   `object-fit: contain` is preserved in BOTH the unzoomed
   and zoomed states. Customers see the entire image regardless
   of its aspect ratio — no cropping is ever introduced by the
   zoom flow itself. The blurred backdrop fills the letterbox
   bands at scale=1 and is faded out once the magnified image
   has filled (or nearly filled) the slide.

   Tap/click toggles zoom; on touch devices a two-finger
   pinch enters zoom continuously and a one-finger drag pans
   while zoomed (handled in h5-f1-store.js).
   While zoomed the image is scaled by `--mz-scale` with
   `transform-origin` driven by pointer position
   (`--mz-x`, `--mz-y`). `touch-action: none` keeps the
   pan/pinch gesture from being hijacked by the carousel
   swipe handler — JS also bails the swipe handler while
   zoomed for belt-and-braces.

   No transition on the transform: the zoomed image must track
   pinch/pan in real time, and on exit a snap-back is both
   visually crisper and avoids holding a promoted compositor
   layer alive during a 300ms animation, which on desktop made
   the next scroll input feel briefly stuck while the GPU
   finished settling. `will-change` is scoped to the zoomed
   class only so the layer is created on enter and released
   immediately on exit.
   ---------------------------------------- */
.merch-detail-slide .merch-img {
    object-fit: contain;
    cursor: zoom-in;
    transform-origin: 50% 50%;
}

.merch-detail-slide.is-zoomed .merch-img {
    cursor: zoom-out;
    touch-action: none;
    transform: scale(var(--mz-scale, 2));
    transform-origin: var(--mz-x, 50%) var(--mz-y, 50%);
    will-change: transform;
}

/* ----------------------------------------
   Detail-carousel arrows — rectangular gold pills sized to
   match `.merch-add-btn`, placed below the Exit button at
   the bottom of the action stack. Pulling them out of the
   image area keeps the photo uncluttered while giving the
   arrows the same visual weight as the primary actions, so
   they're easy to tap on touch devices and never compete
   with the photo for attention.

   The slide counter and dot navigation continue to overlay
   the image (their original positions) — those are passive
   indicators, not tappable affordances, so they don't need
   the same prominence as the navigation buttons.

   Tokens (`--nav-h` / `--nav-px` / `--nav-font`) are set on
   the base rule so the mobile breakpoint only overrides the
   values that change. Add a new variant by re-declaring
   tokens on a modifier class.
   ---------------------------------------- */
.merch-detail-carousel-nav-row {
    display: flex;
    align-items: stretch;
    gap: var(--spacing-sm, 0.75rem);
    width: 100%;
    margin-top: var(--spacing-sm, 0.75rem);
}

/* Single-image products: JS hides both buttons via `[hidden]`,
   so the row collapses entirely (the inline gap would otherwise
   leave a visible blank strip). */
.merch-detail-carousel-nav-row:has(.merch-detail-carousel-nav[hidden]) {
    display: none;
}

.merch-detail-carousel-nav {
    --nav-h:    auto;
    --nav-px:   var(--spacing-lg);
    --nav-py:   var(--spacing-md);
    --nav-font: var(--font-size-base);
    --nav-gap:  var(--spacing-sm);

    flex: 1 1 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--nav-gap);
    height: var(--nav-h);
    padding: var(--nav-py) var(--nav-px);
    background: var(--color-gold);
    color: var(--color-bg-dark, #000716);
    border: none;
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-size: var(--nav-font);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background var(--transition-fast), transform var(--transition-fast);
}

/* Hover-only — mouse/trackpad/stylus. Touch keeps `:hover` set
   on the most recently tapped element (stuck-hover bug on the
   next arrow during a swipe), so this rule is gated to
   (hover: hover) and (pointer: fine). Keyboard users get the
   same affordance via `:focus-visible` below. */
@media (hover: hover) and (pointer: fine) {
    .merch-detail-carousel-nav:hover {
        background: var(--color-gold-hover);
    }
}

.merch-detail-carousel-nav:focus-visible {
    outline: none;
    background: var(--color-gold-hover);
    box-shadow: 0 0 0 3px rgba(255, 153, 0, 0.4);
}

/* Pressed feedback — fires on every input modality (mouse,
   touch, keyboard via Space). Mirrors `.merch-add-btn:active`
   for visual consistency with the primary action. */
.merch-detail-carousel-nav:active {
    transform: scale(0.98);
}

/* Re-honor `[hidden]` since `display: inline-flex` above wins
   over the UA `[hidden] { display: none }`. */
.merch-detail-carousel-nav[hidden] { display: none; }

/* Mobile — keep the same proportions but allow the buttons to
   shrink a touch so they don't crowd the action stack on
   small viewports. */
@media (max-width: 639px) {
    .merch-detail-carousel-nav {
        --nav-px:   var(--spacing-md);
        --nav-font: var(--font-size-sm, 0.9rem);
    }
}

/* ----------------------------------------
   Sticky-on-mobile nav row.

   On narrow viewports the Previous / Next buttons remain
   accessible after the user scrolls past their natural
   position by pinning to the bottom of the modal scrollport
   via `position: sticky`. The chrome (translucent backdrop,
   drop shadow) only appears when the row is actually stuck
   — toggled via the `is-stuck` class set by an
   IntersectionObserver in h5-f1-store.js
   (sentinel: `#merchDetailNavSentinel`).

   Layout strategy:
   - The row keeps its natural width inside the info column,
     so the Previous / Next buttons stay perfectly centered
     under the rest of the action stack.
   - A `::before` pseudo-element paints the stuck chrome
     across the full viewport width, so the blue surface
     spans edge-to-edge on mobile (the modal itself is 95%
     of the viewport, leaving 2.5% transparent on each side
     without the pseudo-element).
   - All values are tokenized so a future redesign / theme
     can tune surface tone, padding, blur, and shadow in
     one place.
   ---------------------------------------- */
.merch-detail-nav-sentinel {
    /* Zero-cost IntersectionObserver target. 1px so that a
       `threshold: 1` observer fires reliably the moment any
       portion of it leaves the scrollport's bottom edge. */
    flex: 0 0 auto;
    height: 1px;
    margin: 0;
    pointer-events: none;
}

@media (max-width: 639px) {
    .merch-detail-carousel-nav-row {
        /* Asymmetric stuck-chrome padding — top gets a slightly
           larger gap so the buttons are visually pushed down from
           the upper edge of the chrome, while the bottom stays
           tight (the safe-area extension below already handles the
           visual breathing room on the device-bottom side). Split
           into two tokens so each side can be tuned in one place. */
        --merch-detail-nav-pad-top:    var(--spacing-sm);
        --merch-detail-nav-pad-bottom: var(--spacing-xs);
        /* Match the product modal's own surface color
           (`.merch-detail-content` uses the same token) so the
           stuck chrome reads as a seamless continuation of the
           modal's bottom area — no visible "panel" boundary, the
           buttons appear to float in the modal's own background. */
        --merch-detail-nav-bg:       var(--color-bg-surface);
        --merch-detail-nav-shadow:   0 -8px 16px -8px rgba(0, 0, 0, 0.45);
        --merch-detail-nav-blur:     blur(8px);

        position: sticky;
        bottom: 0;
        z-index: 2;
        /* Establish a positioning + stacking context so the
           full-viewport-width background pseudo-element below
           paints behind the buttons without escaping this row. */
        isolation: isolate;
        transition: padding var(--transition-fast);
    }

    /* Full-viewport-width background painted only while stuck.
       `left: 50%; transform: translateX(-50%); width: 100vw`
       extends the surface symmetrically around the row's center,
       so the buttons stay centered while the blue background
       reaches both viewport edges.

       The pseudo-element extends BELOW the row by the modal's
       bottom padding + iOS safe-area inset so the surface
       reaches the very bottom of the viewport with no gap.
       Sticky positioning aligns the row to the scrollport's
       content-box bottom, which is the parent's padding-edge
       MINUS the parent's `padding-bottom` — leaving a visible
       gap below the row through which the underlying content
       (description text, etc.) would otherwise show. Extending
       the background closes that gap cleanly without changing
       the buttons' centered position.

       `z-index: -1` parks it behind the buttons within the
       row's own stacking context. */
    .merch-detail-carousel-nav-row.is-stuck::before {
        content: "";
        position: absolute;
        top: 0;
        /* Extend the chrome down past the row to cover both the
           modal's bottom padding AND the iOS home-indicator safe
           area, so the surface reaches the device's true bottom
           edge with no gap. Without this, the modal's scrolling
           content (description, image) leaks through the strip
           below the buttons on Face-ID iPhones. Composing both
           tokens keeps the expression correct if a future
           breakpoint sets a non-zero `--merch-detail-content-pb`
           on mobile. */
        bottom: calc(
            -1 * (
                var(--merch-detail-content-pb, 0px) +
                env(safe-area-inset-bottom, 0px)
            )
        );
        left: 50%;
        transform: translateX(-50%);
        width: 100vw;
        background: var(--merch-detail-nav-bg);
        -webkit-backdrop-filter: var(--merch-detail-nav-blur);
        backdrop-filter: var(--merch-detail-nav-blur);
        box-shadow: var(--merch-detail-nav-shadow);
        pointer-events: none;
        z-index: -1;
    }

    /* Keep the buttons clear of the chrome edges with asymmetric
       breathing room — see the token comments above for rationale. */
    .merch-detail-carousel-nav-row.is-stuck {
        padding-top:    var(--merch-detail-nav-pad-top);
        padding-bottom: var(--merch-detail-nav-pad-bottom);
    }
}

@media (prefers-reduced-motion: reduce) {
    .merch-detail-carousel-nav-row {
        transition: none;
    }
}

/* Slide counter — overlay pill at the top-right of the image. */
.merch-detail-carousel-counter {
    position: absolute;
    top: 8px;
    right: 8px;
    padding: 2px 8px;
    font-size: 0.75rem;
    font-weight: var(--fw-medium, 500);
    background: rgba(0, 7, 22, 0.6);
    color: #fff;
    border-radius: 999px;
    z-index: 2;
    pointer-events: none;
}

/* Single-image products: JS sets the counter's textContent to "",
   but the element still paints as a small dark pill (padding +
   background). Hide it whenever it has no text. */
.merch-detail-carousel-counter:empty { display: none; }

/* Dot navigation — overlay strip across the bottom of the image. */
.merch-detail-carousel-dots {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 10px;
    display: flex;
    justify-content: center;
    gap: 6px;
    z-index: 2;
}

.merch-detail-carousel-dot {
    width: 8px;
    height: 8px;
    border: 0;
    padding: 0;
    border-radius: var(--radius-full);
    background: rgba(255, 255, 255, 0.5);
    cursor: pointer;
    transition: background var(--transition-fast), transform var(--transition-fast);
}

/* Hover-only — see the rationale on `.merch-detail-carousel-nav`. */
@media (hover: hover) and (pointer: fine) {
    .merch-detail-carousel-dot:hover { background: rgba(255, 255, 255, 0.8); }
}

.merch-detail-carousel-dot.is-active {
    background: var(--color-gold, #ff9900);
    transform: scale(1.2);
}

.merch-detail-info {
    padding: var(--spacing-lg) var(--spacing-xl);
    /* Extra top/bottom breathing room so the action stack (Add To Cart
       + Exit) is never visually cramped against the modal edges and the
       user can comfortably scroll fully past the buttons on mobile.
       Token-driven so the rhythm can be tuned per breakpoint in one
       place — mobile overrides this in the small-screen media query. */
    --merch-detail-info-py: var(--spacing-lg);
    padding-top: var(--merch-detail-info-py);
    padding-bottom: var(--merch-detail-info-py);
    display: flex;
    flex-direction: column;
    /* Allow this column to shrink below its content size so the
       parent modal's `overflow-y: auto` engages for long copy. */
    min-width: 0;
    flex: 1 1 auto;
}

/* Desktop balance — push the action stack (qty → Add To Cart → Prev/Next
   → Exit) to the bottom of the info column so it aligns with the image's
   bottom edge instead of clinging to the top under the price. The
   info column already stretches to image height (see `.merch-detail-body`
   ≥640px rule), so `margin-top: auto` on the first action element
   absorbs the leftover space as deliberate breathing room between the
   product details and the CTAs. Single-rule fix: tuning the action-stack
   start point only requires moving the selector.

   Type scale bumps in the same block lift the product copy to match the
   wider desktop column — at small sizes the text reads cramped against
   the spacious 50% width; bumping name/price/desc one token-step each
   restores a real-product-page hierarchy without touching mobile. All
   three values are font-size-token references so the global scale stays
   the single source of truth. */
@media (min-width: 640px) {
    .merch-detail-info .merch-qty-control {
        margin-top: auto;
    }

    .merch-detail-name {
        font-size: var(--font-size-4xl);
    }

    .merch-detail-desc {
        font-size: var(--font-size-lg);
        /* Looser leading multiplies across every wrapped line, so a
           short description still occupies meaningfully more vertical
           space — the cheapest way to fill the column without making
           the type itself comically large. */
        line-height: 1.75;
    }

    .merch-detail-price {
        font-size: var(--font-size-2xl);
    }
}

.merch-detail-name {
    font-family: var(--font-heading);
    font-size: var(--font-size-2xl);
    font-weight: var(--fw-semibold);
    color: var(--color-text-primary);
    margin: 0 0 var(--spacing-sm);
    /* Long names wrap gracefully instead of forcing horizontal scroll. */
    overflow-wrap: anywhere;
}

.merch-detail-desc {
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    color: var(--color-text-secondary);
    line-height: 1.6;
    margin: 0 0 var(--spacing-md);
    /* Preserve author line-breaks + wrap long unbroken strings. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.merch-detail-price {
    font-family: var(--font-body);
    font-size: var(--font-size-xl);
    font-weight: var(--fw-bold);
    color: var(--color-gold);
    margin: 0;
}

.merch-add-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    width: 100%;
    padding: var(--spacing-md) var(--spacing-lg);
    border: none;
    border-radius: var(--radius-sm);
    background: var(--color-gold);
    color: var(--color-bg-dark);
    font-family: var(--font-body);
    font-size: var(--font-size-base);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background var(--transition-fast);
}

.merch-add-btn:hover {
    background: var(--color-gold-hover);
}

.merch-add-btn:active {
    transform: scale(0.98);
}

/* Visual feedback states (set briefly by JS after addToCart) — mirror
   the card-button modifiers so the same flash works on both surfaces.
   See `.merch-card-add-btn--added` for the rationale on choosing
   confirm-blue over success-green. */
.merch-add-btn--added,
.merch-add-btn--added:hover {
    --merch-add-confirm-bg: #3b82f6;
    --merch-add-confirm-fg: #ffffff;

    background: var(--merch-add-confirm-bg);
    color: var(--merch-add-confirm-fg);
    pointer-events: none;
}

.merch-add-btn--max,
.merch-add-btn--max:hover {
    background: var(--color-bg-elevated);
    color: var(--color-text-tertiary);
    pointer-events: none;
}

/* Secondary "Exit" affordance beneath Add To Cart — mirrors the red X on
   the image so users who scroll past the image can still dismiss the
   modal without scrolling back up. Outlined treatment keeps the gold
   primary CTA visually dominant. Tokenized to inherit shape/sizing
   from `.merch-add-btn` for one-place tuning. */
.merch-exit-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    width: 100%;
    margin-top: var(--spacing-sm);
    padding: var(--spacing-md) var(--spacing-lg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text-secondary);
    font-family: var(--font-body);
    font-size: var(--font-size-base);
    font-weight: var(--fw-medium);
    cursor: pointer;
    transition: background var(--transition-fast),
                border-color var(--transition-fast),
                color var(--transition-fast);
}

.merch-exit-btn:hover {
    background: var(--color-bg-alt);
    border-color: var(--color-text-secondary);
    color: var(--color-text-primary);
}

.merch-exit-btn:active {
    transform: scale(0.98);
}

/* ----------------------------------------
   Cart Modal
   ---------------------------------------- */
.merch-cart {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: stretch;
    justify-content: flex-end;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base), visibility var(--transition-base);
}

.merch-cart.active {
    opacity: 1;
    visibility: visible;
}

.merch-cart-backdrop {
    position: absolute;
    inset: 0;
    background: var(--color-backdrop-light);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}

.merch-cart-content {
    /* Cart drawer width is configuration-driven via --merch-cart-width so
       it can be overridden per-breakpoint (mobile full-bleed, desktop 50vw)
       without duplicating layout rules. */
    --merch-cart-width: 520px;
    position: relative;
    width: var(--merch-cart-width);
    max-width: 100%;
    background: var(--color-bg-surface);
    display: flex;
    flex-direction: column;
    transform: translateX(100%);
    transition: transform var(--transition-base);
}

/* Desktop — cart drawer occupies half the viewport for a roomier checkout view. */
@media (min-width: 1024px) {
    .merch-cart-content {
        --merch-cart-width: 50vw;
    }
}

.merch-cart.active .merch-cart-content {
    transform: translateX(0);
}

/* Header is intentionally compact — every pixel here is pixels
   not available to the cart table. Padding tokens picked one
   step down from the surrounding modals; close button is the
   smallest size that still hits the 32px touch baseline. */
.merch-cart-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--spacing-sm) var(--spacing-lg);
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}

.merch-cart-title {
    font-family: var(--font-heading);
    font-size: var(--font-size-lg);
    font-weight: var(--fw-semibold);
    color: var(--color-text-primary);
    margin: 0;
}

.merch-cart-close {
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: var(--radius-full);
    /* Persistent ring around the X so it always reads as a tappable
       affordance — not only when the user happens to hover. The
       hover state still elevates contrast for affordance feedback. */
    background: var(--close-bg-hover);
    color: var(--close-fg);
    font-size: var(--font-size-lg);
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast);
}

.merch-cart-close:hover {
    background: var(--close-bg-hover);
    color: var(--close-fg-hover);
}

.merch-cart-body {
    flex: 1 1 auto;
    min-height: 0; /* allow flex child to shrink so overflow scroll engages */
    overscroll-behavior: contain;
    padding: var(--spacing-lg) var(--spacing-xl);
    /* Anchor for the absolutely-positioned mobile scroll-hint arrow. */
    position: relative;
    /* Flex column so the empty-state placeholder can vertically
       center itself when it is the sole child, and the populated
       table-wrap can flex-fill so its horizontal scrollbar sits at
       the very bottom of the body — flush with the footer separator
       above "Continue Shopping". This puts the scrollbar within
       easy thumb reach on mobile instead of floating mid-drawer. */
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Cart Empty State — centered both axes within the cart body. */
.merch-cart-empty {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--spacing-xl) 0;
    color: var(--color-text-tertiary);
}

.merch-cart-empty-icon {
    font-size: 2.5rem;
    margin-bottom: var(--spacing-md);
    opacity: 0.5;
}

/* Cart Table
   Column order (left → right):
     Quantity · Image · Item · Price · Total · Remove
   Every cell is single-line (`white-space: nowrap`); when the
   viewport is narrower than the sum of the column widths the
   table-wrap scrolls horizontally rather than cramming cells. */
.merch-cart-table-wrap {
    /* Owns both scroll axes so the horizontal scrollbar docks at
       the bottom edge of the cart body — immediately above the
       footer separator — regardless of how many items are in the
       cart. `flex: 1` makes it fill the body; `min-height: 0` lets
       it shrink so the inner overflow engages. */
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    overflow: auto;
    overscroll-behavior: contain;
}

.merch-cart-table {
    /* Column-width tokens (single source of truth). Numeric
       columns size to their declared widths so digits align;
       the image column is fluid so the photo grows with any
       slack the row leaves over after the fixed columns. */
    --w-qty:    120px;
    --w-price:   72px;
    --w-img:     56px;
    --w-remove:  36px;

    /* `min-width` keeps every column at its declared width; the
       wrapper above scrolls horizontally on narrow viewports
       instead of collapsing headers into each other. */
    width: 100%;
    min-width: calc(
        var(--w-qty) + var(--w-price) + var(--w-img) + var(--w-remove)
    );
    border-collapse: collapse;
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
}

.merch-cart-table thead th {
    text-align: left;
    /* Tighter horizontal cell padding so adjacent column
       headings (Quantity ↔ Price) sit visually close — the
       previous `--spacing-xs` on each side compounded into a
       wide gulf when the table relaxed to 100% width. */
    padding: var(--spacing-sm) 4px;
    border-bottom: 1px solid var(--color-border);
    color: var(--color-text-tertiary);
    font-weight: var(--fw-medium);
    font-size: var(--font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
}

/* ----------------------------------------
   Cart column widths — driven by tokens on .merch-cart-table.
   Selectors are qualified with `thead th` so right-align on
   numeric headers wins specificity against the base rule above.
   The image column intentionally has no `width` rule — it
   absorbs whatever slack remains so the photo stays prominent.
   ---------------------------------------- */
.merch-cart-table thead th.merch-cart-th-qty    { width: var(--w-qty);    }
.merch-cart-table thead th.merch-cart-th-price  { width: var(--w-price);  text-align: right; }
.merch-cart-table thead th.merch-cart-th-remove { width: var(--w-remove); }

.merch-cart-table tbody td {
    padding: var(--spacing-sm) 4px;
    border-bottom: 1px solid var(--color-border);
    vertical-align: middle;
    color: var(--color-text-primary);
    white-space: nowrap;
}

/* Numeric column right-aligns so digits line up vertically. */
.merch-cart-td-price { text-align: right; font-variant-numeric: tabular-nums; }

/* Cart row thumbnails use the shared .merch-img-frame--cart
   variant; this selector keeps the cell at the token width so
   the photo never shrinks below the frame size. */
.merch-cart-item-img-cell { width: var(--w-img); }

.merch-cart-item-qty {
    display: flex;
    align-items: center;
    gap: var(--spacing-xs);
}

.merch-cart-item-qty-btn {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-xs);
    background: transparent;
    color: var(--color-text-primary);
    font-size: 0.7rem;
    cursor: pointer;
    transition: background var(--transition-fast);
}

.merch-cart-item-qty-btn:hover {
    background: var(--color-bg-alt);
}

.merch-cart-item-qty-value {
    min-width: 1.5rem;
    text-align: center;
    font-weight: var(--fw-semibold);
}

.merch-cart-item-remove {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: var(--radius-xs);
    background: transparent;
    color: var(--color-text-tertiary);
    font-size: 0.75rem;
    cursor: pointer;
    transition: color var(--transition-fast), background var(--transition-fast);
}

.merch-cart-item-remove:hover {
    color: var(--color-crimson);
    background: var(--color-crimson-light);
}

/* Cart Total Row */
.merch-cart-total-row td {
    border-bottom: none;
    padding-top: var(--spacing-md);
}

.merch-cart-total-label {
    font-weight: var(--fw-semibold);
    text-align: right;
    color: var(--color-text-primary);
    /* Breathing room between "Order Total" and the value cell. */
    padding-right: var(--spacing-lg) !important;
}

.merch-cart-total-value {
    font-weight: var(--fw-bold);
    color: var(--color-gold);
    font-size: var(--font-size-lg);
    white-space: nowrap;
}

/* ----------------------------------------
   Mobile horizontal-scroll hint
   A pulsing arrow pinned to the right edge of the cart body plus a
   reinforcing caption beneath the table. Activated by JS when the
   cart table overflows horizontally on narrow viewports; hidden as
   soon as the user scrolls the table. Config-driven via tokens so
   size, color, and pulse timing can be tuned in one place.
   ---------------------------------------- */
.merch-cart-scroll-arrow {
    /* Tokens — single source of truth for sizing / motion / color */
    --mcsh-size:       40px;
    --mcsh-offset:     var(--spacing-sm);
    --mcsh-color:      var(--color-white, #fff);
    --mcsh-bg:         var(--color-gold, #c9a961);
    --mcsh-shadow:     0 4px 12px rgba(0, 0, 0, 0.25);
    --mcsh-pulse-ms:   1600ms;

    position: absolute;
    top: 50%;
    right: var(--mcsh-offset);
    transform: translateY(-50%);
    width: var(--mcsh-size);
    height: var(--mcsh-size);
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: var(--mcsh-bg);
    color: var(--mcsh-color);
    font-size: calc(var(--mcsh-size) * 0.45);
    box-shadow: var(--mcsh-shadow);
    cursor: pointer;
    z-index: 2;
    animation: merch-cart-scroll-pulse var(--mcsh-pulse-ms) ease-in-out infinite;
}

.merch-cart-scroll-arrow:hover,
.merch-cart-scroll-arrow:focus-visible {
    filter: brightness(1.08);
}

.merch-cart-scroll-arrow:active {
    filter: brightness(0.92);
}

.merch-cart-scroll-arrow[hidden] {
    display: none;
}

@keyframes merch-cart-scroll-pulse {
    0%,
    100% {
        transform: translateY(-50%) translateX(0);
        opacity: 0.85;
    }
    50% {
        transform: translateY(-50%) translateX(6px);
        opacity: 1;
    }
}

.merch-cart-scroll-message {
    /* Pushed clear of the table's bottom edge so the cue reads as
       a discrete hint rather than crowding the last row. The
       caption is also a flex item inside the cart body, so the
       margin reclaims vertical space on every viewport without
       any media-query branching. */
    margin: var(--spacing-lg) 0 0;
    padding: var(--spacing-sm) var(--spacing-md);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    font-size: var(--font-size-xs);
    color: var(--color-text-tertiary);
    background: var(--color-bg-alt);
    border-radius: var(--radius-sm);
    text-align: center;
}

.merch-cart-scroll-message[hidden] {
    display: none;
}

.merch-cart-scroll-message i {
    color: var(--color-gold, #c9a961);
}

@media (prefers-reduced-motion: reduce) {
    .merch-cart-scroll-arrow {
        animation: none;
    }
}

/* Cart Footer — compacted to mirror the trimmed header. The
   action buttons keep their internal padding so they remain
   comfortable touch targets; only the surrounding gutter
   shrinks, freeing vertical space for the table above. */
.merch-cart-footer {
    display: flex;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-lg);
    border-top: 1px solid var(--color-border);
    flex-shrink: 0;
    /* Respect notch / home-indicator on iOS */
    padding-bottom: calc(var(--spacing-sm) + env(safe-area-inset-bottom, 0px));
}

.merch-cart-footer[hidden] {
    display: none;
}

.merch-cart-continue-btn,
.merch-cart-checkout-btn {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-md);
    border: none;
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background var(--transition-fast);
}

.merch-cart-continue-btn {
    background: var(--color-bg-alt);
    color: var(--color-text-primary);
}

.merch-cart-continue-btn:hover {
    background: var(--color-bg-elevated);
}

.merch-cart-checkout-btn {
    background: var(--color-gold);
    color: var(--color-bg-dark);
}

.merch-cart-checkout-btn:hover {
    background: var(--color-gold-hover);
}

/* ----------------------------------------
   Confirmation Modal
   Generic centered "are you sure?" dialog. Layered above the
   cart drawer (z-index 300) so it interrupts whatever modal
   triggered it. Visuals borrow the same surface, radius, and
   gold accent as the order/success modals to stay consistent.
   ---------------------------------------- */
.merch-confirm {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base), visibility var(--transition-base);
}

.merch-confirm.active {
    opacity: 1;
    visibility: visible;
}

.merch-confirm-backdrop {
    position: absolute;
    inset: 0;
    background: var(--color-backdrop);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}

.merch-confirm-content {
    position: relative;
    text-align: center;
    padding: var(--spacing-2xl) var(--spacing-xl);
    background: var(--color-bg-surface);
    border-radius: var(--radius-md);
    max-width: 380px;
    width: 90%;
    transform: scale(0.92);
    transition: transform var(--transition-base);
    box-shadow: var(--shadow-lg, 0 12px 32px rgba(0, 0, 0, 0.4));
}

.merch-confirm.active .merch-confirm-content {
    transform: scale(1);
}

.merch-confirm-icon {
    width: 56px;
    height: 56px;
    margin: 0 auto var(--spacing-md);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--color-crimson-light, rgba(220, 53, 69, 0.12));
    color: var(--color-crimson, #dc3545);
    font-size: 1.5rem;
}

.merch-confirm-title {
    font-family: var(--font-heading);
    font-size: var(--font-size-xl);
    font-weight: var(--fw-semibold);
    color: var(--color-text-primary);
    margin: 0 0 var(--spacing-sm);
}

.merch-confirm-message {
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    color: var(--color-text-secondary);
    margin: 0 0 var(--spacing-xl);
    line-height: 1.5;
}

.merch-confirm-actions {
    display: flex;
    gap: var(--spacing-sm);
    justify-content: center;
}

.merch-confirm-btn {
    flex: 1;
    max-width: 160px;
    padding: var(--spacing-sm) var(--spacing-lg);
    border: none;
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast);
}

.merch-confirm-btn--cancel {
    background: var(--color-bg-alt);
    color: var(--color-text-primary);
}

.merch-confirm-btn--cancel:hover {
    background: var(--color-bg-elevated);
}

.merch-confirm-btn--accept {
    background: var(--color-crimson, #dc3545);
    color: var(--color-white, #fff);
}

.merch-confirm-btn--accept:hover {
    filter: brightness(1.08);
}

/* ----------------------------------------
   Order Form Modal
   ---------------------------------------- */
.merch-order {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base), visibility var(--transition-base);
}

.merch-order.active {
    opacity: 1;
    visibility: visible;
}

.merch-order-backdrop {
    position: absolute;
    inset: 0;
    background: var(--color-backdrop);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}

.merch-order-content {
    position: relative;
    width: 90%;
    max-width: 460px;
    max-height: 90vh;
    background: var(--color-bg-surface);
    border-radius: var(--radius-md);
    overflow-y: auto;
    transform: scale(0.95);
    transition: transform var(--transition-base);
}

.merch-order.active .merch-order-content {
    transform: scale(1);
}

.merch-order-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--spacing-lg) var(--spacing-xl);
    border-bottom: 1px solid var(--color-border);
}

.merch-order-title {
    font-family: var(--font-heading);
    font-size: var(--font-size-xl);
    font-weight: var(--fw-semibold);
    color: var(--color-text-primary);
    margin: 0;
}

.merch-order-close {
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--close-fg);
    font-size: var(--font-size-lg);
    cursor: pointer;
    transition: background var(--transition-fast), color var(--transition-fast);
}

.merch-order-close:hover {
    background: var(--close-bg-hover);
    color: var(--close-fg-hover);
}

.merch-order-body {
    padding: var(--spacing-lg) var(--spacing-xl);
}

.merch-order-summary {
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    color: var(--color-text-secondary);
    margin: 0 0 var(--spacing-lg);
    line-height: 1.5;
}

.merch-order-field {
    margin-bottom: var(--spacing-md);
}

.merch-order-field--error .merch-order-input {
    border-color: var(--color-crimson);
}

.merch-order-label {
    display: block;
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    font-weight: var(--fw-medium);
    color: var(--color-text-primary);
    margin-bottom: var(--spacing-xs);
}

.merch-order-input {
    width: 100%;
    padding: var(--spacing-sm) var(--spacing-md);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-size: var(--font-size-base);
    color: var(--color-text-primary);
    background: var(--color-bg-surface);
    transition: border-color var(--transition-fast);
    box-sizing: border-box;
}

.merch-order-input:focus {
    outline: none;
    border-color: var(--color-gold);
}

.merch-order-field-error {
    display: block;
    font-family: var(--font-body);
    font-size: var(--font-size-xs);
    color: var(--color-crimson);
    margin-top: var(--spacing-xs);
    min-height: 1rem;
}

.merch-order-submit {
    width: 100%;
    padding: var(--spacing-md);
    border: none;
    border-radius: var(--radius-sm);
    background: var(--color-gold);
    color: var(--color-bg-dark);
    font-family: var(--font-body);
    font-size: var(--font-size-base);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background var(--transition-fast);
    position: relative;
}

.merch-order-submit:hover {
    background: var(--color-gold-hover);
}

.merch-order-submit:disabled {
    cursor: not-allowed;
    opacity: 0.7;
}

.merch-order-spinner {
    display: none;
    margin-left: var(--spacing-sm);
}

.merch-order-submit:disabled .merch-order-submit-label {
    opacity: 0.7;
}

.merch-order-submit:disabled .merch-order-spinner {
    display: inline-block;
}

.merch-order-status {
    margin-top: var(--spacing-md);
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    color: var(--color-crimson);
    text-align: center;
    min-height: 1.2rem;
}

/* ----------------------------------------
   Success Overlay
   ---------------------------------------- */
.merch-success {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base), visibility var(--transition-base);
}

.merch-success.active {
    opacity: 1;
    visibility: visible;
}

.merch-success-backdrop {
    position: absolute;
    inset: 0;
    background: var(--color-backdrop);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}

.merch-success-content {
    position: relative;
    text-align: center;
    padding: var(--spacing-3xl) var(--spacing-2xl);
    background: var(--color-bg-surface);
    border-radius: var(--radius-md);
    max-width: 420px;
    width: 90%;
    transform: scale(0.9);
    transition: transform var(--transition-slow);
}

.merch-success.active .merch-success-content {
    transform: scale(1);
}

.merch-success-checkmark {
    width: 72px;
    height: 72px;
    margin: 0 auto var(--spacing-lg);
}

.merch-success-svg {
    width: 100%;
    height: 100%;
}

.merch-success-circle {
    stroke: var(--color-success);
    stroke-width: 2;
    stroke-dasharray: 157;
    stroke-dashoffset: 157;
    animation: merchCircle 0.6s ease forwards 0.2s;
}

.merch-success-check {
    stroke: var(--color-success);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 50;
    stroke-dashoffset: 50;
    animation: merchCheck 0.4s ease forwards 0.7s;
}

@keyframes merchCircle {
    to { stroke-dashoffset: 0; }
}

@keyframes merchCheck {
    to { stroke-dashoffset: 0; }
}

.merch-success-title {
    font-family: var(--font-heading);
    font-size: var(--font-size-2xl);
    font-weight: var(--fw-semibold);
    color: var(--color-text-primary);
    margin: 0 0 var(--spacing-sm);
}

.merch-success-message {
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    color: var(--color-text-secondary);
    margin: 0 0 var(--spacing-sm);
    line-height: 1.5;
}

.merch-success-id {
    font-family: var(--font-body);
    font-size: var(--font-size-xs);
    color: var(--color-text-tertiary);
    margin: 0 0 var(--spacing-xl);
}

.merch-success-btn {
    padding: var(--spacing-sm) var(--spacing-2xl);
    border: none;
    border-radius: var(--radius-full);
    background: var(--color-gold);
    color: var(--color-bg-dark);
    font-family: var(--font-body);
    font-size: var(--font-size-base);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background var(--transition-fast);
}

.merch-success-btn:hover {
    background: var(--color-gold-hover);
}

/* ----------------------------------------
   Empty State
   ---------------------------------------- */
.merch-empty {
    padding: var(--spacing-3xl) 0;
    text-align: center;
}

.merch-empty[hidden] {
    display: none;
}

.merch-empty-inner {
    max-width: 320px;
    margin: 0 auto;
}

.merch-empty-icon {
    font-size: 3rem;
    color: var(--color-text-tertiary);
    opacity: 0.4;
    margin-bottom: var(--spacing-md);
}

.merch-empty-title {
    font-family: var(--font-heading);
    font-size: var(--font-size-xl);
    color: var(--color-text-primary);
    margin: 0 0 var(--spacing-sm);
}

.merch-empty-subtitle {
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    color: var(--color-text-secondary);
    margin: 0;
}

/* ----------------------------------------
   Skeleton Loader
   ---------------------------------------- */
.merch-skeleton {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--spacing-md);
}

.merch-skeleton[hidden] {
    display: none;
}

@media (min-width: 640px) {
    .merch-skeleton {
        grid-template-columns: repeat(3, 1fr);
        gap: var(--spacing-lg);
    }
}

@media (min-width: 1024px) {
    .merch-skeleton {
        grid-template-columns: repeat(4, 1fr);
        gap: var(--spacing-xl);
    }
}

.merch-skeleton-item {
    border-radius: var(--radius-md);
    background: linear-gradient(90deg, var(--color-bg-alt) 25%, var(--color-bg-elevated) 50%, var(--color-bg-alt) 75%);
    background-size: 200% 100%;
    animation: merchShimmer 1.5s infinite;
    padding-top: 130%;
}

@keyframes merchShimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* ----------------------------------------
   Responsive — Small screens
   ---------------------------------------- */
@media (max-width: 639px) {
    .merch-store-content {
        width: 100vw;
        height: 100vh;
        height: 100dvh;
        border-radius: 0;
    }

    .merch-store-body {
        padding: var(--spacing-md);
    }

    .merch-cart-content {
        --merch-cart-width: 100%;
    }

    .merch-detail-content {
        --merch-detail-width: 95%;
        --merch-detail-max-height: 95vh;
        /* Symmetric mobile breathing room inside the scroll viewport so
           the user can pull the image fully into view at the top and
           comfortably scroll past the action stack at the bottom. */
        --merch-detail-content-pt: var(--spacing-2xl);
        --merch-detail-content-pb: var(--spacing-2xl);
    }

    /* Mobile breathing room: bump the info column's vertical padding so
       the action stack (Add To Cart + Exit) is never visually cramped
       against the modal edges and the user can comfortably scroll fully
       past the buttons. Single token override keeps the rule scoped. */
    .merch-detail-info {
        --merch-detail-info-py: var(--spacing-2xl);
    }

    .merch-cart-footer {
        flex-direction: column;
    }
}

/* ----------------------------------------
   Touch Device Overrides
   ---------------------------------------- */
@media (hover: none) and (pointer: coarse) {
    .merch-card:hover {
        transform: none;
        box-shadow: var(--shadow-sm);
    }

    .merch-card:active {
        transform: scale(0.98);
    }

    .merch-qty-btn {
        width: 44px;
        height: 44px;
    }

    .merch-cart-item-qty-btn {
        width: 36px;
        height: 36px;
    }

    .merch-cart-item-remove {
        width: 36px;
        height: 36px;
    }

    .merch-shop-btn {
        min-height: 44px;
    }

    .merch-store-close,
    .merch-store-cart-btn {
        width: 48px;
        height: 48px;
    }
}

/* ----------------------------------------
   Reduced Motion
   ---------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .merch-card,
    .merch-shop-btn,
    .merch-store,
    .merch-store-content,
    .merch-store-close,
    .merch-store-cart-btn,
    .merch-detail,
    .merch-detail-content,
    .merch-cart,
    .merch-cart-content,
    .merch-order,
    .merch-order-content,
    .merch-success,
    .merch-success-content,
    .merch-confirm,
    .merch-confirm-content,
    .merch-cart-bar {
        transition: none;
    }

    .merch-skeleton-item {
        animation: none;
    }

    .merch-success-circle,
    .merch-success-check {
        animation: none;
        stroke-dashoffset: 0;
    }
}

/* ----------------------------------------
   Top-Anchored Toast — lightweight, modal-agnostic
   confirmation banner used by flows that need to acknowledge
   an action without dismissing the surrounding UI.

   Currently invoked by the product detail modal's "Add To Cart"
   button so the customer stays in the product view (great for
   adding multiple variants in a row), with a brief slide-down
   confirmation from the top of the viewport. The element is
   created lazily by JS, lives on `document.body`, and reuses
   a single instance across every call.

   Z-INDEX: above all merchandise modals (success at 300) so
   it remains visible while a modal is open. Fixed positioning
   with `pointer-events: none` keeps the toast click-through
   so it never blocks an interaction underneath.
   ---------------------------------------- */
/* Toast component-scope tokens. Centralize edge offset, hidden
   travel, and surface colors so the top and bottom variants stay
   in lockstep and so a future toast (warning, info, error) can
   override only the color it cares about. */
.merch-toast {
    --toast-edge-offset: 16px;
    --toast-hidden-travel: calc(100% + 24px);
    --toast-bg: rgba(0, 7, 22, 0.94);
    /* The site-wide `--color-text-inverse` resolves to dark navy
       (it's intended for use ON light surfaces). The toast surface
       is dark, so we set an explicit light foreground here rather
       than inherit the inverse token — otherwise the message
       reads as black-on-dark and the icon disappears entirely. */
    --toast-fg: #ffffff;
    --toast-icon: #34d399;

    position: fixed;
    left: 50%;
    /* Default = top variant. The bottom variant flips both the
       anchoring edge and the hidden-state translate direction. */
    top: max(env(safe-area-inset-top, 0px), var(--toast-edge-offset));
    transform: translate(-50%, calc(-1 * var(--toast-hidden-travel)));
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 1.1rem;
    background: var(--toast-bg);
    color: var(--toast-fg);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: var(--radius-full, 999px);
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
    font-size: 0.95rem;
    font-weight: var(--fw-medium, 500);
    line-height: 1.3;
    max-width: min(92vw, 480px);
    z-index: 400;
    pointer-events: none;
    opacity: 0;
    transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.28s ease;
}

.merch-toast.is-visible {
    transform: translate(-50%, 0);
    opacity: 1;
}

/* Bottom-anchored variant — used by the zoom-out hint so the
   message sits below the magnified image and doesn't compete
   with carousel chrome at the top of the slide. */
.merch-toast--bottom {
    top: auto;
    bottom: max(env(safe-area-inset-bottom, 0px), var(--toast-edge-offset));
    transform: translate(-50%, var(--toast-hidden-travel));
}

.merch-toast--bottom.is-visible {
    transform: translate(-50%, 0);
}

.merch-toast-icon {
    color: var(--toast-icon);
    font-size: 1.05rem;
    flex: 0 0 auto;
}

.merch-toast-message {
    /* Allow long product names to wrap inside the pill rather
       than overflow off the side of narrow viewports. */
    white-space: normal;
    overflow-wrap: anywhere;
    /* Inherit the toast surface foreground so subclasses that
       override `--toast-fg` (e.g. a warning variant) keep the
       message text in sync without a second rule. */
    color: inherit;
}

@media (prefers-reduced-motion: reduce) {
    .merch-toast,
    .merch-toast--bottom {
        transition: opacity 0.15s ease;
        transform: translate(-50%, 0);
    }
}
