﻿/* ==========================================================================
   BazarKoribo storefront
   Customer-facing components composed from the design-system tokens.
   ========================================================================== */

/* ==========================================================================
   Header
   ========================================================================== */

.site-header {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
    background: var(--glass-bg-strong);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border-bottom: 1px solid var(--border-subtle);
    transition: box-shadow var(--duration-base) var(--ease-out);
}

.site-header.is-scrolled { box-shadow: var(--shadow-sm); }

@supports not (backdrop-filter: blur(1px)) {
    .site-header { background: var(--surface-card); }
}

.header__top {
    display: none;
    background: var(--brand-secondary);
    color: var(--text-inverse);
    font-size: var(--text-xs);
}

@media (min-width: 1024px) {
    .header__top { display: block; }
}

.header__top .container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 34px;
    gap: var(--space-4);
}

.header__top a { color: var(--text-inverse); opacity: 0.9; }
.header__top a:hover { opacity: 1; color: var(--text-inverse); }

.header__main {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: var(--header-height);
}

.logo {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-xl);
    font-weight: 800;
    color: var(--brand-primary);
    white-space: nowrap;
    flex-shrink: 0;
}

.logo:hover { color: var(--brand-primary-dark); }

/* The logo is the way back to the homepage from anywhere, and its painted height is set by
   the text. Padding rather than a min-height, so the header row keeps its own rhythm and
   only the touchable area grows. */
@media (hover: none) {
    .logo { padding-block: var(--space-1); min-height: 44px; }
}
.logo__mark { font-size: 1.5rem; line-height: 1; }
.logo__tagline {
    display: none;
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--text-muted);
}

@media (min-width: 1024px) {
    .logo__tagline { display: block; }
}

/* Search --------------------------------------------------------------------
   Hidden on the smallest screens where it would crowd the logo and cart;
   a dedicated search row appears below instead. */
.header__search {
    flex: 1;
    position: relative;
    display: none;
}

@media (min-width: 768px) {
    .header__search { display: block; }
}

.search-box {
    display: flex;
    align-items: center;
    background: var(--surface-sunken);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-pill);
    padding-inline-start: var(--space-4);
    transition: border-color var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out);
}

.search-box:focus-within {
    border-color: var(--brand-primary);
    box-shadow: 0 0 0 3px var(--brand-primary-soft);
    background: var(--surface-card);
}

.search-box__input {
    flex: 1;
    /* An <input> carries an intrinsic default width of roughly 20 characters, and a
       flex item never shrinks below its intrinsic minimum unless told to. Without
       this the search box is wider than a 390px phone and pushes the whole page
       into a horizontal scroll. */
    min-width: 0;
    width: 100%;
    border: none;
    background: transparent;
    padding: var(--space-3) var(--space-2);
    min-height: 42px;
}

.search-box__input:focus { outline: none; }

.search-box__button {
    border: none;
    background: var(--brand-primary);
    color: var(--text-on-brand);
    border-radius: var(--radius-pill);
    padding: var(--space-2) var(--space-5);
    margin: 4px;
    min-height: 34px;
    cursor: pointer;
    font-weight: 600;
    font-size: var(--text-sm);
}

.search-box__button:hover { background: var(--brand-primary-dark); }

/* Search is the most-used control on the site and sat at 34px, below the size a thumb hits
   reliably. Raised only on touch: the 4px margin keeps it inset inside the pill, so growing
   it on desktop would push the field taller for no gain. */
@media (hover: none) {
    .search-box__button { min-height: 40px; }
}

/* Autocomplete panel */
.search-suggestions {
    position: absolute;
    top: calc(100% + var(--space-2));
    inset-inline: 0;
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    max-height: 420px;
    overflow-y: auto;
    z-index: var(--z-dropdown);
}

.search-suggestions:empty { display: none; }

/* Entrance, so suggestions arrive rather than snapping into place. The mega-menu already
   animates; these did not, which made the two feel like different systems. */
@media (prefers-reduced-motion: no-preference) {
    .search-suggestions:not(:empty) {
        animation: dropdown-in var(--duration-fast) var(--ease-out);
    }
}

@keyframes dropdown-in {
    from { opacity: 0; transform: translateY(-6px); }
    to { opacity: 1; transform: none; }
}

.suggestion {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    cursor: pointer;
    border-bottom: 1px solid var(--border-subtle);
    color: var(--text-primary);
}

.suggestion:last-child { border-bottom: none; }

.suggestion:hover,
.suggestion[aria-selected="true"] {
    background: var(--brand-primary-soft);
    color: var(--text-primary);
}

.suggestion__img {
    width: 44px; height: 44px;
    object-fit: cover;
    border-radius: var(--radius-sm);
    background: var(--surface-sunken);
    flex-shrink: 0;
}

.suggestion__name { font-size: var(--text-sm); font-weight: 500; }
.suggestion__price { font-size: var(--text-xs); color: var(--brand-primary); font-weight: 600; }

/* Header actions ---------------------------------------------------------- */

.header__actions {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    margin-inline-start: auto;
}

.header-action {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: var(--space-2);
    min-width: 44px;
    min-height: 44px;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    background: none;
    border: none;
    cursor: pointer;
    font-size: var(--text-xs);
}

.header-action:hover { background: var(--surface-sunken); color: var(--brand-primary); }
.header-action__icon { font-size: 1.25rem; line-height: 1; }
.header-action__label { display: none; }

@media (min-width: 1024px) {
    .header-action__label { display: block; }
}

/* Count badge on the cart and wishlist icons */
.header-action__count {
    position: absolute;
    top: 2px;
    inset-inline-end: 2px;
    min-width: 18px;
    height: 18px;
    padding-inline: 5px;
    background: var(--brand-primary);
    color: var(--text-on-brand);
    border-radius: var(--radius-pill);
    font-size: 10px;
    font-weight: 700;
    display: grid;
    place-items: center;
    line-height: 1;
}

.header-action__count:empty,
.header-action__count[data-count="0"] { display: none; }

/* A brief pulse confirms the cart changed without interrupting the flow. */
.header-action__count.is-bumped { animation: count-bump var(--duration-slow) var(--ease-out); }

@keyframes count-bump {
    0% { transform: scale(1); }
    45% { transform: scale(1.35); }
    100% { transform: scale(1); }
}

/* Language toggle --------------------------------------------------------- */

.lang-toggle {
    display: inline-flex;
    background: var(--surface-sunken);
    border-radius: var(--radius-pill);
    padding: 2px;
    gap: 2px;
}

.lang-toggle__option {
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-secondary);
    border: none;
    background: none;
    cursor: pointer;
    min-height: 30px;
}

.lang-toggle__option.is-active {
    background: var(--surface-card);
    color: var(--brand-primary);
    box-shadow: var(--shadow-xs);
}

/* Category navigation ----------------------------------------------------- */

.category-nav {
    display: none;
    border-top: 1px solid var(--border-subtle);
    background: var(--surface-card);
}

@media (min-width: 1024px) {
    .category-nav { display: block; }
}

/* Wraps rather than scrolls on purpose. `overflow-x: auto` here made the strip a scroll
   container, and a scroll container clips its absolutely positioned descendants -- so the
   mega menu still painted but stopped receiving the pointer, and the hero underneath took
   every click. `overflow-y` cannot be re-opened on its own either: CSS forces it to `auto`
   whenever `overflow-x` is, so a horizontally scrollable strip and a hover panel are
   mutually exclusive. Wrapping keeps every category reachable without clipping anything. */
.category-nav__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: var(--space-1);
    row-gap: 0;
    list-style: none;
    margin: 0;
    padding: 0;
}

.category-nav__item { position: relative; }

.category-nav__link {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    /* Tight enough that the full category row fits on one line at ordinary desktop
       widths; below roughly 1150px it wraps to a second row instead of being cut off. */
    padding: var(--space-3) var(--space-3);
    color: var(--text-secondary);
    font-size: var(--text-sm);
    font-weight: 500;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
}

.category-nav__link:hover {
    color: var(--brand-primary);
    border-bottom-color: var(--brand-primary);
}

/* Mega menu opens on hover for pointers and on focus for keyboards. */
.category-nav__panel {
    position: absolute;
    top: 100%;
    inset-inline-start: 0;
    min-width: 240px;
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: var(--space-3);
    z-index: var(--z-dropdown);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-6px);
    transition: opacity var(--duration-fast) var(--ease-out),
                transform var(--duration-fast) var(--ease-out),
                visibility var(--duration-fast);
}

.category-nav__item:hover > .category-nav__panel,
.category-nav__item:focus-within > .category-nav__panel {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* The open item is lifted above its later siblings, whose links would otherwise sit on
   top of the panel and swallow the pointer on the way down to the submenu. */
.category-nav__item:hover,
.category-nav__item:focus-within { z-index: 1; }

/* A panel near the right edge hangs from the right of its trigger instead of the left, so
   it stays on screen. Which items those are cannot be expressed as a positional selector
   once the row wraps -- an item can wrap to the start of the second row -- so the flag is
   set from measured layout in storefront.js. */
.category-nav__item.is-flipped > .category-nav__panel {
    inset-inline-start: auto;
    inset-inline-end: 0;
}

.category-nav__panel a {
    display: block;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: var(--text-sm);
}

.category-nav__panel a:hover { background: var(--brand-primary-soft); color: var(--brand-primary); }

/* Mobile search row ------------------------------------------------------- */

.mobile-search {
    padding: var(--space-2) 0 var(--space-3);
    border-top: 1px solid var(--border-subtle);
}

@media (min-width: 768px) {
    .mobile-search { display: none; }
}

/* ==========================================================================
   Bottom navigation — thumb-reachable primary actions on mobile
   ========================================================================== */

.bottom-nav {
    position: fixed;
    bottom: 0;
    inset-inline: 0;
    z-index: var(--z-sticky);
    display: flex;
    background: var(--glass-bg-strong);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border-top: 1px solid var(--border-default);
    /* Clears the iOS home indicator. */
    padding-bottom: env(safe-area-inset-bottom, 0);
}

@media (min-width: 1024px) {
    .bottom-nav { display: none; }
}

@supports not (backdrop-filter: blur(1px)) {
    .bottom-nav { background: var(--surface-card); }
}

.bottom-nav__item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: var(--space-2);
    min-height: var(--bottom-nav-height);
    color: var(--text-muted);
    font-size: 10px;
    font-weight: 500;
    position: relative;
}

.bottom-nav__item.is-active { color: var(--brand-primary); }
.bottom-nav__icon { font-size: 1.25rem; line-height: 1; }

/* The page needs room so the fixed bar never covers the last element. */
@media (max-width: 1023px) {
    body { padding-bottom: calc(var(--bottom-nav-height) + env(safe-area-inset-bottom, 0px)); }
}

/* ==========================================================================
   Hero
   ========================================================================== */

.hero {
    position: relative;
    border-radius: var(--radius-xl);
    overflow: hidden;
    background: linear-gradient(135deg, var(--brand-primary-soft), var(--brand-secondary-soft));
    margin-block: var(--space-5);
}

/* The track lays every slide in one row and slides horizontally. Transform is used
   rather than left/scroll so the animation stays on the compositor. */
.hero__track {
    display: flex;
    transition: transform var(--duration-slow, 420ms) var(--ease-out);
    will-change: transform;
}

.hero__slide {
    position: relative;
    display: grid;
    align-items: center;
    flex: 0 0 100%;
    min-width: 0;
    /* Matches the 4:3 mobile artwork, so the illustration fills the slide instead of
       being letterboxed inside a shorter box. */
    aspect-ratio: 4 / 3;
    min-height: 320px;
    max-height: 78vh;
}

@media (min-width: 768px) {
    /* Slightly wider than the 2.5:1 artwork, which trims the empty sky above and
       below the subjects rather than letterboxing the slide. */
    .hero__slide {
        aspect-ratio: 3 / 1;
        min-height: 340px;
        max-height: 460px;
    }
}

@media (min-width: 1100px) {
    .hero__slide { min-height: 440px; }
}

/* Previous / next arrows. Hidden on touch-first widths where swiping is the
   natural gesture and the arrows would only cover artwork. */
.hero__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: none;
    place-items: center;
    width: 44px; height: 44px;
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-pill);
    background: var(--glass-bg-strong);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    color: var(--text-primary);
    font-size: 1.75rem;
    line-height: 1;
    cursor: pointer;
    box-shadow: var(--shadow-sm);
    transition: background var(--duration-base) var(--ease-out),
                transform var(--duration-base) var(--ease-out);
}

.hero__nav:hover { background: #fff; }
.hero__nav:active { transform: translateY(-50%) scale(0.94); }
.hero__nav--prev { inset-inline-start: var(--space-4); }
.hero__nav--next { inset-inline-end: var(--space-4); }

@media (min-width: 768px) {
    .hero__nav { display: grid; }
}

/* The dots sit over artwork of unknown lightness, so the strip carries its own pill
   of contrast rather than relying on whatever is behind it. Width comes from the
   dots themselves, so the pill always hugs them however many slides there are. */
.hero__dots {
    position: absolute;
    inset-inline: 0;
    bottom: var(--space-4);
    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--space-2);
    width: max-content;
    margin-inline: auto;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-pill);
    background: rgba(28, 25, 23, 0.3);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.hero__dot {
    position: relative;
    width: 30px; height: 6px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, 0.7);
    cursor: pointer;
    transition: background var(--duration-base) var(--ease-out),
                width var(--duration-base) var(--ease-out);
}

/* The hero dots are deliberately left at 6px.

   Two attempts to widen the touch area both broke the visual: .hero__dot sets
   overflow:hidden to contain its autoplay progress fill, so a ::before pad is clipped away,
   and growing the button or stretching it in the row turns the pill bar into a tall
   capsule -- the width animation on the active dot fights every version.

   Left as they are because they are an INDICATOR, not the navigation. The hero is swiped on
   a phone and has prev/next arrows on a pointer; nothing is unreachable if a dot is missed,
   and a working 6px indicator is better than a 44px one that looks wrong. */

.hero__dot.is-active {
    width: 44px;
    background: var(--brand-primary);
}

/* Dots and arrows are real buttons, so they must show a visible focus ring. */
.hero__dot:focus-visible,
.hero__nav:focus-visible {
    outline: 3px solid var(--brand-primary);
    outline-offset: 3px;
}

/* Without JS the track cannot translate, so fall back to a horizontal snap
   scroller: every slide stays reachable rather than only the first. */
.hero--slider:not([data-hero-ready]) .hero__track {
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
}

.hero--slider:not([data-hero-ready]) .hero__track::-webkit-scrollbar { display: none; }
.hero--slider:not([data-hero-ready]) .hero__slide { scroll-snap-align: start; }

@media (prefers-reduced-motion: reduce) {
    .hero__track { transition: none; }
}

.hero__image {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%; height: 100%;
    object-fit: cover;
}

.hero__content {
    position: relative;
    padding: var(--space-6) var(--space-5);
    width: 100%;
}

@media (min-width: 768px) {
    .hero__content {
        /* The artwork composes its subject on the right, so the panel is held to the
           left half and never covers it. */
        padding: var(--space-8);
        width: 56%;
        max-width: 620px;
    }
}

/* The glass panel is what makes hero text readable over an arbitrary photo. */
.hero__panel {
    /* Near-opaque rather than glassy: over a light illustration the translucent
       panel dropped body text below a readable contrast ratio. */
    background: rgba(255, 255, 255, 0.94);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    box-shadow: var(--shadow-lg);
}

@media (min-width: 768px) {
    .hero__panel { padding: var(--space-8); }
}

@supports not (backdrop-filter: blur(1px)) {
    .hero__panel { background: rgba(255, 255, 255, 0.97); }
}

.hero__title {
    font-size: clamp(1.6rem, 5vw, 2.5rem);
    margin-bottom: var(--space-2);
}

.hero__subtitle {
    color: var(--text-secondary);
    margin-bottom: var(--space-5);
}


/* Hero depth and motion -----------------------------------------------------

   The slider mechanics already worked; what follows is the presentation layer.

   Motion is driven by three custom properties the script writes -- --hero-x,
   --hero-y and --hero-lift -- and the stylesheet decides what each layer does
   with them. Two consequences worth the indirection: the hero renders complete
   and correct before any script runs (they default to 0 below), and the whole
   depth feel can be retuned by editing multipliers in one place.

   Layers move in OPPOSED directions. The artwork drifts one way, the copy the
   other. Same-direction motion reads as "the picture slid"; opposed reads as
   distance between them, which is the entire effect.                        */

.hero {
    --hero-x: 0;
    --hero-y: 0;
    --hero-lift: 0;
    --hero-interval: 6000ms;
    /* Confines the blurred glow and its blend mode to the hero, so neither
       bleeds onto the header sitting directly above it. */
    isolation: isolate;
}

/* A soft brand-tinted bloom behind the artwork, countering the pointer so it
   reads as sitting further back than everything else. */
.hero__glow {
    position: absolute;
    inset: -18%;
    /* Behind the artwork. Both are absolutely positioned siblings, so without an
       explicit negative layer the glow painted over the picture and tinted its
       right edge green. */
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(40% 46% at 22% 32%, rgba(232, 93, 44, 0.34), transparent 70%),
        radial-gradient(36% 42% at 78% 66%, rgba(31, 111, 74, 0.26), transparent 72%);
    filter: blur(28px);
    opacity: 0.75;
    transform: translate3d(calc(var(--hero-x) * -18px), calc(var(--hero-y) * -14px), 0);
    will-change: transform;
}

/* A specular highlight whose centre follows the pointer and which brightens as
   the pointer enters. Pure decoration, so it never intercepts clicks. */
.hero__glare {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background: radial-gradient(
        58% 74% at calc(50% + var(--hero-x) * 20%) calc(40% + var(--hero-y) * 20%),
        rgba(255, 255, 255, 0.22), transparent 62%);
    opacity: calc(0.25 + var(--hero-lift) * 0.4);
    transition: opacity var(--duration-slow) var(--ease-out);
}

/* The artwork leans away from the pointer and lifts very slightly on entry.
   The offsets are deliberately small. The hero artwork is a 2.5:1 illustration in
   a 3:1 frame, so `cover` is already cropping it; translating further walks the
   visible window off the composed subject and into the flat sky at its edge.
   6px of travel reads as depth without moving the picture off its own centre. */
.hero__image {
    /* Translation only, no base scale.
       These are 2.5:1 illustrations in a 3:1 frame, so `cover` is already cropping
       top and bottom and the subject sits close to the right edge. Scaling up
       re-resolves the cover fit and walks a band of the SVG's own flat sky into
       view -- measured as the warm sky at the edge turning pale green. A few pixels
       of travel give the depth without touching the fit. */
    transform: translate3d(calc(var(--hero-x) * -6px), calc(var(--hero-y) * -5px), 0);
    transition: transform var(--duration-slow) var(--ease-out);
    will-change: transform;
}

/* A slow ken-burns drift, but only on the slide currently on screen: an
   off-screen slide animating costs compositor work nobody can see.
   `alternate` ping-pongs, so the loop never jump-cuts back to its start. */
@media (prefers-reduced-motion: no-preference) {
    .hero__slide.is-active .hero__image {
        animation: hero-drift 22s var(--ease-in-out) infinite alternate;
    }
}

@keyframes hero-drift {
    /* The pointer offset is folded into both keyframes rather than left as a
       separate declaration: an animated transform replaces a static one
       outright, so the parallax would otherwise be dropped while this runs.
       A slow pan rather than a zoom, for the cover-fit reason noted above. */
    from {
        transform: translate3d(calc(var(--hero-x) * -6px - 4px), calc(var(--hero-y) * -5px), 0);
    }
    to {
        transform: translate3d(calc(var(--hero-x) * -6px + 4px), calc(var(--hero-y) * -5px), 0);
    }
}

/* The copy leans TOWARD the pointer while the artwork leans away. */
.hero__content {
    z-index: 3;
    transform: translate3d(calc(var(--hero-x) * 10px), calc(var(--hero-y) * 7px), 0);
    transition: transform var(--duration-slow) var(--ease-out);
}

/* The panel keeps its near-opaque ground for contrast, but the edge nearest the
   artwork is dissolved. A hard rounded edge landing mid-picture reads as a slab
   pasted on top; fading it reads as light falling across the copy. */
@media (min-width: 768px) {
    .hero__panel {
        -webkit-mask-image: linear-gradient(to right, #000 72%, transparent 100%);
        mask-image: linear-gradient(to right, #000 72%, transparent 100%);
    }

    [dir="rtl"] .hero__panel {
        -webkit-mask-image: linear-gradient(to left, #000 72%, transparent 100%);
        mask-image: linear-gradient(to left, #000 72%, transparent 100%);
    }
}

/* Entrance stagger. --i comes from the markup, so one rule serves any number of
   children. `both` holds the from-state during the delay, so nothing flashes at
   full opacity before its turn. */
.hero__reveal {
    animation: hero-rise 720ms var(--ease-out) both;
    animation-delay: calc(var(--i, 0) * 90ms + 120ms);
}

/* Disabling the animation on inactive slides is what makes it replay: when the
   script marks a slide active the cascade re-applies the rule from scratch, so
   every rotation runs the full stagger again with no JS and no reflow hack. */
.hero__slide:not(.is-active) .hero__reveal {
    animation: none;
    opacity: 0;
}

@keyframes hero-rise {
    from { opacity: 0; transform: translate3d(0, 20px, 0); }
    to   { opacity: 1; transform: none; }
}

/* Call to action ----------------------------------------------------------- */

.hero__cta {
    position: relative;
    overflow: hidden;
}

/* A shine sweeping across on hover. It animates transform rather than an inset,
   so it stays on the compositor instead of triggering layout on every frame. */
.hero__cta::after {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    width: 45%;
    background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.45), transparent);
    transform: translateX(-220%) skewX(-18deg);
    pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
    .hero__cta::after {
        transition: transform 620ms var(--ease-out);
    }

    .hero__cta:hover::after {
        transform: translateX(320%) skewX(-18deg);
    }
}

/* Autoplay progress -------------------------------------------------------- */

.hero__dot { overflow: hidden; }

.hero__dot-fill {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    /* Full width and scaled from zero: animating transform keeps the fill on the
       compositor, where animating `width` would lay out on every frame. */
    width: 100%;
    transform: scaleX(0);
    transform-origin: left center;
    border-radius: inherit;
    background: var(--brand-primary);
    pointer-events: none;
}

/* The bar fills over exactly one autoplay interval, and pauses through the same
   attribute that pauses the timer -- so the two cannot drift apart. */
@media (prefers-reduced-motion: no-preference) {
    .hero__dot.is-active .hero__dot-fill {
        animation: hero-progress var(--hero-interval) linear forwards;
    }

    .hero--slider[data-hero-paused="true"] .hero__dot-fill {
        animation-play-state: paused;
    }
}

@keyframes hero-progress {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

[dir="rtl"] .hero__dot-fill { transform-origin: right center; }

/* The active dot already widens; the fill rides inside it. */
.hero__dot.is-active { background: rgba(255, 255, 255, 0.45); }

/* No hover means no pointer to track, so the parallax can never run and the two
   blurred layers it drives are pure cost -- and they are the most expensive thing
   on the first screen. Keyed to the input rather than to width, so a touch laptop
   is treated as touch. Removed outright rather than faded, so they cost nothing. */
@media (hover: none) {
    .hero__glow,
    .hero__glare { display: none; }

    .hero__image,
    .hero__content { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .hero__glow,
    .hero__glare,
    .hero__image,
    .hero__content {
        transform: none;
        animation: none;
    }

    /* Shown complete rather than stuck empty, so the dot still reads as active. */
    .hero__dot.is-active .hero__dot-fill {
        animation: none;
        transform: scaleX(1);
    }
}

/* ==========================================================================
   Category tiles
   ========================================================================== */

.category-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: var(--space-3);
}

.category-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-4) var(--space-2);
    background: var(--surface-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    text-align: center;
    color: var(--text-primary);
    transition: transform var(--duration-base) var(--ease-out),
                box-shadow var(--duration-base) var(--ease-out),
                border-color var(--duration-base) var(--ease-out);
}

.category-tile:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-md);
    border-color: var(--brand-primary);
    color: var(--brand-primary);
}

/* Tile artwork. The illustrations already carry their own rounded background, so
   they need no frame of their own -- just a consistent box. */
.category-tile__img {
    width: 56px;
    height: 56px;
    border-radius: var(--radius-md);
    object-fit: contain;
}

@media (min-width: 768px) {
    .category-tile__img { width: 64px; height: 64px; }
}

.category-tile__icon {
    display: grid;
    place-items: center;
    width: 52px; height: 52px;
    border-radius: var(--radius-pill);
    background: var(--brand-primary-soft);
    font-size: 1.5rem;
}

.category-tile__name { font-size: var(--text-sm); font-weight: 600; line-height: 1.3; }

/* ==========================================================================
   Product card
   ========================================================================== */

.product-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--surface-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    overflow: hidden;
    transition: transform var(--duration-base) var(--ease-out),
                box-shadow var(--duration-base) var(--ease-out);
}

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

.product-card__media {
    position: relative;
    display: block;
    aspect-ratio: 1;
    background: var(--surface-sunken);
    overflow: hidden;
}

.product-card__img {
    width: 100%; height: 100%;
    /* contain, not cover: the artwork is composed to its own square with deliberate
       margins, and cropping the edges would clip the subject. Photographs uploaded
       later are re-encoded square by the storage layer, so they fill it too. */
    object-fit: contain;
    background: var(--surface-card);
    transition: transform var(--duration-slow) var(--ease-out);
}

.product-card:hover .product-card__img { transform: scale(1.05); }

.product-card__badges {
    position: absolute;
    top: var(--space-2);
    inset-inline-start: var(--space-2);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
    z-index: 1;
}

.product-card__quick-actions {
    position: absolute;
    top: var(--space-2);
    inset-inline-end: var(--space-2);
    z-index: 1;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.product-card__wish {
    display: grid;
    place-items: center;
    width: 36px; height: 36px;
    border-radius: var(--radius-pill);
    background: var(--glass-bg-strong);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1px solid var(--glass-border);
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    transition: transform var(--duration-fast) var(--ease-out);
}

.product-card__wish:hover { transform: scale(1.12); }


.product-card__wish.is-active { color: var(--brand-primary); }

/* A quick pop confirms the wishlist toggle registered. */
.product-card__wish.is-animating { animation: wish-pop var(--duration-slow) var(--ease-out); }

@keyframes wish-pop {
    0% { transform: scale(1); }
    40% { transform: scale(1.4); }
    70% { transform: scale(0.92); }
    100% { transform: scale(1); }
}

.product-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
    flex: 1;
}

.product-card__brand {
    font-size: var(--text-xs);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.product-card__name {
    font-size: var(--text-sm);
    font-weight: 600;
    line-height: 1.4;
    color: var(--text-primary);
    /* Two lines keeps every card in a row the same height. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.8em;
}

.product-card__rating {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.product-card__stars { color: #F59E0B; letter-spacing: -1px; }

.product-card__price {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: auto;
    /* One line's worth of height whether or not a struck-through original price is
       present, so the Add-to-cart buttons line up across a row of mixed cards. */
    min-height: 1.85em;
}

/* The low-stock badge is conditional. Reserving its row keeps cards that have one
   the same height as cards that do not. */
.product-card__stock {
    align-self: flex-start;
    margin-top: var(--space-1);
}

.price {
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--brand-primary);
    font-variant-numeric: tabular-nums;
}

.price--original {
    font-size: var(--text-sm);
    font-weight: 400;
    color: var(--text-muted);
    text-decoration: line-through;
}

.product-card__stock { font-size: var(--text-xs); }
.product-card__actions { padding: 0 var(--space-3) var(--space-3); }

/* ==========================================================================
   Product detail
   ========================================================================== */

.product-layout {
    display: grid;
    gap: var(--space-6);
    align-items: start;
}

@media (min-width: 1024px) {
    .product-layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

.gallery__main {
    position: relative;
    aspect-ratio: 1;
    background: var(--surface-sunken);
    border-radius: var(--radius-lg);
    overflow: hidden;
    cursor: zoom-in;
}

.gallery__main img { width: 100%; height: 100%; object-fit: contain; }

.gallery__thumbs {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-3);
    overflow-x: auto;
    padding-bottom: var(--space-2);
}

.gallery__thumb {
    flex: 0 0 auto;
    width: 68px; height: 68px;
    border-radius: var(--radius-md);
    overflow: hidden;
    border: 2px solid transparent;
    background: var(--surface-sunken);
    cursor: pointer;
    padding: 0;
}

.gallery__thumb.is-active { border-color: var(--brand-primary); }
.gallery__thumb img { width: 100%; height: 100%; object-fit: contain; }

.product-title { font-size: clamp(1.3rem, 3.5vw, 1.85rem); margin-bottom: var(--space-3); }

.product-price-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-3);
    padding-block: var(--space-4);
    border-block: 1px solid var(--border-subtle);
    margin-bottom: var(--space-4);
}

.product-price-row .price { font-size: var(--text-3xl); }

/* Variant selection ------------------------------------------------------- */

.variant-group { margin-bottom: var(--space-4); }

.variant-group__label {
    display: block;
    font-size: var(--text-sm);
    font-weight: 600;
    margin-bottom: var(--space-2);
}

.variant-options { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.variant-option {
    padding: var(--space-2) var(--space-4);
    min-height: 42px;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    background: var(--surface-card);
    cursor: pointer;
    font-size: var(--text-sm);
    transition: all var(--duration-fast) var(--ease-out);
}

.variant-option:hover { border-color: var(--brand-primary); }

.variant-option.is-selected {
    border-color: var(--brand-primary);
    background: var(--brand-primary-soft);
    color: var(--brand-primary);
    font-weight: 600;
}

.variant-option:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    text-decoration: line-through;
}

/* Quantity stepper -------------------------------------------------------- */

.qty {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.qty__btn {
    width: 42px; height: 42px;
    border: none;
    background: var(--surface-sunken);
    cursor: pointer;
    font-size: var(--text-lg);
    color: var(--text-primary);
    display: grid;
    place-items: center;
}

.qty__btn:hover:not(:disabled) { background: var(--brand-primary-soft); color: var(--brand-primary); }
.qty__btn:disabled { opacity: 0.4; cursor: not-allowed; }

.qty__input {
    width: 56px;
    height: 42px;
    border: none;
    text-align: center;
    font-weight: 600;
    background: var(--surface-card);
    -moz-appearance: textfield;
}

.qty__input::-webkit-outer-spin-button,
.qty__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Trust strip ------------------------------------------------------------- */

/* Two columns on a phone rather than four cramped ones; four across from tablet up. */
.trust-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
    margin-top: var(--space-6);
}

@media (min-width: 768px) {
    .trust-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* Cards holding a full sentence need room to set it. Below a comfortable reading
   width they stack; above it they share a row. */
.trust-grid--prose { grid-template-columns: 1fr; }

@media (min-width: 768px) {
    .trust-grid--prose {
        grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    }
}

.trust-grid--prose .trust-item { align-items: flex-start; }

.trust-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4);
    background: var(--surface-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-xs);
    font-size: var(--text-sm);
    min-width: 0;
    transition: transform var(--duration-base) var(--ease-out),
                box-shadow var(--duration-base) var(--ease-out),
                border-color var(--duration-base) var(--ease-out);
}

.trust-item:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-sm);
    border-color: var(--brand-primary);
}

/* Long labels wrap inside the card instead of widening the grid track. */
.trust-item > div { min-width: 0; }
.trust-item strong { font-weight: 600; }

/* The icon gets a soft brand-tinted disc, which reads as designed rather than as a
   bare emoji sitting on the page. */
.trust-item__icon {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 40px; height: 40px;
    border-radius: var(--radius-md);
    background: var(--brand-primary-soft);
    font-size: 1.25rem;
    line-height: 1;
}

/* Tabs -------------------------------------------------------------------- */

.tabs {
    display: flex;
    gap: var(--space-1);
    border-bottom: 1px solid var(--border-default);
    overflow-x: auto;
    margin-bottom: var(--space-5);
}

.tab {
    padding: var(--space-3) var(--space-5);
    border: none;
    background: none;
    cursor: pointer;
    font-weight: 600;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.tab:hover { color: var(--brand-primary); }
.tab[aria-selected="true"] { color: var(--brand-primary); border-bottom-color: var(--brand-primary); }

/* ==========================================================================
   Listing page
   ========================================================================== */

.listing-layout { display: grid; gap: var(--space-6); }

@media (min-width: 1024px) {
    .listing-layout { grid-template-columns: 260px minmax(0, 1fr); }
}

/* The sidebar becomes a slide-over drawer on mobile. */
.filters {
    background: var(--surface-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
}

@media (max-width: 1023px) {
    .filters {
        position: fixed;
        inset: 0;
        z-index: var(--z-drawer);
        border-radius: 0;
        overflow-y: auto;
        transform: translateX(-100%);
        transition: transform var(--duration-slow) var(--ease-out);
    }

    .filters.is-open { transform: translateX(0); }
}

.filter-group { padding-block: var(--space-4); border-bottom: 1px solid var(--border-subtle); }
.filter-group:last-child { border-bottom: none; }

.filter-group__title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    font-size: var(--text-sm);
    font-weight: 700;
    margin-bottom: var(--space-3);
    color: var(--text-primary);
}

.listing-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.view-toggle {
    display: inline-flex;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.view-toggle__btn {
    padding: var(--space-2) var(--space-3);
    min-height: 40px;
    border: none;
    background: var(--surface-card);
    cursor: pointer;
    color: var(--text-secondary);
}

.view-toggle__btn.is-active { background: var(--brand-primary); color: var(--text-on-brand); }

/* List view: the same cards laid out horizontally. */
.product-grid--list { grid-template-columns: 1fr; }

.product-grid--list .product-card { flex-direction: row; }

.product-grid--list .product-card__media {
    width: 140px;
    flex-shrink: 0;
    aspect-ratio: 1;
}

@media (min-width: 640px) {
    .product-grid--list .product-card__media { width: 190px; }
}


/* Product card polish -------------------------------------------------------

   Everything below is additive to the card rules above and deliberately touches
   properties those rules do not set, so neither can silently override the other.

   All motion is transform and opacity only. A catalogue page carries a hundred
   cards, so anything that lays out or paints on hover would be felt.           */

/* A designed ground rather than a flat grey. The artwork is `contain`-fitted, so
   this is visible on most cards and reads as a studio backdrop -- and because it
   paints before the image decodes, it doubles as the placeholder. */
.product-card__media {
    background:
        radial-gradient(120% 90% at 50% 0%, var(--surface-card), transparent 70%),
        linear-gradient(170deg, var(--surface-sunken), var(--surface-page));
}

/* Elevation and a warmer border on hover. Focus-within gets the same signal but
   no lift: cards jumping as a keyboard user tabs across a grid is disorienting. */
.product-card {
    transition: transform var(--duration-base) var(--ease-out),
                box-shadow var(--duration-base) var(--ease-out),
                border-color var(--duration-base) var(--ease-out);
}

.product-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-lg);
    border-color: var(--border-default);
}

.product-card:focus-within {
    box-shadow: var(--shadow-lg);
    border-color: var(--border-default);
}

/* Image ------------------------------------------------------------------- */

.product-card__img {
    transition: transform var(--duration-slow) var(--ease-out),
                opacity var(--duration-slow) var(--ease-out);
}

.product-card:hover .product-card__img,
.product-card:focus-within .product-card__img { transform: scale(1.06); }

/* The second rendition, when a product has one, sits exactly over the first and
   crossfades in. Both scale together, so the fade happens between two moving
   frames rather than one sliding behind a static one. A product with a single
   image simply has no such element and keeps the plain zoom. */
.product-card__img--alt {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    object-fit: contain;
    opacity: 0;
    pointer-events: none;
}

.product-card:hover .product-card__img--alt,
.product-card:focus-within .product-card__img--alt { opacity: 1; }

/* Badges ------------------------------------------------------------------ */

/* Scoped to the card: `.badge` is shared with admin tables, which must not move. */
.product-card__badges .badge {
    padding: 0.3em 0.6em;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    box-shadow: var(--shadow-xs);
}

/* The discount is the number that closes the sale, so it is the one badge
   allowed to be loud -- a size up, and a hue-matched glow so it reads as
   emitting rather than casting a shadow. */
.product-card__badges .badge--sale {
    font-size: var(--text-xs);
    box-shadow: 0 4px 12px rgba(232, 93, 44, 0.32);
}

/* Quick actions ----------------------------------------------------------- */

/* Hidden until the card is hovered or focused. Hidden with opacity rather than
   visibility or display, so the buttons stay in the accessibility tree and
   remain focusable -- which is what makes the :focus-within reveal work at all. */
.product-card__quick-actions {
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity var(--duration-base) var(--ease-out),
                transform var(--duration-base) var(--ease-out);
}

.product-card:hover .product-card__quick-actions,
.product-card:focus-within .product-card__quick-actions {
    opacity: 1;
    transform: translateY(0);
}

/* An already-saved product keeps its heart visible, so a customer scanning their
   own wishlist can see the state without hovering every card. */
.product-card__quick-actions:has(.product-card__wish.is-active) {
    opacity: 1;
    transform: none;
}

/* On touch there is no hover, so the reveal above never fires and the wishlist button is
   invisible -- and, being opacity 0 over the photo, not hit-testable either. A customer on a
   phone simply had no way to save a product from a listing.

   Shown outright on touch instead. The quick-view and compare buttons stay hidden there by
   their own hide-mobile class, so this is the heart alone rather than three buttons crowding
   every card. */
@media (hover: none) {
    .product-card__quick-actions {
        opacity: 1;
        transform: none;
    }

    /* And with no hover to reveal it, the button has to be reachable by thumb. */
    .product-card__wish { width: 44px; height: 44px; }
}

/* The base rules already set the hover scale and the saved colour; this only adds
   the background lift they do not touch, so neither declaration is left dead. */
.product-card__wish {
    transition: transform var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
}

.product-card__wish:hover { background: #fff; }

/* There is no hover on a touch screen, so the actions would never appear.
   A media query on the input, not on width: a touch laptop is still touch. */
@media (hover: none) {
    .product-card__quick-actions {
        opacity: 1;
        transform: none;
    }
}

/* Information ------------------------------------------------------------- */

/* Two lines maximum, so a long name cannot make one card taller than its row. */
.product-card__name {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

/* Pushes the price and the stock line to the floor of every card, so prices sit
   on one line across a row however many lines the name above them took. */
.product-card__price { margin-block-start: auto; }

/* Lining figures of equal width, so a column of prices reads as a column rather
   than as ragged text. */
.product-card__price .price,
.product-card__price .price--original { font-variant-numeric: tabular-nums; }

/* Reduced motion ---------------------------------------------------------- */

/* The global rule collapses durations but leaves the end state applied, so a
   card would still jump to its lifted, zoomed position instantly. These remove
   the movement itself and leave the elevation and the reveal, which carry the
   same information without motion. */
@media (prefers-reduced-motion: reduce) {
    .product-card:hover,
    .product-card:focus-within { transform: none; }

    .product-card:hover .product-card__img,
    .product-card:focus-within .product-card__img,
    .product-card:hover .product-card__img--alt,
    .product-card:focus-within .product-card__img--alt { transform: none; }

    .product-card__quick-actions { transform: none; }

    .product-card__wish:hover { transform: none; }
}


/* Section reveal ------------------------------------------------------------

   data-reveal is only ever set by the script, and only when the reader has not
   asked for reduced motion -- so this rule cannot hide anything that has no way
   to be shown again. */

[data-reveal] {
    opacity: 0;
    transform: translate3d(0, 22px, 0);
    transition: opacity 640ms var(--ease-out),
                transform 640ms var(--ease-out);
}

[data-reveal][data-revealed="true"] {
    opacity: 1;
    transform: none;
}


/* Promo cards ---------------------------------------------------------------

   A picture beside a coloured panel of copy. Both the panel colour and the copy
   colour come from the banner row, so a merchant sets them per promotion without
   a deployment; the fallbacks below keep a banner saved before those fields
   existed rendering correctly rather than blank.                              */

.promo-grid {
    display: grid;
    gap: var(--space-4);
    /* min(), not a bare value: a floor wider than the column on a small phone
       would force the track to that width and push the page sideways. */
    grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr));
}

.promo-card {
    position: relative;
    display: grid;
    /* The picture takes a fixed share so the copy panel keeps a readable measure
       however wide the card gets. */
    grid-template-columns: minmax(0, 44%) minmax(0, 1fr);
    min-height: 260px;
    overflow: hidden;
    border-radius: var(--radius-lg);
    color: var(--promo-ink, #fff);
    background: var(--promo-bg, var(--brand-secondary));
    transition: transform var(--duration-base) var(--ease-out),
                box-shadow var(--duration-base) var(--ease-out);
}

.promo-card:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-lg);
}

.promo-card:focus-visible {
    outline: 3px solid var(--brand-primary);
    outline-offset: 3px;
}

/* No artwork: the copy takes the whole card, lifted by a soft highlight so it
   does not read as a flat rectangle of colour. */
.promo-card--plain { grid-template-columns: minmax(0, 1fr); }

.promo-card--plain::before {
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(120% 90% at 78% 18%, rgba(255, 255, 255, 0.22), transparent 62%),
        radial-gradient(80% 70% at 10% 100%, rgba(0, 0, 0, 0.24), transparent 70%);
}

.promo-card__figure {
    position: relative;
    overflow: hidden;
    background: rgba(0, 0, 0, 0.14);
}

/* Blurred and over-scaled, so the contained picture never leaves a hard edge or
   an empty bar at the panel border. */
.promo-card__fill {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: blur(22px) saturate(1.2);
    transform: scale(1.25);
    opacity: 0.55;
}

/* `contain`, never `cover`: an uploaded photograph has to stay whole, whatever
   shape it arrived in. */
.promo-card__image {
    position: relative;
    width: 100%;
    height: 100%;
    object-fit: contain;
    object-position: center;
    transition: transform var(--duration-slow) var(--ease-out);
}

/* Vector artwork is decorative rather than a product, so it may fill the panel. */
.promo-card__figure--art .promo-card__image { object-fit: cover; }

.promo-card:hover .promo-card__image { transform: scale(1.04); }

.promo-card__body {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-5);
}

.promo-card__offer {
    display: inline-flex;
    align-items: center;
    padding: 0.3rem 0.75rem;
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: inherit;
    background: rgba(255, 255, 255, 0.18);
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: var(--radius-pill);
}

.promo-card__title {
    margin: 0;
    font-size: var(--text-xl);
    line-height: 1.2;
    color: inherit;
    /* Splits the heading across lines evenly rather than leaving a short tail. */
    text-wrap: balance;
}

.promo-card__text {
    margin: 0;
    font-size: var(--text-sm);
    color: inherit;
    opacity: 0.88;
    text-wrap: pretty;
}

.promo-card__cta {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-block-start: var(--space-2);
    padding-block-end: 2px;
    font-size: var(--text-sm);
    font-weight: 600;
    color: inherit;
    border-bottom: 1.5px solid rgba(255, 255, 255, 0.45);
}

.promo-card__arrow {
    transition: transform var(--duration-fast) var(--ease-out);
}

.promo-card:hover .promo-card__arrow { transform: translateX(3px); }

/* Below this the two columns leave the copy too narrow to read, so the picture
   moves above it and takes a landscape box of its own. */
@media (max-width: 460px) {
    .promo-card {
        grid-template-columns: minmax(0, 1fr);
        min-height: 0;
    }

    .promo-card__figure { aspect-ratio: 16 / 9; }
}

@media (prefers-reduced-motion: reduce) {
    .promo-card,
    .promo-card__image,
    .promo-card__arrow { transition: none; }

    .promo-card:hover,
    .promo-card:hover .promo-card__image,
    .promo-card:hover .promo-card__arrow { transform: none; }
}

/* Section heading ---------------------------------------------------------- */

/* The existing .section-head is a two-item flex row. A heading that also carries
   an eyebrow and a subtitle needs those stacked in the first item, which is what
   the wrapping div in the markup provides. */
.section-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-1);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--brand-secondary);
}

.section-subtitle {
    margin: var(--space-1) 0 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
}



/* Trust badges --------------------------------------------------------------

   A deliberately separate block from .trust-item, which the product page still
   uses in its --prose form. Repurposing those classes would have changed a page
   this section has nothing to do with.                                        */

/* Two, three and six all divide six evenly, so no row is ever left short. */
.trust-badges {
    display: grid;
    gap: var(--space-3);
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (min-width: 700px) {
    .trust-badges { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 1200px) {
    .trust-badges { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

/* An explicit flex column: the children are inline spans and an icon, which
   would otherwise run together on one line. */
.trust-badge {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    height: 100%;
    padding: var(--space-5) var(--space-3);
    text-align: center;
    background: var(--surface-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    transition: border-color var(--duration-base) var(--ease-out),
                box-shadow var(--duration-base) var(--ease-out),
                transform var(--duration-base) var(--ease-out);
}

.trust-badge:hover {
    border-color: var(--brand-secondary-light);
    box-shadow: var(--shadow-sm);
    transform: translateY(-2px);
}

.trust-badge__icon {
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    margin-bottom: var(--space-1);
    color: var(--brand-secondary);
    background: var(--brand-secondary-soft);
    border-radius: 50%;
}

.trust-badge__icon svg {
    width: 22px;
    height: 22px;
    fill: currentColor;
}

.trust-badge__title {
    font-size: var(--text-sm);
    font-weight: 700;
    line-height: 1.3;
    color: var(--text-primary);
    /* Splits a two-word title evenly rather than leaving one word alone. */
    text-wrap: balance;
}

.trust-badge__text {
    font-size: var(--text-xs);
    line-height: 1.6;
    color: var(--text-secondary);
    text-wrap: pretty;
}

@media (prefers-reduced-motion: reduce) {
    .trust-badge { transition: none; }
    .trust-badge:hover { transform: none; }
}

/* Centred section heading -------------------------------------------------- */

/* .section-head is a two-item flex row for headings that carry a "view all" on
   the right. Centring collapses it to one centred column instead. */
.section-head--center {
    justify-content: center;
    text-align: center;
}

.section-head--center .section-subtitle { margin-inline: auto; }

/* A short brand rule above the title, replacing the vertical bar .section-title
   normally carries on its left.

   Every property that bar sets has to be undone, not just overridden: it is
   absolutely positioned at left:0 and vertically centred, so a bare width and
   height here inherited all of that and drew the rule beside the heading
   instead of above it. The left padding that made room for it goes too. */
.section-title--ruled { padding-left: 0; }

.section-title--ruled::before {
    content: "";
    position: static;
    display: block;
    transform: none;
    width: 44px;
    height: 3px;
    margin-bottom: var(--space-3);
    background: linear-gradient(90deg, var(--brand-secondary), var(--brand-primary));
    border-radius: var(--radius-pill);
}

.section-head--center .section-title--ruled::before { margin-inline: auto; }

/* Tighter than a full section: this one sits between two others and does not
   need the same breathing room above and below. */
.section--tight { padding-block: var(--space-8); }



/* Testimonials --------------------------------------------------------------

   A quieter ground than the page, so the section reads as a panel of social
   proof rather than another row of cards.                                    */

.testimonials {
    background: var(--surface-sunken);
    border-radius: var(--radius-xl);
    padding-inline: var(--space-5);
}

.testimonial-grid {
    display: grid;
    gap: var(--space-4);
    grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 700px) {
    .testimonial-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

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

/* An author `display` beats the browser's built-in [hidden] rule, so a card the
   pager marks hidden would keep painting. Restoring it here is what makes paging
   work at all -- and it has to come before .testimonial sets its own display. */
.testimonial[hidden] { display: none; }

.testimonial {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    height: 100%;
    margin: 0;
    padding: var(--space-5);
    background: var(--surface-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    transition: box-shadow var(--duration-base) var(--ease-out),
                transform var(--duration-base) var(--ease-out);
}

.testimonial:hover {
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
}

.testimonial__head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.testimonial__stars {
    color: var(--brand-primary);
    font-size: var(--text-base);
    letter-spacing: 1px;
    line-height: 1;
}

.testimonial__quote {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.7;
    color: var(--text-primary);
    text-wrap: pretty;
}

/* Pushes the author to the floor of the card, so a short quote and a long one
   still line their bylines up across a row. */
.testimonial__author {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-top: auto;
    padding-top: var(--space-4);
    border-top: 1px solid var(--border-subtle);
}

.testimonial__avatar {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--brand-secondary-soft);
    color: var(--brand-secondary);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
}

.testimonial__name {
    display: block;
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--text-primary);
}

.testimonial__meta {
    display: block;
    font-size: var(--text-xs);
    color: var(--text-secondary);
}

/* Rail paging -------------------------------------------------------------- */

/* Both the arrows and the numbered pages are revealed by the script, so a page
   without JavaScript shows every card and no controls that would do nothing. */
/* An author `display` beats the browser built-in [hidden] rule, so a card the pager marks
   hidden keeps painting and every page shows the whole rail. .product-card sets
   display: flex, which is exactly that case -- the same trap .testimonial[hidden] below
   exists to solve. Written against the rail rather than the card so any future item type
   dropped into a rail is covered without remembering this. */
[data-rail] > [hidden] { display: none !important; }

/* Product rails ------------------------------------------------------------

   A homepage rail holds thirty products and shows ten. How the other twenty are reached
   differs by device, and the markup is identical for both -- only this stylesheet and one
   media query in the script decide which behaviour applies.

   Phone: a horizontal swipe. The gesture is native to a touch screen, costs no vertical
   space, and is what every shop a customer here already uses does. Every card stays in the
   DOM and reachable; nothing is hidden.

   Desktop: the same rail becomes a grid again and the script pages it with arrows and dots,
   because there is no swipe on a mouse and a wide grid shows more at once than a scroller
   would.                                                                                */

@media (max-width: 767px) {
    /* Every rail swipes on a phone, not just the product rows.
       Written against [data-rail] rather than .product-rail so a section built on the same
       machinery cannot end up with the other behaviour by omission -- which is exactly how
       the reviews rail came to page on a phone while the product rows beside it swiped. */
    [data-rail] {
        display: grid;
        grid-auto-flow: column;
        grid-template-columns: none;

        overflow-x: auto;
        overscroll-behavior-inline: contain;

        /* Snap to card starts so a swipe settles on a card rather than mid-card. */
        scroll-snap-type: x mandatory;

        /* The rail bleeds to the screen edges so a card can scroll off cleanly rather than
           stopping short of the margin, and the padding puts the gutter back inside it.
           scroll-padding matches, or a snap would land a card hard against the edge with no
           gutter -- which reads as a clipped card rather than a scrolled one. */
        padding-inline: var(--space-4);
        margin-inline: calc(var(--space-4) * -1);
        scroll-padding-inline: var(--space-4);
        padding-bottom: var(--space-2);

        scrollbar-width: none;
    }

    [data-rail]::-webkit-scrollbar { display: none; }

    [data-rail] > * { scroll-snap-align: start; }

    /* Two and a bit cards in view: the part-visible third is the affordance that says the
       row scrolls. A whole number of cards looks like a finished grid and gets no swipe. */
    .product-rail { grid-auto-columns: minmax(44%, 1fr); }

    /* A review is a paragraph of prose, not a price and a button, so it needs most of the
       width to stay readable -- but still not all of it, or nothing signals the swipe. */
    .testimonial-grid { grid-auto-columns: minmax(82%, 1fr); }

    /* The journal is three articles stacked, each a wide image over a title and excerpt --
       1078px of scrolling on a phone for three items. Swiping it recovers a screen without
       hiding anything.

       No pager attached: three cards is under one page, so dots would render a single dot
       and the arrows would be furniture. The swipe is the whole control here. */
    .blog-grid--rail {
        display: grid;
        grid-auto-flow: column;
        grid-auto-columns: minmax(78%, 1fr);
        grid-template-columns: none;

        overflow-x: auto;
        overscroll-behavior-inline: contain;
        scroll-snap-type: x mandatory;

        padding-inline: var(--space-4);
        margin-inline: calc(var(--space-4) * -1);
        scroll-padding-inline: var(--space-4);
        padding-bottom: var(--space-2);

        scrollbar-width: none;
    }

    .blog-grid--rail::-webkit-scrollbar { display: none; }
    .blog-grid--rail > * { scroll-snap-align: start; }

    /* On a phone a hidden card is one the swipe cannot reach, so nothing is hidden. This
       has to beat the [data-rail] > [hidden] rule the desktop pager relies on. */
    [data-rail] > [hidden] { display: flex !important; }

    /* The arrows are a pointer affordance. On a phone the swipe is the control, and a pair
       of buttons beside the heading only competes with it for room. */
    .section-head__actions .rail-controls,
    .testimonials .rail-controls { display: none; }
}

/* The heading row carries the paging controls and the "view all" link together, so they
   read as one group rather than drifting to opposite edges. */
.section-head__actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-shrink: 0;
}

@media (max-width: 480px) {
    /* Narrower phones: a card at 44% leaves the price and the button cramped. */
    .product-rail { grid-auto-columns: minmax(60%, 1fr); }
}

.rail-controls { display: flex; gap: var(--space-2); }

.rail-control {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    background: var(--surface-card);
    color: var(--text-primary);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out);
}

.rail-control:hover:not(:disabled) {
    background: var(--surface-sunken);
    border-color: var(--brand-secondary-light);
}

.rail-control:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.rail-pages {
    display: flex;
    justify-content: center;
    gap: var(--space-2);
    margin-top: var(--space-5);
}

.rail-page {
    min-width: 40px;
    height: 40px;
    padding-inline: var(--space-2);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    background: var(--surface-card);
    color: var(--text-primary);
    font-size: var(--text-sm);
    font-weight: 600;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}

.rail-page:hover { border-color: var(--brand-secondary-light); }

.rail-page[aria-current="page"] {
    background: var(--brand-secondary);
    border-color: var(--brand-secondary);
    color: var(--text-inverse);
}

@media (prefers-reduced-motion: reduce) {
    .testimonial { transition: none; }
    .testimonial:hover { transform: none; }
}



/* FAQ accordion -------------------------------------------------------------

   Built on native <details>, so it opens with no JavaScript and the browser
   supplies the expanded state, the keyboard handling and the screen-reader
   semantics that a div-and-button accordion has to reimplement.             */

.faq-section {
    background: var(--surface-sunken);
    border-radius: var(--radius-xl);
    padding-inline: var(--space-5);
}

/* One card holding the whole list, with the questions divided by hairlines --
   the same shape a settings list has, and calmer than six separate cards. */
.faq-list {
    max-width: 62rem;
    margin-inline: auto;
    background: var(--surface-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.faq-item + .faq-item { border-top: 1px solid var(--border-subtle); }

.faq-item__question {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--text-primary);
    cursor: pointer;
    /* Removes the default disclosure triangle in every engine, so the chevron
       below is the only marker. */
    list-style: none;
    transition: background var(--duration-fast) var(--ease-out);
}

.faq-item__question::-webkit-details-marker { display: none; }

.faq-item__question:hover { background: var(--surface-sunken); }

.faq-item__question:focus-visible {
    outline: 3px solid var(--brand-primary);
    outline-offset: -3px;
}

.faq-item__marker {
    flex-shrink: 0;
    font-size: 1.1rem;
    line-height: 1;
    color: var(--text-secondary);
    transition: transform var(--duration-base) var(--ease-out);
}

.faq-item[open] .faq-item__marker { transform: rotate(180deg); }

.faq-item__answer {
    padding: 0 var(--space-5) var(--space-4);
    color: var(--text-secondary);
    font-size: var(--text-sm);
    line-height: 1.7;
}

.faq-item__answer p {
    margin: 0;
    /* Long answers stay readable rather than running the full card width. */
    max-width: 68ch;
    text-wrap: pretty;
}

.faq-more {
    margin: var(--space-5) 0 0;
    text-align: center;
}

@media (prefers-reduced-motion: reduce) {
    .faq-item__question,
    .faq-item__marker { transition: none; }

    .faq-item[open] .faq-item__marker { transform: none; }
}



/* Newsletter ---------------------------------------------------------------- */

.newsletter {
    position: relative;
    overflow: hidden;
    padding: var(--space-12) var(--space-5);
    border-radius: var(--radius-xl);
    color: var(--text-inverse);
    background: linear-gradient(135deg, var(--brand-secondary-dark), var(--brand-secondary));
}

/* A soft bloom off the corner, so a large panel of one colour has some depth to it
   rather than reading as a flat rectangle. */
.newsletter::after {
    content: "";
    position: absolute;
    inset-block-start: -40%;
    inset-inline-end: -12%;
    width: 46%;
    aspect-ratio: 1;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.09);
    pointer-events: none;
}

.newsletter__inner {
    position: relative;
    max-width: 44rem;
    margin-inline: auto;
    text-align: center;
}

.newsletter__icon {
    display: inline-grid;
    place-items: center;
    width: 56px;
    height: 56px;
    margin-bottom: var(--space-4);
    font-size: 1.6rem;
    line-height: 1;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.14);
    border: 1px solid rgba(255, 255, 255, 0.22);
}

.newsletter__title {
    margin: 0 0 var(--space-2);
    font-size: var(--text-2xl);
    color: inherit;
    text-wrap: balance;
}

/* .section-title carries a positioned bar on its left; this heading is centred and
   needs none, so the padding it reserves is removed here rather than inherited. */
.newsletter__title::before { content: none; }

.newsletter__text {
    margin: 0 auto var(--space-5);
    max-width: 52ch;
    font-size: var(--text-base);
    /* 0.88 rather than a lighter tint: measured against the darker end of the gradient
       this clears the 4.5:1 minimum, and a fainter one did not. */
    color: rgba(255, 255, 255, 0.88);
    text-wrap: pretty;
}

.newsletter__form {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
}

.newsletter__input {
    flex: 1 1 20rem;
    min-width: 0;
    max-width: 30rem;
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-base);
    color: var(--text-primary);
    background: var(--surface-card);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
}

.newsletter__input::placeholder { color: var(--text-muted); }

.newsletter__input:focus-visible {
    outline: 3px solid var(--brand-primary);
    outline-offset: 2px;
}

.newsletter__button {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-5);
    font-size: var(--text-base);
    font-weight: 700;
    color: var(--brand-secondary-dark);
    background: var(--surface-card);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--duration-fast) var(--ease-out),
                transform var(--duration-fast) var(--ease-out);
}

.newsletter__button:hover { background: #fff; }

.newsletter__button:focus-visible {
    outline: 3px solid var(--brand-primary);
    outline-offset: 2px;
}

.newsletter__arrow {
    transition: transform var(--duration-fast) var(--ease-out);
}

.newsletter__button:hover .newsletter__arrow { transform: translateX(3px); }

.newsletter__note {
    margin: 0;
    font-size: var(--text-xs);
    color: rgba(255, 255, 255, 0.88);
}

/* On a phone the field and the button each take the full width: side by side they
   leave the field too narrow to read a typed address in. */
@media (max-width: 560px) {
    .newsletter { padding: var(--space-10) var(--space-4); }

    .newsletter__input,
    .newsletter__button {
        flex: 1 1 100%;
        max-width: none;
    }

    .newsletter__button { justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
    .newsletter__button,
    .newsletter__arrow { transition: none; }

    .newsletter__button:hover .newsletter__arrow { transform: none; }
}


/* ==========================================================================
   Breadcrumbs
   ========================================================================== */

.breadcrumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    list-style: none;
    margin: 0 0 var(--space-4);
    padding: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.breadcrumbs li { display: flex; align-items: center; gap: var(--space-2); }
.breadcrumbs li:not(:last-child)::after { content: '›'; color: var(--border-strong); }
.breadcrumbs a { color: var(--text-secondary); }
.breadcrumbs a:hover { color: var(--brand-primary); }
.breadcrumbs [aria-current="page"] { color: var(--text-primary); font-weight: 500; }

/* ==========================================================================
   Cart and checkout
   ========================================================================== */

.checkout-layout { display: grid; gap: var(--space-6); align-items: start; }

@media (min-width: 1024px) {
    .checkout-layout { grid-template-columns: minmax(0, 1fr) 380px; }
}

/* The summary follows the customer down a long form. */
@media (min-width: 1024px) {
    .order-summary { position: sticky; top: calc(var(--header-height) + var(--space-4)); }
}

.cart-line {
    display: grid;
    grid-template-columns: 84px minmax(0, 1fr);
    gap: var(--space-3);
    padding: var(--space-4) 0;
    border-bottom: 1px solid var(--border-subtle);
}

@media (min-width: 640px) {
    .cart-line { grid-template-columns: 96px minmax(0, 1fr) auto; align-items: center; }
}

.cart-line__img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-md);
    background: var(--surface-sunken);
}

.summary-row {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    padding-block: var(--space-2);
    font-size: var(--text-sm);
}

.summary-row--total {
    padding-top: var(--space-3);
    margin-top: var(--space-2);
    border-top: 2px solid var(--border-default);
    font-size: var(--text-lg);
    font-weight: 700;
}

.summary-row--discount { color: var(--success); }

/* Payment method selection ------------------------------------------------ */

.payment-methods { display: grid; gap: var(--space-3); }

.payment-method {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4);
    border: 2px solid var(--border-default);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: all var(--duration-fast) var(--ease-out);
}

.payment-method:hover { border-color: var(--brand-primary-light); }

.payment-method:has(input:checked) {
    border-color: var(--brand-primary);
    background: var(--brand-primary-soft);
}

.payment-method__logo { height: 26px; width: auto; margin-inline-start: auto; }

/* ==========================================================================
   Order tracking timeline
   ========================================================================== */

.timeline { position: relative; padding-inline-start: var(--space-8); }

/* The rail behind the step markers. */
.timeline::before {
    content: '';
    position: absolute;
    inset-inline-start: 11px;
    top: 8px; bottom: 8px;
    width: 2px;
    background: var(--border-default);
}

.timeline__step { position: relative; padding-bottom: var(--space-6); }
.timeline__step:last-child { padding-bottom: 0; }

.timeline__dot {
    position: absolute;
    inset-inline-start: calc(-1 * var(--space-8) + 4px);
    top: 2px;
    width: 18px; height: 18px;
    border-radius: var(--radius-pill);
    background: var(--surface-card);
    border: 2px solid var(--border-strong);
    display: grid;
    place-items: center;
    font-size: 10px;
}

.timeline__step.is-reached .timeline__dot {
    background: var(--brand-secondary);
    border-color: var(--brand-secondary);
    color: var(--text-inverse);
}

.timeline__step.is-current .timeline__dot {
    background: var(--brand-primary);
    border-color: var(--brand-primary);
    color: var(--text-inverse);
    box-shadow: 0 0 0 4px var(--brand-primary-soft);
}

.timeline__step.is-exception .timeline__dot {
    background: var(--danger);
    border-color: var(--danger);
    color: var(--text-inverse);
}

.timeline__title { font-weight: 600; font-size: var(--text-sm); margin-bottom: 2px; }
.timeline__step:not(.is-reached) .timeline__title { color: var(--text-muted); }
.timeline__meta { font-size: var(--text-xs); color: var(--text-muted); }

/* ==========================================================================
   Footer
   ========================================================================== */

/* Text opacities here are not free choices: white at 0.78 over the lighter gradient stop
   measures 4.42:1 and white at 0.65 measures 3.60:1, both under the 4.5:1 minimum. 0.85
   gives 4.91:1, which clears it at the worst point of the gradient. */
.site-footer {
    margin-top: var(--space-16);
    /* A slight vertical shift off the flat brand green: the footer reads as a deliberate
       surface rather than a block of colour, and the darker foot separates the legal line
       from the navigation above it without needing another border. */
    background: linear-gradient(180deg, var(--brand-secondary) 0%, var(--brand-secondary-dark) 100%);
    color: rgba(255, 255, 255, 0.85);
    padding-block: var(--space-12) var(--space-5);
    /* A hairline of brand orange ties the footer back to the header and buttons. */
    border-top: 3px solid var(--brand-primary);
}

.footer-grid {
    display: grid;
    gap: var(--space-8);
    margin-bottom: var(--space-8);
}

/* Mobile-first: one column, and the brand block reads first. Columns arrive only when
   there is room for them to hold a readable measure. */
@media (min-width: 600px) {
    .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .footer-brand { grid-column: 1 / -1; }
}

/* Phone -------------------------------------------------------------------
   The footer carries four headings and seventeen links. Stacked one per row they ran to
   several screens of scrolling before the legal line -- a wall of green a visitor has to
   swipe past rather than a place they can find anything in.

   Nothing is removed or hidden behind a disclosure: a link a customer cannot see is a link
   they will not use, and the returns policy and the phone number are exactly what someone
   scrolls down here to find. The height comes out of the layout instead.                */
@media (max-width: 599px) {
    .site-footer {
        margin-top: var(--space-10);
        padding-block: var(--space-8) var(--space-4);
    }

    /* Sections need to read as separate without four full gaps between them. */
    .footer-grid {
        gap: var(--space-6);
        margin-bottom: var(--space-6);
    }

    /* A hairline between blocks does the work the old spacing did, in a fraction of the
       room, and makes the four groups legible as groups while scrolling past. */
    .footer-grid > * + * {
        padding-top: var(--space-6);
        border-top: 1px solid rgba(255, 255, 255, 0.12);
    }

    .site-footer h4 { margin-bottom: var(--space-3); }
}

@media (min-width: 1000px) {
    .footer-grid {
        /* The brand column carries prose and the contact column carries the form, so both
           get more room than the two link lists between them. */
        grid-template-columns: 1.6fr 1fr 1fr 1.4fr;
        gap: var(--space-10);
    }
    .footer-brand { grid-column: auto; }
}

.site-footer h4 {
    color: var(--text-inverse);
    font-size: var(--text-sm);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-bottom: var(--space-4);
}

.site-footer a {
    color: rgba(255, 255, 255, 0.85);
    font-size: var(--text-sm);
    transition: color var(--duration-fast) var(--ease-out);
}

.site-footer a:hover { color: var(--text-inverse); }

/* Brand block ------------------------------------------------------------- */

.footer-logo {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    color: var(--text-inverse);
    font-size: var(--text-lg);
    margin-bottom: var(--space-4);
}

.footer-logo:hover { color: var(--text-inverse); }

.footer-logo__mark {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-md);
    background: rgba(255, 255, 255, 0.12);
    font-size: 1.25rem;
    flex-shrink: 0;
}

.footer-logo__tagline {
    display: block;
    font-size: var(--text-xs);
    font-weight: 500;
    color: rgba(255, 255, 255, 0.85);
}

.footer-about {
    font-size: var(--text-sm);
    line-height: 1.7;
    max-width: 42ch;
    margin-bottom: var(--space-5);
}

/* Trust list -------------------------------------------------------------- */

.footer-trust {
    list-style: none;
    margin: 0 0 var(--space-5);
    padding: 0;
    display: grid;
    gap: var(--space-2);
}

.footer-trust li {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
}

.footer-trust svg {
    width: 16px;
    height: 16px;
    fill: currentColor;
    color: var(--brand-primary);
    flex-shrink: 0;
}

/* Social icons ------------------------------------------------------------ */

.footer-social { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.footer-social__link {
    display: grid;
    place-items: center;
    /* 40px: comfortably past the minimum comfortable touch target on a phone, where these
       sit close together. */
    width: 40px;
    height: 40px;
    border-radius: var(--radius-md);
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.14);
    transition: background var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                transform var(--duration-fast) var(--ease-out);
}

.footer-social__link svg {
    width: 18px;
    height: 18px;
    fill: currentColor;
    color: rgba(255, 255, 255, 0.9);
}

.footer-social__link:hover {
    background: var(--brand-primary);
    border-color: var(--brand-primary);
}

.footer-social__link:hover svg { color: var(--text-inverse); }

@media (prefers-reduced-motion: no-preference) {
    .footer-social__link:hover { transform: translateY(-2px); }
}

/* Link lists -------------------------------------------------------------- */

.footer-links { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }

/* A second heading inside one column needs the space a new column would have given it. */
.footer-column__next { margin-top: var(--space-6); }

/* Phone: two columns.
   These are short labels -- "Home", "Offers", "Track order" -- not prose, so a full row
   each wastes most of the width and doubles the height for nothing. Two columns halve the
   scroll while every link keeps a comfortable tap target.

   auto-fit with a minimum, rather than a flat repeat(2), so a long Bangla label such as
   "ক্যাটালগ ডাউনলোড (PDF)" drops to a single full-width column instead of wrapping into a
   cramped ragged stack. */
@media (max-width: 599px) {
    .footer-links {
        /* 8rem, not 9: at 320px -- still a common screen in Bangladesh -- a 9rem minimum
           leaves no room for two tracks plus the gap, and the list silently collapsed back
           to one column on exactly the device that needed the saving most. */
        grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
        column-gap: var(--space-4);

        /* No row gap. The tap padding below already separates the rows, and keeping both
           spaced them twice over -- which is why the first attempt saved almost no height
           despite halving the number of rows. */
        row-gap: 0;
    }

    /* The whole row is the target, not just the words. Padding rather than a taller line
       so the text keeps its rhythm while the touchable area reaches 44px. */
    .footer-links a {
        display: block;
        padding-block: var(--space-3);
        line-height: 1.25;
    }

    .footer-column__next { margin-top: var(--space-4); }
}

/* A hover that only changes colour is easy to miss against a dark ground, so the link
   also steps toward the reader. */
.footer-links a {
    display: inline-block;
    transition: color var(--duration-fast) var(--ease-out),
                transform var(--duration-fast) var(--ease-out);
}

@media (prefers-reduced-motion: no-preference) {
    .footer-links a:hover { transform: translateX(3px); }
}

/* Contact list ------------------------------------------------------------ */

.footer-contact {
    list-style: none;
    margin: 0 0 var(--space-6);
    padding: 0;
    display: grid;
    gap: var(--space-3);
}

.footer-contact a,
.footer-contact > li > span {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    font-size: var(--text-sm);
    line-height: 1.5;
}

/* Deliberately NOT two columns: a phone number, an email and a street address each need the
   full measure, and splitting them would wrap every one. They gain the tap padding instead --
   these are the rows most likely to be pressed rather than read. */
@media (max-width: 599px) {
    .footer-contact { gap: 0; margin-bottom: var(--space-5); }

    /* Same reasoning as the link lists: the padding is the separation, so a gap on top of
       it only adds height. The address is a span rather than a link and needs no target,
       but it keeps the padding so the rows stay evenly spaced. */
    .footer-contact a,
    .footer-contact > li > span {
        padding-block: var(--space-2);
        line-height: 1.3;
        min-height: 44px;
        align-items: center;
    }

    /* The icon was nudged down two pixels to sit on the baseline of a taller line box.
       Centred rows do not need that, and it read as misaligned. */
    .footer-contact .footer-icon { margin-top: 0; }
}

.footer-icon {
    width: 16px;
    height: 16px;
    fill: currentColor;
    color: var(--brand-primary);
    flex-shrink: 0;
    /* Nudged to sit on the text baseline rather than the line box top. */
    margin-top: 2px;
}

/* Newsletter -------------------------------------------------------------- */

.footer-newsletter__heading { margin-top: var(--space-6); }

.footer-newsletter__hint {
    font-size: var(--text-xs);
    line-height: 1.6;
    margin-bottom: var(--space-3);
    color: rgba(255, 255, 255, 0.85);
}

.footer-newsletter { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.footer-newsletter input {
    flex: 1 1 11rem;
    min-width: 0;
    padding: var(--space-3);
    font-size: var(--text-sm);
    color: var(--text-inverse);
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: var(--radius-md);
}

.footer-newsletter input::placeholder { color: rgba(255, 255, 255, 0.75); }

.footer-newsletter input:focus-visible {
    outline: 2px solid var(--brand-primary);
    outline-offset: 1px;
    background: rgba(255, 255, 255, 0.16);
}

.footer-newsletter button {
    padding: var(--space-3) var(--space-5);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-inverse);
    background: var(--brand-primary);
    border: none;
    border-radius: var(--radius-md);
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--duration-fast) var(--ease-out);
}

.footer-newsletter button:hover { background: var(--brand-primary-dark); }

/* Payments and legal line ------------------------------------------------- */

/* The payment chips share the legal bar rather than sitting on a rule of their own.
   Two bars left an empty band across the middle of the footer, because neither row had
   enough content to justify its own full-width line. */
.footer-payments {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
}

.footer-payments__label {
    font-size: var(--text-xs);
    color: rgba(255, 255, 255, 0.85);
}

.payment-logos { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

.payment-logos img,
.payment-logos span {
    height: 28px;
    padding-inline: var(--space-3);
    background: rgba(255, 255, 255, 0.95);
    border-radius: var(--radius-sm);
    display: inline-flex;
    align-items: center;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--brand-secondary-dark);
}

.footer-bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3) var(--space-5);
    margin-top: var(--space-6);
    padding-top: var(--space-5);
    border-top: 1px solid rgba(255, 255, 255, 0.14);
    font-size: var(--text-xs);
    color: rgba(255, 255, 255, 0.85);
}

/* The chips take the slack so the copyright and the credit stay pinned to the two
   edges; without this the three parts drift apart at wide viewports. */
.footer-bottom > .footer-payments {
    flex: 1 1 auto;
    justify-content: center;
}

.footer-credit a {
    font-size: inherit;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.9);
}

.footer-credit a:hover { color: var(--brand-primary); }

/* Below roughly a laptop the three parts cannot share one line without crowding, so they
   stack and centre -- ragged left edges read as a mistake rather than a layout. */
@media (max-width: 900px) {
    .footer-bottom {
        flex-direction: column;
        justify-content: center;
        text-align: center;
    }

    .footer-bottom > .footer-payments { flex: 0 1 auto; }
}

/* Phone: the legal bar is the last thing before the end of the page, so it should settle
   rather than sprawl. Five chips at desktop size wrapped to two rows and stranded the
   longest label -- "ক্যাশ অন ডেলিভারি" -- alone on a line of its own, which read as a
   mistake. Smaller chips fit the row. */
@media (max-width: 599px) {
    .footer-bottom {
        gap: var(--space-3);
        margin-top: var(--space-5);
        padding-top: var(--space-4);
    }

    .payment-logos { justify-content: center; gap: var(--space-1); }

    .payment-logos img,
    .payment-logos span {
        height: 24px;
        padding-inline: var(--space-2);
        font-size: 0.6875rem;
    }
}

/* ==========================================================================
   Rating stars
   ========================================================================== */

.rating-bar {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: var(--space-3);
    font-size: var(--text-sm);
    margin-bottom: var(--space-2);
}

.rating-bar__track {
    height: 8px;
    background: var(--surface-sunken);
    border-radius: var(--radius-pill);
    overflow: hidden;
}

.rating-bar__fill {
    height: 100%;
    background: #F59E0B;
    border-radius: var(--radius-pill);
    /* Animates from zero when the section scrolls into view. */
    transition: width var(--duration-slow) var(--ease-out);
}

.review {
    padding: var(--space-4) 0;
    border-bottom: 1px solid var(--border-subtle);
}

.review__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
}

.review__author { font-weight: 600; font-size: var(--text-sm); }

/* A category suggestion is a different kind of result from a product, so it is tinted to
   separate the two at a glance rather than reading as a product with an odd price. */
.suggestion--category .suggestion__price {
    color: var(--brand-secondary);
    font-weight: 600;
}

/* ==========================================================================
   Campaigns and flash sales

   A campaign borrows the standard section layout and changes only its accent,
   so a store running six campaigns still reads as one site rather than six.
   The accent is supplied per campaign as --campaign-accent and falls back to
   the brand colour when the admin has not chosen one.
   ========================================================================== */

.campaign {
    --campaign-accent: var(--brand-primary);
}

.campaign .section-title::before {
    background: var(--campaign-accent);
}

.campaign__head {
    flex-wrap: wrap;
    align-items: center;
}

.section-subtitle {
    margin: var(--space-1) 0 0 var(--space-4);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* Tabular figures keep the clock from jittering as the digits change. */
.campaign__countdown {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--campaign-accent);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--campaign-accent) 8%, transparent);
}

.campaign__countdown-label {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.campaign__countdown-value {
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--campaign-accent);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

@media (max-width: 640px) {
    .campaign__head {
        gap: var(--space-3);
    }

    .campaign__countdown {
        width: 100%;
        justify-content: center;
    }
}

/* The magnified image needs a transition to settle rather than snap, and must not
   escape the frame. Zoom is engaged from script only on hover-capable pointers. */
.gallery__main img {
    transition: transform 0.2s ease-out;
    will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
    .gallery__main img { transition: none; }
}

/* ==========================================================================
   Support conversation

   Staff replies are tinted and inset so the customer can tell the two sides
   apart at a glance, without relying on the author name alone.
   ========================================================================== */

.ticket-message {
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--surface-card);
}

.ticket-message--staff {
    border-color: color-mix(in srgb, var(--brand-secondary) 40%, var(--border-subtle));
    background: color-mix(in srgb, var(--brand-secondary) 6%, var(--surface-card));
}

@media (min-width: 640px) {
    .ticket-message--staff { margin-left: var(--space-6); }
}

/* ==========================================================================
   Sitewide announcement bar

   Sits above the header on every page. Uses the secondary brand colour rather
   than the primary, so it reads as information and never competes with the
   page's main call to action.
   ========================================================================== */

.announcement {
    background: var(--brand-secondary);
    color: var(--text-on);
    font-size: var(--text-sm);
}

.announcement__inner {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding-block: var(--space-2);
    text-align: center;
}

.announcement__detail { opacity: 0.9; }

.announcement__link {
    color: inherit;
    text-decoration: underline;
    font-weight: 600;
}

/* Responsive 16:9 video frame. aspect-ratio rather than the padding-top trick, which is
   supported everywhere the rest of this stylesheet already assumes grid and custom properties. */
.video-embed {
    position: relative;
    width: 100%;
    max-width: 800px;
    aspect-ratio: 16 / 9;
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--surface-2, #000);
}

.video-embed iframe,
.video-embed video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* An uploaded clip is whatever shape the merchant filmed it in; contain letterboxes it
   inside the 16:9 frame rather than cropping the subject out of a portrait video. */
.video-embed video { object-fit: contain; background: #000; }

/* ==========================================================================
   Blog
   ========================================================================== */

/* Article list beside a sidebar; the sidebar drops below the list on narrow screens
   rather than squeezing both into columns too thin to read. */
.blog-layout {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
    margin-bottom: var(--space-12);
}

@media (min-width: 900px) {
    .blog-layout { grid-template-columns: minmax(0, 1fr) 280px; }
}

.blog-grid {
    display: grid;
    /* auto-fit, not auto-fill: with three articles in a four-track row auto-fill keeps
       the empty fourth track and leaves a card-shaped hole on the right. auto-fit
       collapses it so the cards share the width.

       min() guards the floor -- a 280px track inside a narrower column would force the
       grid wider than the page and scroll it sideways. */
    grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
    gap: var(--space-6);
}

.blog-card {
    display: flex;
    flex-direction: column;
    background: var(--surface-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    overflow: hidden;
    transition: box-shadow var(--duration-base) var(--ease-out),
                transform var(--duration-base) var(--ease-out);
}

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

/* Fixed ratio so a grid of cards lines up even when the images do not match. */
.blog-card__media {
    display: block;
    aspect-ratio: 16 / 9;
    background: var(--brand-primary-soft);
    overflow: hidden;
}

.blog-card__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Stands in for a featured image. It carries the article's own title over a brand
   tint and a soft bloom, so a journal published without photography still reads as
   designed rather than as a missing asset. */
.blog-card__placeholder {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--space-1);
    height: 100%;
    padding: var(--space-5);
    overflow: hidden;
    color: var(--text-inverse);
    background: linear-gradient(140deg, var(--brand-secondary), var(--brand-secondary-dark));
}

/* The bloom sits behind the words and is what keeps a flat panel from reading as a
   coloured rectangle. */
.blog-card__placeholder::after {
    content: "";
    position: absolute;
    inset-block-start: -30%;
    inset-inline-end: -18%;
    width: 68%;
    aspect-ratio: 1;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.14);
}

/* Four tints, chosen per category so a row of cards is varied but a given section
   always looks the same. */
.blog-card__placeholder--0 {
    background: linear-gradient(140deg, var(--brand-secondary), var(--brand-secondary-dark));
}

.blog-card__placeholder--1 {
    background: linear-gradient(140deg, var(--brand-primary), var(--brand-primary-dark));
}

.blog-card__placeholder--2 {
    background: linear-gradient(140deg, #3E6B8A, #24455C);
}

.blog-card__placeholder--3 {
    background: linear-gradient(140deg, #6A4B8A, #43305C);
}

.blog-card__placeholder-title {
    position: relative;
    font-size: var(--text-lg);
    font-weight: 700;
    line-height: 1.25;
    text-wrap: balance;
    /* Two lines at most, so a long headline cannot push the panel out of ratio. */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

.blog-card__placeholder-brand {
    position: relative;
    font-size: var(--text-xs);
    opacity: 0.8;
}

.blog-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-4);
}

.blog-card__category {
    align-self: flex-start;
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--brand-primary);
}

.blog-card__title {
    font-size: var(--text-lg);
    line-height: 1.35;
    margin: 0;
}

.blog-card__title a { color: var(--text-primary); }
.blog-card__title a:hover { color: var(--brand-primary); }

.blog-card__excerpt {
    margin: 0;
    color: var(--text-secondary);
    font-size: var(--text-sm);
    /* Three lines keeps every card the same height without truncating mid-word. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.blog-card__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-xs);
}

/* --- sidebar ------------------------------------------------------------ */

.blog-side-block { margin-bottom: var(--space-6); }

.blog-side-block__title {
    font-size: var(--text-base);
    margin: 0 0 var(--space-3);
}

.blog-side-list { list-style: none; margin: 0; padding: 0; }

.blog-side-list a {
    display: flex;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    color: var(--text-secondary);
    border-bottom: 1px solid var(--border-subtle);
}

.blog-side-list a:hover,
.blog-side-list a.is-active { color: var(--brand-primary); }

.blog-side-list__count { color: var(--text-muted); font-size: var(--text-xs); }

.blog-search { display: flex; gap: var(--space-2); margin-bottom: var(--space-6); }
.blog-search .input { flex: 1 1 auto; min-width: 0; }

.blog-tags { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.blog-tag {
    padding: 4px 12px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-default);
    color: var(--text-secondary);
    font-size: var(--text-xs);
}

.blog-tag:hover,
.blog-tag.is-active {
    background: var(--brand-primary);
    border-color: var(--brand-primary);
    color: var(--text-on-brand);
}

/* --- article ------------------------------------------------------------ */

/* Constrained measure: long-form text is unreadable at full container width. */
.blog-article {
    max-width: 760px;
    margin: 0 auto var(--space-12);
}

.blog-article__header { margin-bottom: var(--space-6); }

.blog-article__title {
    font-size: var(--text-3xl);
    line-height: 1.25;
    margin: var(--space-2) 0;
}

.blog-article__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.blog-article__hero {
    width: 100%;
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-6);
}

.blog-article__body {
    font-size: var(--text-base);
    line-height: 1.75;
    color: var(--text-primary);
}

.blog-article__body p { margin: 0 0 var(--space-4); }

.blog-article__tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-8);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border-subtle);
}

/* ==========================================================================
   Product comparison
   ========================================================================== */

.compare-actions {
    display: flex;
    justify-content: flex-end;
    margin-bottom: var(--space-4);
}

/* The table scrolls inside its own container; the page never scrolls sideways. */
.compare-wrap { overflow-x: auto; }

.compare-table { min-width: 600px; }

.compare-table th[scope="row"] {
    white-space: nowrap;
    color: var(--text-secondary);
    font-weight: 600;
}

.compare-table td,
.compare-table th[scope="col"] {
    vertical-align: top;
    min-width: 160px;
}

.compare-head {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    color: var(--text-primary);
}

.compare-head img {
    width: 100%;
    max-width: 120px;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-md);
}

.compare-head:hover { color: var(--brand-primary); }

/* The checkout summary line image. Was an inline style; moved here so the box is described in
   one place and the markup can stay a plain tag. */
.checkout-line__img {
    border-radius: var(--radius-sm);
    object-fit: cover;
}

/* Order history strip and order detail lines. */
.order-thumb {
    border-radius: var(--radius-sm);
    object-fit: cover;
    background: var(--surface-sunken);
}

/* A photo a customer attached to their own review. */
.review-photo {
    border-radius: var(--radius-md);
    object-fit: cover;
}

/* Quick view fills its square frame. */
.quick-view__img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Matches the wishlist heart's active treatment so the two quick actions read as a pair. */
.product-card__wish.is-active {
    color: var(--brand-primary);
    border-color: var(--brand-primary);
}

/* ==========================================================================
   Product questions
   ========================================================================== */

.qa-list { list-style: none; margin: 0 0 var(--space-6); padding: 0; }

.qa-item {
    padding: var(--space-4) 0;
    border-bottom: 1px solid var(--border-subtle);
}

.qa-item:last-child { border-bottom: none; }

.qa-item__question,
.qa-item__answer {
    display: flex;
    gap: var(--space-3);
    margin: 0 0 var(--space-2);
    line-height: 1.6;
}

.qa-item__answer { color: var(--text-secondary); }

/* Q and A markers: a fixed-width badge so the two lines align regardless of text length. */
.qa-item__marker {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--brand-primary-soft);
    color: var(--brand-primary);
    font-size: var(--text-xs);
    font-weight: 700;
}

.qa-item__marker--answer {
    background: var(--brand-secondary-soft, var(--brand-primary-soft));
    color: var(--brand-secondary, var(--brand-primary));
}

.qa-item__meta,
.qa-item__pending {
    margin: 0 0 0 36px;
    font-size: var(--text-xs);
}

.qa-form {
    padding-top: var(--space-6);
    border-top: 1px solid var(--border-subtle);
}

.qa-form__title { font-size: var(--text-base); margin: 0 0 var(--space-3); }

/* ==========================================================================
   Exit-intent offer
   ========================================================================== */

.exit-intent {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
    background: rgba(0, 0, 0, 0.55);
}

.exit-intent__panel {
    position: relative;
    width: 100%;
    max-width: 420px;
    padding: var(--space-8) var(--space-6) var(--space-6);
    border-radius: var(--radius-xl);
    background: var(--surface-card);
    box-shadow: var(--shadow-lg);
    text-align: center;
}

.exit-intent__close {
    position: absolute;
    top: var(--space-2);
    inset-inline-end: var(--space-2);
    width: 36px;
    height: 36px;
    border: 0;
    background: none;
    color: var(--text-muted);
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
}

.exit-intent__close:hover { color: var(--text-primary); }

.exit-intent__title { margin: 0 0 var(--space-3); font-size: var(--text-xl); }
.exit-intent__body { margin: 0 0 var(--space-4); color: var(--text-secondary); }

.exit-intent__coupon {
    margin: 0 0 var(--space-4);
    padding: var(--space-3);
    border: 1px dashed var(--brand-primary);
    border-radius: var(--radius-md);
    color: var(--brand-primary);
}

.exit-intent__coupon strong { display: block; font-size: var(--text-lg); letter-spacing: 0.08em; }

.exit-intent__form { display: flex; gap: var(--space-2); margin-bottom: var(--space-3); }
.exit-intent__form .input { flex: 1 1 auto; min-width: 0; }

.exit-intent__dismiss {
    border: 0;
    background: none;
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-decoration: underline;
    cursor: pointer;
}

/* Respect a reduced-motion preference: no entrance animation at all. */
@media (prefers-reduced-motion: no-preference) {
    .exit-intent__panel { animation: exit-intent-in var(--duration-base) var(--ease-out); }

    @keyframes exit-intent-in {
        from { opacity: 0; transform: translateY(12px); }
        to { opacity: 1; transform: none; }
    }
}

/* ==========================================================================
   Page transitions
   ========================================================================== */

/*
   A short fade-and-lift on the main content when a page arrives.

   Deliberately brief (180ms) and applied to <main> only: animating the whole document
   delays first paint of the header and navigation, which is the opposite of what a
   transition is for. Nothing here blocks interaction -- the page is usable from the
   first frame.

   view-transition-name is set where the browser supports the View Transitions API, so
   navigation cross-fades natively; the keyframe is the fallback everywhere else.
*/
@media (prefers-reduced-motion: no-preference) {
    main {
        animation: page-in 180ms var(--ease-out);
    }

    @keyframes page-in {
        from { opacity: 0; transform: translateY(6px); }
        to { opacity: 1; transform: none; }
    }
}

@view-transition {
    navigation: auto;
}

/* The header and footer persist across navigations rather than fading with the content. */
@media (prefers-reduced-motion: no-preference) {
    .site-header { view-transition-name: site-header; }
    .site-footer { view-transition-name: site-footer; }
}

/* Recent searches, shown in the suggestion panel when the box is focused and empty. */
.suggestion-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border-subtle);
    color: var(--text-muted);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.suggestion-head__clear {
    border: 0;
    background: none;
    color: var(--brand-primary);
    font-size: var(--text-xs);
    cursor: pointer;
}

/* A history row has no thumbnail, so it needs the padding the image would have given it. */
.suggestion--history { padding-inline-start: var(--space-3); }

/* Wishlist price watch ----------------------------------------------------- */

.wishlist-entry { display: flex; flex-direction: column; gap: var(--space-2); }

.wishlist-entry__drop {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--success);
}
