/* harvestly_modern — all classes prefixed with hm- */

/* ============================================================================
 * === Discount banner ========================================================
 * ============================================================================
 *
 *
 * Width and horizontal positioning are set imperatively in JS, not CSS, on
 * mount + window resize. The reason: pure-CSS approaches that mix viewport
 * units with parent-relative percentages (e.g. `width: 98vw; margin-left:
 * calc(50% - 49vw)`) drift at certain breakpoints because `50%` is parent-
 * relative and `vw` is viewport-relative — they only agree when the parent
 * is exactly viewport-centered, which CS-Cart's responsive container isn't
 * at every breakpoint (scrollbar gutter, container padding, breakpoint
 * snapping all factor in). Measuring document.documentElement.clientWidth
 * and the mount div's getBoundingClientRect().left in JS gives a pixel-
 * accurate result at any viewport width and stays correct on resize.
 *
 * Both slide-IN and slide-OUT are driven by element.animate() in JS rather
 * than CSS. The slide-in is deferred to window.load so the page is on
 * screen before it plays; the slide-out runs on dismiss and naturally
 * supersedes the slide-in's Animation object (we cancel() it explicitly
 * for cleanliness).
 *
 * Typography matches the site (Open Sans cascade); palette uses the brand
 * green #21703b lifted from .ty-btn__primary so the banner reads as part
 * of the existing CTA system, not a foreign import.
 */

.hm-banner {
    position: relative;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    /* width, margin-left, margin-right are inline-styled by JS on mount + resize. */
    /* margin-bottom gives the rounded card breathing room before the next block. */
    margin-bottom: 8px;
    /* padding-top / padding-bottom are inline-styled by JS from the admin's
     * vertical_padding setting; left/right keep their static values to clear
     * the close button on the right. */
    padding-left: 24px;
    padding-right: 56px;
    background: #21703b;
    color: #fff;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    font-size: 15px;
    line-height: 1.5;
    text-align: center;
    border-radius: 10px;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
    overflow: hidden;
    /* Resting state. JS animations interpolate to/from collapsed inline styles. */
    max-height: 200px;
    opacity: 1;
    transform: translateY(0);
}

.hm-banner__text {
    display: inline;
}

.hm-banner__link {
    color: #fff;
    font-weight: 500;
    text-decoration: none;
    border-bottom: 1px solid rgba(255, 255, 255, 0.45);
    transition: border-color 160ms ease;
}

.hm-banner__link:hover,
.hm-banner__link:focus {
    color: #fff;
    text-decoration: underline;
    border-bottom-color: transparent;
}

.hm-banner__close {
    position: absolute;
    top: 50%;
    right: 12px;
    transform: translateY(-50%);
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 50%;
    color: #fff;
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    transition: background-color 160ms ease;
}

.hm-banner__close:hover,
.hm-banner__close:focus {
    background-color: rgba(255, 255, 255, 0.15);
    outline: none;
}

.hm-banner__close:focus-visible {
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
}

@media (max-width: 480px) {
    .hm-banner {
        /* Vertical padding is set inline by JS; only override left/right here. */
        padding-left: 16px;
        padding-right: 48px;
        font-size: 14px;
        line-height: 1.45;
        text-align: left;
    }
    .hm-banner__close {
        width: 32px;
        height: 32px;
        font-size: 20px;
        right: 8px;
    }
}

/* ============================================================================
 * === Countdown banner =======================================================
 * ============================================================================
 *
 *
 * Inline pill that sits in the top-bar row alongside the account/cart
 * links — NOT a full-width stripe. The block's grid placement provides
 * the surrounding column; the pill itself is sized to its text content
 * with a subtle green chip background.
 *
 * Centering vs. left-anchoring: at desktop widths the pill is centered
 * within its grid column (justify-content: center on the grid). Below
 * the mobile breakpoint we left-anchor (flex-start) so the pill doesn't
 * push the cart/account elements onto a second row, AND we hide the
 * "Order in " lead text to claw back the width that pushes cart down.
 *
 * The {time} portion uses monospace digits with pad2'd values, so the
 * digit column width is constant and the surrounding suffixes ("h",
 * "m", "s") don't jitter as values tick. All values fade-on-change
 * (~150ms); seconds tick once per second, minutes once per minute, etc.
 */

.hm-countdown {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0;
    padding: 4px 12px;
    background: rgba(33, 112, 59, 0.08);
    border: 1px solid rgba(33, 112, 59, 0.25);
    border-radius: 999px;
    color: #21703b;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    font-size: 12px;
    line-height: 1.5;
    white-space: nowrap;
}

.hm-countdown__lead,
.hm-countdown__tail {
    font-weight: 400;
}

.hm-countdown__date {
    font-weight: 600;
}

.hm-countdown__time {
    /* inline-block (not inline-flex) so the text-node spaces between
     * value/unit spans render as actual whitespace. inline-flex turns
     * those text nodes into anonymous flex items whose whitespace
     * collapses — producing "6days08h03m" instead of "6 days 08h03m". */
    display: inline-block;
    margin: 0 2px;
    font-family: 'SF Mono', 'Menlo', 'Consolas', 'Courier New', monospace;
    font-weight: 700;
    font-size: 13px;
    letter-spacing: 0.01em;
}

.hm-countdown__value,
.hm-countdown__unit {
    display: inline-block;
}

@keyframes hm-countdown-fade {
    0%   { opacity: 0.55; }
    100% { opacity: 1; }
}
.hm-countdown__value--fade,
.hm-countdown__unit--fade {
    animation: hm-countdown-fade 150ms ease-out;
}

/* Grid wrapper: centers the pill within its 13-col column at desktop.
 *
 * text-align (not flex) is the right tool here because CS-Cart's grid
 * renderer wraps our block in a layer we don't control:
 *
 *   .hm-countdown-grid (this rule)
 *     └─ [data-island="countdown-banner"]    ← full-width block, JS-mounted
 *         └─ .hm-countdown                   ← inline-flex pill
 *
 * If we used `display: flex; justify-content: center` here, the only
 * flex item is the [data-island] div (which is block-level, full-width)
 * — the pill inside it sits at the left of that full-width band, not
 * centered. text-align cascades to the [data-island] and centers the
 * inline-flex pill inside it directly.
 */
.hm-countdown-grid {
    text-align: center;
}

/* Mobile: left-anchor the pill so cart/account elements keep room on
 * the right, and drop the "Order in " lead so the remaining text fits
 * on one line alongside cart/account at narrow widths. */
@media (max-width: 767px) {
    .hm-countdown-grid {
        text-align: left;
    }
    .hm-countdown__lead {
        display: none;
    }
}

@media (max-width: 480px) {
    .hm-countdown {
        font-size: 11px;
        padding: 3px 10px;
        white-space: normal;
    }
    .hm-countdown__time {
        font-size: 12px;
    }
}

/* ============================================================================
 * === Product carousel =======================================================
 * ============================================================================
 *
 *
 * Horizontal scroll-snap row of product cards. The visual model is the
 * 7-Eleven app circa 2020: square images on top, name + vendor + price
 * underneath, native CSS scroll-snap, partial cards peeking on the edges
 * to signal "more here".
 *
 * Card sizing is fixed-width (200px desktop / 160px mobile). The peek effect
 * comes from the triplicated cards in the JS island — there's always more
 * content on both sides of whatever's centered, so no track padding is
 * needed at the rails. This is how the infinite-loop wrap stays invisible:
 * copy 0 + copy 1 + copy 2, scroll wraps from end of copy 2 back into
 * copy 1 silently.
 *
 * Why scroll-snap-type: x proximity (not mandatory): mandatory snaps
 * aggressively even during 1-px-per-frame programmatic scroll, which fights
 * our continuous auto-scroll preview. Proximity snaps only when scrolling
 * stops — perfect for both modes (auto-scroll is smooth and uninterrupted;
 * user-driven scroll lands snapped to the nearest card when the gesture
 * ends).
 *
 * Card click navigates to product detail, vendor link is a separate target
 * — visually nested, behaviorally distinct (see product-carousel.js;
 * stopPropagation on the vendor anchor's click).
 */
/* Block root is centered horizontally with a max-width that matches the
 * page's typical content column. Without this the carousel inherited the
 * full grid width and looked off-axis from the rest of the page on wider
 * viewports.
 *
 * 1280px caps the row width on desktop. Side padding scales with viewport
 * (clamp) so it shrinks to zero on narrow viewports — at 875px we don't
 * want 32px of horizontal padding eating real estate that could be
 * showing another card-and-a-half. */
.hm-pc {
    max-width: 1280px;
    margin: 16px auto;
    padding: 0 clamp(0px, 1vw, 16px);
    box-sizing: border-box;
}

.hm-pc__title {
    margin: 0 0 12px 0;
    text-align: center;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    font-size: 22px;
    font-weight: 700;
    color: #21282f;
    line-height: 1.2;
}

/* Track wrap is a positioning context for the absolute-positioned chevron
 * buttons and the edge-fade gradients. The fades are pure visual cue —
 * they signal "there's more content past this edge" without occluding any
 * actual card content (the gradients are pointer-events:none). */
.hm-pc__track-wrap {
    position: relative;
}

.hm-pc__track-wrap::before,
.hm-pc__track-wrap::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 60px;
    pointer-events: none;
    z-index: 1;
    opacity: 0;
    transition: opacity 200ms ease;
}

.hm-pc__track-wrap::before {
    left: 0;
    background: linear-gradient(to right, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0));
}

.hm-pc__track-wrap::after {
    right: 0;
    background: linear-gradient(to left, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0));
}

.hm-pc__track-wrap:hover::before,
.hm-pc__track-wrap:hover::after {
    opacity: 1;
}

.hm-pc__track {
    display: flex;
    /* Gap is JS-driven (proportional to --hm-card-width); 16px is the
     * fallback if JS hasn't set it yet. */
    gap: var(--hm-card-gap, 16px);
    overflow-x: scroll;
    overflow-y: hidden;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    /* iOS Safari fix: pan-x tells the browser this element only handles
     * horizontal panning, so an ambiguous-direction swipe (more horizontal
     * than vertical) doesn't get claimed by the page-level vertical scroll
     * gesture. Without this, iOS demands more swipe pressure before
     * committing to horizontal — the carousel feels "sticky" to start.
     *
     * overscroll-behavior-x: contain prevents a swipe at the carousel's
     * edge from chaining into a page-level horizontal-scroll-of-nothing
     * (which iOS still tries to satisfy with a rubber-band, taking the
     * scroll-grab away from us). */
    touch-action: pan-x;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    /* Vertical padding: +14 top / +16 bottom over the original 4/12 to
     * give the focused card's scale(1.10) growth room to breathe AND
     * accommodate the elevated shadow's 0 6px 20px reach. With the
     * previous 4/12 values, overflow-y: hidden was clipping ~12px off
     * the bottom of focused cards (the scale grows the card by 5% in
     * each direction from center, plus the shadow extends ~16px below
     * the scaled bottom edge — the original 12px just barely covered
     * the unfocused state). Asymmetric extra (more on bottom than top)
     * because the shadow is Y-offset 6px downward; the top bleed is
     * smaller. */
    padding: 14px 0 28px;
    box-sizing: border-box;
}

.hm-pc__track::-webkit-scrollbar {
    display: none;
}

/* Card width is JS-driven via the --hm-card-width CSS variable, set on the
 * carousel root by product-carousel.js after a ResizeObserver measurement
 * of the block's container width. Polish pass replaced a clamp()-based
 * sizing with this — clamp's mid coefficient produced dead zones at certain
 * viewport widths (e.g. 16vw of 875 = 140 hit the floor of 150, so cards
 * stuck at 150 while available width grew), and tuning the mid required
 * juggling viewport-relative and pixel constraints in two dimensions.
 *
 * The JS picks visibleCount and cardWidth jointly so peeks land in a
 * natural [15%, 55%] band, then writes both as variables. Track gap is
 * also JS-driven (--hm-card-gap) so card+gap arithmetic stays consistent.
 *
 * Fallback width: 180px is a safe mid-range for desktop in case the JS
 * fails to compute (e.g. ResizeObserver missing — extremely unlikely on
 * any modern browser, but the carousel still renders).
 */
.hm-pc-card {
    flex: 0 0 auto;
    width: var(--hm-card-width, 180px);
    box-sizing: border-box;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
    cursor: pointer;
    scroll-snap-align: center;
    transition: transform 200ms ease-out, box-shadow 200ms ease-out;
    overflow: hidden;
    user-select: none;
}

.hm-pc-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
}

/* Center-card focus — applied by an IntersectionObserver in product-carousel.js
 * to whichever card's center sits in a thin viewport-center band. Persistent
 * (not hover-only): the row always has one prominent card so the user has a
 * clear visual anchor while scanning. Scale 1.10 pops the card off the row
 * without reflowing neighbors (transform doesn't trigger layout); the green
 * elevated shadow + 5% brightness lift land it as "selected" without dimming
 * siblings. z-index keeps the scaled edge above neighboring cards' shadows.
 *
 * --focused wins over --active and :hover by being the more specific class;
 * --active is now JS-only (it drives the action button's referent product;
 * see product-carousel.js setActive()) and carries no visual styling. */
.hm-pc-card--focused {
    transform: scale(1.10);
    filter: brightness(1.05);
    box-shadow: 0 6px 20px rgba(33, 112, 59, 0.22);
    z-index: 2;
}

.hm-pc-card--focused:hover {
    /* Don't stack hover's translateY on top of the focus scale — the
     * combined transform jitters when the hover state flickers as the
     * pointer moves inside the scaled card's bounds. */
    transform: scale(1.10);
    box-shadow: 0 8px 22px rgba(33, 112, 59, 0.26);
}

@media (prefers-reduced-motion: reduce) {
    /* No scale animation under reduced motion; still indicate focus via
     * shadow + brightness so the visual anchor is preserved. */
    .hm-pc-card--focused,
    .hm-pc-card--focused:hover {
        transform: none;
    }
}

/* Image-wrap is the positioned host for the label overlay so labels
 * absolute-position over the top of the image, not the whole card.
 * Without this the labels would shift as the body text reflowed
 * (different line-clamps for short vs. long product names) and would
 * sometimes overlap the price line on narrow widths. */
.hm-pc-card__image-wrap {
    position: relative;
    width: 100%;
    line-height: 0; /* kill the inline-image text-baseline gap */
}

/* Label overlay: positioned across the top of the image with a small
 * inset. flex-wrap lets multiple labels stack onto a second row when a
 * product has 3+ tags; gap keeps them visually separated. pointer-
 * events:none so the labels don't intercept clicks meant for the card
 * → product-detail navigation. */
.hm-pc-card__labels {
    position: absolute;
    top: 8px;
    left: 8px;
    right: 8px;
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    pointer-events: none;
    z-index: 1;
    line-height: 1.2; /* restore from image-wrap's reset */
}

/* Label pill: defaults to brand green if the admin-configured per-label
 * colors aren't present (inline-style overrides win when set). Sized for
 * the card scale — too big and they crowd the image; too small and
 * they're unreadable on mobile. The tight letter-spacing + uppercase
 * matches the brand's CTA buttons. */
.hm-pc-card__label {
    padding: 3px 8px;
    background: rgba(33, 112, 59, 0.92);
    color: #fff;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    border-radius: 4px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
    white-space: nowrap;
    /* Don't truncate single-word labels — but cap at card width minus a
     * little so a runaway long label can't blow out the row. */
    max-width: calc(var(--hm-card-width, 180px) - 24px);
    overflow: hidden;
    text-overflow: ellipsis;
}

@media (max-width: 480px) {
    .hm-pc-card__labels {
        top: 6px;
        left: 6px;
        right: 6px;
        gap: 3px;
    }
    .hm-pc-card__label {
        padding: 2px 6px;
        font-size: 10px;
        letter-spacing: 0.03em;
    }
}

/* Image follows the fluid card width — fills 100% of card width with
 * aspect-ratio: 1/1 driving height. Now that cards are clamp()-sized,
 * we can't keep the explicit width: 200px we had before; the image
 * needs to track the card's actual rendered width.
 *
 * aspect-ratio: 1/1 + width: 100% is reliable in modern browsers (Safari
 * 15+, Chrome 88+, Firefox 89+ — all comfortably in the support window
 * for our deployment). The bundle's `img { height: auto }` reset rule
 * has lower specificity than this class selector, so our square
 * dimensions win. object-fit: cover crops portrait/landscape sources to
 * fill the square cleanly. */
.hm-pc-card__image {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    background: #f4f4f4;
}

.hm-pc-card__body {
    padding: 10px 12px 12px;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
}

.hm-pc-card__name {
    font-size: 14px;
    font-weight: 600;
    color: #21282f;
    line-height: 1.3;
    /* Two-line clamp to keep card heights uniform regardless of name length. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.6em;
    margin-bottom: 4px;
}

.hm-pc-card__vendor {
    display: inline-block;
    font-size: 12px;
    color: #6b7682;
    text-decoration: none;
    line-height: 1.4;
    margin-bottom: 6px;
}

.hm-pc-card__vendor:hover,
.hm-pc-card__vendor:focus {
    color: #21703b;
    text-decoration: underline;
}

.hm-pc-card__price {
    font-size: 15px;
    font-weight: 700;
    color: #21703b;
    line-height: 1.2;
}

/* Chevron nav buttons — desktop only. Hidden by default; fade in when the
 * carousel root is hovered. We hide them entirely on touch-friendly widths
 * because (a) they're hover-driven, which doesn't translate to touch, and
 * (b) mobile users navigate via swipe, which already works.
 *
 * Color: brand green at ~88% opacity with cream chevron — the inverse of
 * the site's footer (cream text on green panel), so the button visually
 * "belongs" to the brand without mimicking the footer literally. Slight
 * translucency lets a hint of the underlying card edge show through, which
 * helps the chevron feel embedded in the carousel rather than floating.
 *
 * z-index: 3 sits above the edge-fade gradients (z-index: 1) so the buttons
 * are fully readable at the edges where the fade is most opaque.
 *
 * display: grid + place-items:center centers the chevron glyph without the
 * inline-flex text-node-collapse gotcha (NOTES.md §9.2). */
.hm-pc__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    padding: 0;
    background: rgba(33, 112, 59, 0.88);
    color: #f5efdc;
    border: 0;
    border-radius: 50%;
    font-size: 20px;
    line-height: 1;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.22);
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 160ms ease, transform 120ms ease, background-color 160ms ease;
    z-index: 3;
}

.hm-pc__nav--prev {
    left: 12px;
}

.hm-pc__nav--next {
    right: 12px;
}

.hm-pc__track-wrap:hover .hm-pc__nav,
.hm-pc__nav:focus-visible {
    opacity: 1;
    pointer-events: auto;
}

.hm-pc__nav:hover {
    background: rgba(33, 112, 59, 1);
}

.hm-pc__nav:active {
    transform: translateY(-50%) scale(0.94);
}

.hm-pc__action {
    display: block;
    margin: 16px auto 0;
    /* Match the card width JS computed for the carousel — keeps the button
     * visually equal to one card. Falls back to 180px if JS hasn't
     * computed yet. */
    width: var(--hm-card-width, 180px);
    min-height: 48px;
    padding: 12px 20px;
    background: #21703b;
    color: #fff;
    border: 0;
    border-radius: 8px;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    font-size: 15px;
    font-weight: 700;
    line-height: 1.2;
    cursor: pointer;
    transition: background-color 160ms ease, transform 120ms ease;
    /* Truncate runaway product names with an ellipsis instead of wrapping
     * the button onto a second line — keeps the visual rhythm steady as
     * the active card changes. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.hm-pc__action:hover,
.hm-pc__action:focus {
    background: #1b5d31;
    outline: none;
}

.hm-pc__action:active {
    transform: scale(0.98);
}

/* Options-required state — visually softer (less saturated), not greyed out.
 * The label changes to "Choose options for [Product]" and the click routes
 * to the product detail page rather than performing add-to-cart. */
.hm-pc__action--options {
    background: #6aa17e;
}

.hm-pc__action--options:hover,
.hm-pc__action--options:focus {
    background: #5b9070;
}

@media (max-width: 767px) {
    .hm-pc {
        max-width: none;
        padding: 0 8px;
    }
    /* Card width and gap are now JS-driven (the polish-pass dynamic sizer
     * picks visibleCount=3 on mobile and lays out from the measured block
     * width). Title/name/price font tweaks remain CSS-driven. */
    .hm-pc__title {
        font-size: 18px;
    }
    .hm-pc-card__name {
        font-size: 13px;
    }
    .hm-pc-card__price {
        font-size: 14px;
    }
}

/* Chevron-hide breakpoint, separate from the typography breakpoint above.
 * Keeps the chevrons visible on tablet widths (~576-767px) where some
 * users still prefer click navigation over swipe — the chevrons are
 * hover-driven so a finger-only tablet user won't see them, but a stylus/
 * trackpad-equipped tablet (Surface, iPad with Magic Keyboard) will. The
 * swipe gesture continues to work either way; we hide the chevrons only
 * on the smallest viewports where they'd visually crowd the row. */
@media (max-width: 575px) {
    .hm-pc__nav {
        display: none;
    }
}

/* ============================================================================
 * === Vendor carousel ========================================================
 * ============================================================================
 *
 *
 * Visually parallel to the product carousel but distinct: hm-vc prefix instead
 * of hm-pc, no trailing action button (vendor cards link directly to the
 * microstore on click), card content is logo + name + city + product count.
 *
 * Layout reuse: the scroll-snap track and edge-fade gradients are functionally
 * identical to .hm-pc__track-wrap / .hm-pc__track. We did NOT factor those into
 * shared .hm-carousel-* selectors because:
 *  - The product carousel CSS has product-card-specific concerns (clamp values
 *    aligned with the action button below) that we'd have to either duplicate
 *    upward into the shared rule or override downward in the product-specific
 *    rule. Either way, no win.
 *  - Two ~30-line blocks of duplicated layout CSS is less code overall than
 *    a shared base + two override layers.
 * NOTES.md §12.6 carries the rationale.
 *
 * Card sizing tracks the product carousel's clamp() approach. Vendor cards
 * carry one less line of body text (no price line) but a logo image instead
 * of a product image — net visual height is similar. Same width range keeps
 * vendor and product carousels aligned visually when both are placed on the
 * same page.
 */
.hm-vc {
    max-width: 1280px;
    margin: 16px auto;
    padding: 0 clamp(0px, 1vw, 16px);
    box-sizing: border-box;
}

.hm-vc__title {
    margin: 0 0 12px 0;
    text-align: center;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    font-size: 22px;
    font-weight: 700;
    color: #21282f;
    line-height: 1.2;
}

.hm-vc__track-wrap {
    position: relative;
}

.hm-vc__track-wrap::before,
.hm-vc__track-wrap::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 60px;
    pointer-events: none;
    z-index: 1;
    opacity: 0;
    transition: opacity 200ms ease;
}

.hm-vc__track-wrap::before {
    left: 0;
    background: linear-gradient(to right, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0));
}

.hm-vc__track-wrap::after {
    right: 0;
    background: linear-gradient(to left, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0));
}

.hm-vc__track-wrap:hover::before,
.hm-vc__track-wrap:hover::after {
    opacity: 1;
}

.hm-vc__track {
    display: flex;
    /* JS-driven gap; see .hm-pc__track for the polish-pass rationale. */
    gap: var(--hm-card-gap, 16px);
    overflow-x: scroll;
    overflow-y: hidden;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    /* iOS Safari fix — see .hm-pc__track for rationale. */
    touch-action: pan-x;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    /* See .hm-pc__track for the focus-scale clearance rationale. */
    padding: 14px 0 28px;
    box-sizing: border-box;
}

.hm-vc__track::-webkit-scrollbar {
    display: none;
}

.hm-vc-card {
    flex: 0 0 auto;
    /* JS-driven; see .hm-pc-card for the polish-pass rationale. */
    width: var(--hm-card-width, 180px);
    box-sizing: border-box;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
    cursor: pointer;
    scroll-snap-align: center;
    transition: transform 200ms ease-out, box-shadow 200ms ease-out;
    overflow: hidden;
    user-select: none;
}

.hm-vc-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
}

/* Center-card focus — see .hm-pc-card--focused for full rationale.
 * Duplicated CSS rule (not extracted to a shared selector) per the
 * no-extract decision in NOTES §13.7. */
.hm-vc-card--focused {
    transform: scale(1.10);
    filter: brightness(1.05);
    box-shadow: 0 6px 20px rgba(33, 112, 59, 0.22);
    z-index: 2;
}

.hm-vc-card--focused:hover {
    transform: scale(1.10);
    box-shadow: 0 8px 22px rgba(33, 112, 59, 0.26);
}

@media (prefers-reduced-motion: reduce) {
    .hm-vc-card--focused,
    .hm-vc-card--focused:hover {
        transform: none;
    }
}

/* Logo slot: square 1:1, neutral light-gray background, object-fit: contain
 * (NOT cover — cropping a logo destroys the brand). The product carousel uses
 * cover because product photos benefit from edge-cropping; logos almost never
 * do. The light gray bg ensures non-square logos pillar/letter-box cleanly
 * without floating against the white card body. */
.hm-vc-card__logo {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    background: #f4f4f4;
    padding: 12px;
    box-sizing: border-box;
}

/* Initials placeholder: shown when fn_get_logos returned no theme logo for a
 * vendor (3/85 active vendors on this install). The JS island sets a deterministic
 * background color from the vendor name's hash, places the first 1-2 letters
 * centered. Matches the card's square logo slot dimensions so the layout stays
 * consistent regardless of which fallback fires. */
.hm-vc-card__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    aspect-ratio: 1 / 1;
    color: #fff;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    font-size: clamp(36px, 4.5vw, 64px);
    font-weight: 700;
    letter-spacing: -1px;
    user-select: none;
}

.hm-vc-card__body {
    padding: 10px 12px 14px;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
}

.hm-vc-card__name {
    font-size: 14px;
    font-weight: 600;
    color: #21282f;
    line-height: 1.3;
    /* Two-line clamp for vendor names — matches product card name treatment. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.6em;
    margin-bottom: 4px;
}

.hm-vc-card__city {
    font-size: 12px;
    color: #6b7682;
    line-height: 1.4;
    margin-bottom: 6px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Product count: brand green, smaller weight than the price line on the
 * product carousel (which is the primary CTA there). Here it's secondary
 * info — the vendor name is the lead. */
.hm-vc-card__count {
    font-size: 12px;
    font-weight: 600;
    color: #21703b;
    line-height: 1.2;
}

/* Chevrons — exact mirror of .hm-pc__nav. */
.hm-vc__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    padding: 0;
    background: rgba(33, 112, 59, 0.88);
    color: #f5efdc;
    border: 0;
    border-radius: 50%;
    font-size: 20px;
    line-height: 1;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.22);
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 160ms ease, transform 120ms ease, background-color 160ms ease;
    z-index: 3;
}

.hm-vc__nav--prev { left: 12px; }
.hm-vc__nav--next { right: 12px; }

.hm-vc__track-wrap:hover .hm-vc__nav,
.hm-vc__nav:focus-visible {
    opacity: 1;
    pointer-events: auto;
}

.hm-vc__nav:hover {
    background: rgba(33, 112, 59, 1);
}

.hm-vc__nav:active {
    transform: translateY(-50%) scale(0.94);
}

@media (max-width: 767px) {
    .hm-vc {
        max-width: none;
        padding: 0 8px;
    }
    /* Card width / gap are JS-driven on mobile too (visibleCount=3 floor). */
    .hm-vc__title {
        font-size: 18px;
    }
    .hm-vc-card__name {
        font-size: 13px;
    }
}

/* See .hm-pc__nav's 575px chevron-hide rule for rationale. */
@media (max-width: 575px) {
    .hm-vc__nav {
        display: none;
    }
}

/* ============================================================================
 * === Toast (shared utility) =================================================
 * ============================================================================
 *
 *
 * Top-right "noticeable" position: top: 25vh sits well below the header
 * but high enough that desktop eyes catch it on the way to scanning the
 * carousel. The corner-pinned bottom-right placement we used originally
 * was easy to miss when the user's gaze stayed on the carousel mid-page.
 *
 * Brand colors: solid brand-green background with cream text — opposite
 * of the footer's "cream on green" treatment so the toast reads as part
 * of the same color system without being mistaken for a footer fragment.
 *
 * Width hugs content (width: max-content, capped at 360px) so a short
 * confirmation like "Sage Bouquet added to cart" doesn't render in a
 * needlessly wide pill. column-reverse on the stack: newer toasts at the
 * bottom of the visible stack push older ones up.
 *
 * Container is created lazily on first showToast() call (see toast.js)
 * so pages that never fire a toast pay zero DOM cost. role="alert" vs
 * "status" is set in JS based on type so screen readers interrupt for
 * errors but not successes.
 */
.hm-toast-stack {
    position: fixed;
    right: 24px;
    top: 25vh;
    /* z-index: well above CS-Cart's modals/dropdowns. Stock theme uses
     * z-indices in the high thousands for popovers/CKEditor/notifications
     * (some go up to 10000 for full-screen modals). 2147483647 is the
     * max int that browsers honor — overkill but unambiguous. */
    z-index: 2147483647;
    display: flex;
    flex-direction: column-reverse;
    align-items: flex-end;
    gap: 8px;
    pointer-events: none;
}

.hm-toast {
    pointer-events: auto;
    /* Width hugs content; cap protects against absurdly long server messages. */
    width: max-content;
    max-width: 360px;
    padding: 12px 18px;
    background: rgba(33, 112, 59, 0.92);
    color: #f5efdc;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    font-size: 14px;
    font-weight: 500;
    line-height: 1.4;
    border-radius: 8px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
    cursor: pointer;
    backdrop-filter: blur(2px);
}

/* Type variants: error breaks the brand-green palette to a warm red so
 * users register "something failed" without reading the message. Info
 * keeps the brand green but slightly desaturated. Success uses the
 * default (full brand green) — no override needed. */
.hm-toast--error {
    background: rgba(176, 47, 30, 0.94);
}

.hm-toast--info {
    background: rgba(38, 99, 185, 0.92);
}

.hm-toast__message {
    display: block;
}

@media (max-width: 480px) {
    .hm-toast-stack {
        right: 8px;
        top: 16px;
        left: 8px;
        align-items: stretch;
    }
    .hm-toast {
        max-width: none;
        width: auto;
    }
}

/* ============================================================================
 * === Delivery zone checker ==================================================
 * ============================================================================
 *
 *
 * Form container: card-shaped, white background, subtle elevation. Matches
 * the discount banner's brand-green CTA palette so the "Check" button reads
 * as part of the same visual system. Failure result intentionally uses a
 * muted blue rather than red — "we don't deliver here yet" is informational,
 * not punitive (per spec).
 *
 * Suggestions dropdown: positioned absolutely below the input, 100% width
 * of the input wrap. List options highlight on hover and via keyboard nav
 * (ArrowDown/Up applies hm-zone__suggestion--active). Hidden by default
 * until the API returns results.
 *
 * Mobile (≤767px): the form stacks; input above, button full-width below.
 * Touch targets stay ≥44px tall.
 */
.hm-zone {
    box-sizing: border-box;
    max-width: 560px;
    margin: 0 auto;
    padding: 24px;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
}

.hm-zone__intro {
    margin: 0 0 16px;
    font-size: 18px;
    font-weight: 600;
    line-height: 1.3;
    color: #2a2a2a;
}

.hm-zone__form {
    display: flex;
    gap: 8px;
    align-items: stretch;
}

.hm-zone__input-wrap {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
}

.hm-zone__input {
    box-sizing: border-box;
    width: 100%;
    height: 44px;
    padding: 10px 14px;
    background: #fff;
    color: #2a2a2a;
    font-family: inherit;
    font-size: 15px;
    line-height: 1.4;
    border: 1px solid #d0d4d8;
    border-radius: 8px;
    outline: none;
    transition: border-color 160ms ease, box-shadow 160ms ease;
}

.hm-zone__input::placeholder {
    color: #8b8f93;
}

.hm-zone__input:focus {
    border-color: #21703b;
    box-shadow: 0 0 0 3px rgba(33, 112, 59, 0.15);
}

.hm-zone__submit {
    flex: 0 0 auto;
    min-width: 110px;
    height: 44px;
    padding: 0 20px;
    background: #21703b;
    color: #fff;
    font-family: inherit;
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: background 160ms ease;
}

.hm-zone__submit:hover:not(:disabled),
.hm-zone__submit:focus:not(:disabled) {
    background: #1a5b30;
}

.hm-zone__submit:disabled {
    background: #5b9070;
    cursor: not-allowed;
}

.hm-zone__suggestions {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    margin: 0;
    padding: 4px 0;
    list-style: none;
    background: #fff;
    border: 1px solid #d0d4d8;
    border-radius: 8px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.10);
    /* Above the result card so the dropdown overlays cleanly. */
    z-index: 10;
    max-height: 280px;
    overflow-y: auto;
}

.hm-zone__suggestion {
    padding: 10px 14px;
    color: #2a2a2a;
    font-size: 14px;
    line-height: 1.35;
    cursor: pointer;
}

.hm-zone__suggestion:hover,
.hm-zone__suggestion--active {
    background: #f1f6f3;
    color: #21703b;
}

.hm-zone__result {
    /* Column flex so the icon-row sits above any CTA (Sign Up Now button
     * or "get notified" link), each taking its own line. The previous
     * single-row layout cramped the success message and button together. */
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 16px;
    padding: 14px 16px;
    border-radius: 8px;
    font-size: 15px;
    line-height: 1.45;
}

.hm-zone__result-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
}

.hm-zone__result-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    font-weight: 700;
    font-size: 14px;
    /* Slight top offset so the icon optical-aligns with the first line of
     * text rather than crowding the cap-height of multi-line copy. */
    margin-top: 1px;
}

.hm-zone__result-message {
    flex: 1 1 auto;
    color: #2a2a2a;
}

/* Failure-flow CTA: subdued underlined link. Keeps with the
 * "this is information, not an error" tone — a button would over-emphasize
 * an action the user may not want. */
.hm-zone__result-cta {
    align-self: flex-start;
    color: inherit;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.hm-zone__result-cta:hover,
.hm-zone__result-cta:focus {
    color: inherit;
    text-decoration: none;
}

/* Success-flow CTA: full button styling matching the "Check" submit
 * (brand green, rounded, white text). The success result already
 * confirms delivery; the button is the natural next step. */
.hm-zone__signup-btn {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 40px;
    padding: 0 22px;
    background: #21703b;
    color: #fff;
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
    border-radius: 8px;
    text-decoration: none;
    transition: background 160ms ease, transform 160ms ease;
}

.hm-zone__signup-btn:hover,
.hm-zone__signup-btn:focus {
    background: #1a5b30;
    color: #fff;
    text-decoration: none;
    transform: translateY(-1px);
}

.hm-zone__result--success {
    background: #e8f4ec;
    color: #1a5b30;
}

.hm-zone__result--success .hm-zone__result-icon {
    background: #21703b;
    color: #fff;
}

/* Failure: muted blue-grey (informational, not error red — per spec).
 * "We don't deliver here yet" is news, not a mistake the user made. */
.hm-zone__result--failure {
    background: #eef2f6;
    color: #3d556b;
}

.hm-zone__result--failure .hm-zone__result-icon {
    background: #3d556b;
    color: #fff;
}

/* Genuine errors (network, malformed input, missing key) get a warmer
 * grey-amber so they read as "something went wrong, retry" rather than
 * "you typed wrong" or "we don't deliver". Still not red. */
.hm-zone__result--error {
    background: #fbf2e6;
    color: #7a5a1a;
}

.hm-zone__result--error .hm-zone__result-icon {
    background: #c08a2e;
    color: #fff;
}

@media (max-width: 767px) {
    .hm-zone {
        padding: 18px;
    }
    .hm-zone__form {
        flex-direction: column;
    }
    .hm-zone__submit {
        width: 100%;
    }
    .hm-zone__intro {
        font-size: 17px;
    }
}

