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

/* ============================================================================
 * === Brand tokens (Phase 7w-foundation) =====================================
 * ============================================================================
 *
 * Single source of truth for brand colors + fonts. Application phase (next)
 * will sweep references across CTA blocks, carousels, backgrounds. This
 * foundation phase only DECLARES the tokens; existing CSS continues to use
 * the literal values it had (with `var(--hm-brand-green, #21703b)` patterns
 * resolving through the alias below).
 *
 * Webfont loading lives in hooks/index/links.post.tpl — Nunito + Open Sans
 * + Caveat (plus Montserrat, retained for the /shop/ hero welcome line).
 * Nexa is NOT loaded as a webfont: the logo is vector-rendered, and Nexa
 * is design-tool only.
 *
 * Fallback chains:
 *   - --hm-font-display + --hm-font-body include `system-ui, -apple-system,
 *     sans-serif` so a Google Fonts outage still produces a credible
 *     sans-serif (not the browser-default Times).
 *   - --hm-font-script falls back to `cursive` — no system equivalent for
 *     handwritten script; `cursive` is consistent across browsers.
 *
 * Backwards-compat: `--hm-brand-green` was never declared (only referenced
 * with literal fallback `var(--hm-brand-green, #21703b)` in ~30 places).
 * Now it's an alias to --hm-color-green-dark, so the existing references
 * resolve cleanly through the alias. The literal fallbacks become harmless
 * backup if a future edit ever removes the alias.
 * ========================================================================= */

:root {
    /* Brand colors */
    --hm-color-green-fresh: #56A430;
    --hm-color-green-dark: #21703B;
    --hm-color-green-darkest: #2D3E10;
    --hm-color-cream: #F1EBBF;
    --hm-color-linen: #F2F2F2;

    /* Backwards-compat alias — existing CSS references --hm-brand-green */
    --hm-brand-green: var(--hm-color-green-dark);

    /* Brand fonts */
    --hm-font-display: 'Nunito', system-ui, -apple-system, sans-serif;
    --hm-font-script:  'Caveat', cursive;
    --hm-font-body:    'Open Sans', system-ui, -apple-system, sans-serif;
    /* Accent — small-caps/uppercase chrome (CTAs, vendor names, view-all).
       Added Phase 7w-application; complements Nunito's display role. */
    --hm-font-accent:  'Montserrat', system-ui, -apple-system, sans-serif;
    /* Headline — Nexa-alike geometric display font (Phase 7-1m §60).
       Stands in for the brand-guide Nexa wordmark font (which is only
       used in the inline-SVG logo per §43.3). Poppins is the closest
       free geometric — modern, confident, distinct from Nunito's
       rounded-friendly tone. Used for page-defining H1 role: shop
       frame heading, vendor directory heading, vendor identity name,
       "Shop the Aisles" title, /shop/ hero welcome line. Pairs with
       Nunito on the same page — Poppins names the page, Nunito names
       the sections inside it. */
    --hm-font-headline: 'Poppins', system-ui, -apple-system, sans-serif;
}

/* ============================================================================
 * === Page background (Phase 7w-application) ================================
 * ============================================================================
 *
 * Body gets a warm cream wash (#F3EED0) — sits between full brand
 * cream (#F1EBBF, too saturated for a full-body wash) and the
 * original 7w-application's paler tint (#FAF7E5, too subtle for most
 * viewers to register as a chosen color). About 60% of the way from
 * the paler tint toward full cream — visibly committed warmth, but
 * still distinct from the brand-cream token (which is reserved for
 * specific accents/badges).
 *
 * Page color shows in the gutters between blocks; content surfaces
 * (hero, intro, carousels, footer) sit on white or near-white so
 * they read as "cards" floating on the page.
 *
 * Header stays white as a distinct chrome zone (navigation/branding
 * contrast against the page wash).
 *
 * Scoped to body via the `.tygh-content`-adjacent wrapper that the
 * responsive theme uses — applying directly to body is the simplest
 * lever and doesn't fight CS-Cart's own background rules.
 * ========================================================================= */

body {
    background-color: #F3EED0;
}

/* Phase 7-2b — defensive horizontal-overflow safety net at mobile widths.
 *
 * Background: Adam reported page-level L/R scroll on prod across all pages
 * on mobile. Local audit (Playwright @ 320/360/375/414px, 9 page templates)
 * found document.scrollWidth === viewport everywhere — bug is prod-only and
 * I couldn't isolate the offending element from this side. Likely candidates:
 * a prod-only third-party widget injecting overflowing DOM (Smile.io,
 * Searchanise search bar, Stripe Connect onboarding box), a CMS block on
 * prod that doesn't exist locally, or an iOS/Android Safari quirk where
 * `mask-image` + `transform` on the partners/testimonials marquees breaks
 * the marquee containers' `overflow:hidden` clipping.
 *
 * Applying `overflow-x: clip` at the document root catches whatever it is.
 * Scoped to the mobile breakpoint where the symptom appears; desktop is
 * unaffected. Uses `clip` (preferred) which doesn't create a scroll
 * container, with `hidden` declared first as a fallback for browsers that
 * don't support `clip` yet (iOS Safari <16).
 *
 * Doesn't break:
 *   - The product / aisle carousels (they have their own `overflow-x: scroll`
 *     containers, which still scroll within their own box)
 *   - The vertical page scroll (overflow-Y stays default visible)
 *   - position: fixed (FAB, drawers) — those anchor to viewport, not body
 *
 * If a future audit on prod identifies the actual culprit element, this
 * net can be replaced with a targeted fix. Until then, defensive depth. */
@media (max-width: 992px) {
    html, body {
        overflow-x: hidden;   /* fallback for browsers without `clip` */
        overflow-x: clip;     /* modern (Chromium 90+, Firefox 81+, Safari 16+) */
    }
}

/* ============================================================================
 * === 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;
    /* Phase 7-1f: background is JS-driven (countdown-banner.js
     * interpolates green → mustard amber → terracotta brick across
     * the 7-day cutoff cycle). The CSS fallback color matches the
     * green endpoint (#56A430) so the pill still reads branded if
     * JS hasn't mounted yet — also covers the first paint before
     * the first tick.
     *
     * Border is dropped; the saturated background provides its own
     * boundary against the white header strip without an outline.
     * Text goes white + drop-shadow for readability across the
     * entire gradient. The shadow stack is heavy enough to carry
     * the mid-amber values where the background lightens the most. */
    background-color: #56A430;
    border-radius: 999px;
    color: #fff;
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    font-size: 12px;
    line-height: 1.5;
    white-space: nowrap;
    text-shadow:
        0 1px 2px rgba(0, 0, 0, 0.45),
        0 0 6px rgba(0, 0, 0, 0.25);
    transition: background-color 600ms linear;
}

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

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

/* Phase 7o: 3-hour urgent pulse. Gentle "slow breath" via opacity
 * interpolation — 2.4s ease-in-out, opacity 1.0 → 0.85 → 1.0. No
 * flashing, no color shift; the gold gradient end is already doing the
 * urgency communication, this animation is a subtle reinforcement.
 *
 * @media (prefers-reduced-motion) zeroes the animation so the pill
 * stays static at the gold gradient end without movement. */
@keyframes hm-countdown-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.85; }
}

.hm-countdown--urgent {
    animation: hm-countdown-pulse 2.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .hm-countdown--urgent {
        animation: none;
    }
    .hm-countdown {
        transition: none;
    }
}

.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".
     *
     * Phase 7-1f: dropped the prior `margin: 0 2px` — the lead and
     * tail nbsps already provide the boundary separation. Monospace
     * was moved off this element and onto .hm-countdown__value only
     * (see below) so the nbsp separators between value spans don't
     * render at monospace-glyph width (~7.8px at 13px) — that read
     * as huge visible gaps next to the digits. With monospace scoped
     * to the value spans, the separators inherit Open Sans (~3-4px)
     * matching the lead/tail boundary spacing.
     */
    display: inline-block;
    margin: 0;
    font-weight: 700;
    font-size: 13px;
    letter-spacing: 0.01em;
}

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

/* Digits-only monospace. Keeps the value column non-jittery as
 * pad2'd values tick (every digit at fixed width) while the unit
 * labels ("days"/"day", "h", "m", "s") and the inter-value nbsp
 * separators inherit the surrounding Open Sans for proportional,
 * normal-width spacing. */
.hm-countdown__value {
    font-family: 'SF Mono', 'Menlo', 'Consolas', 'Courier New', monospace;
}

@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);
}

/* §60 follow-up: center-card focus visual REMOVED. Phase 7y removed
 * the bottom "Add to Cart" button in favor of per-card quick-add, so
 * the focused state has no functional referent — there's no longer a
 * "this card is the action button's target" relationship to surface.
 * Carrying the scale(1.10) + brightness lift + green shadow on a
 * random center card with the add-to-cart now per-card was visual
 * noise without payoff. The golden-glow on `.hm-pc__title` /
 * `.hm-vc__title` (carousel titles) takes over as the "row spotlight"
 * treatment instead — see the §44.7 carousel-polish block below.
 *
 * The IntersectionObserver-driven `--focused` class toggle in
 * product-carousel.js + vendor-carousel.js remains a forward-compat
 * hook with zero visual effect. A future surface that wants a
 * center-card emphasis can add CSS rules here without touching JS.
 * Cost: one IO observer + one classList.toggle on band-edge crossings
 * — negligible perf, kept for cheap optionality. */

/* 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;
    /* §68 (Phase 7-1u): reverted from §61's absolute `36.4px` back to
     * `2.6em`. The absolute value was pinned for the 14px ideal, but
     * fit-text (§61) shrinks long titles to the 11px floor — at which
     * point 36.4px is 2.54 line-heights, NOT 2. The `-webkit-line-clamp`
     * caps the rendered TEXT to 2 lines, but min-height forced the box
     * taller than that, and `overflow:hidden` then exposed a ~7.8px
     * sliver of line 3 (the "cutoff third row" Adam screenshotted on the
     * homepage product carousel). `2.6em` keeps the reserved slot at
     * exactly 2 lines at whatever size fit-text lands on, so no sliver.
     * Measured: peek gone, and cards still render uniform (256px) —
     * §61's feared height variance doesn't manifest here (card height is
     * driven by the image + body, not this slot). Matches the sibling
     * `.hm-vc-card__name`, which always used `2.6em` and never had the
     * bug (it isn't a fit-text target). See NOTES §68. */
    min-height: 2.6em;
    margin-bottom: 4px;
}

.hm-pc-card__vendor {
    /* §61: display block + nowrap + ellipsis-truncation properties
     * added so the fit-text algorithm has a clean single-line
     * measurement target (matches the .hm-aisle-card__vendor pattern
     * from §60). Without these, a long vendor name would wrap to a
     * second line, and `scrollWidth` would measure the wrapped width
     * — fit-text needs the un-wrapped width to detect overflow.
     * max-width: 100% caps the block to the card body's inner width. */
    display: block;
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    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);
}

/* === Phase 7-1f: low-count modes =====================================
 *
 * .hm-pc--static    — all products fit in the block viewport. Center
 *                     the cards, hide chevrons, kill the edge-fade
 *                     masks (no overflow to fade against).
 * .hm-pc--no-loop   — products exceed the viewport but a single copy
 *                     isn't wide enough to loop seamlessly. Chevrons
 *                     and swipe are the only navigation; the track is
 *                     scrollable with rails at both ends.
 *
 * JS (product-carousel.js mount) renders a single copy of products in
 * both modes (vs. the triplicated copy 0/1/2 of loop mode). These rules
 * adjust the chrome accordingly.
 */
.hm-pc--static .hm-pc__track {
    justify-content: center;
    overflow-x: hidden;
}

.hm-pc--static .hm-pc__nav,
.hm-pc--static .hm-pc__track-wrap::before,
.hm-pc--static .hm-pc__track-wrap::after {
    display: none;
}

/* No-loop: keep the chevrons; the edge fades stay too (they signal
 * "more cards beyond the rail" — accurate in no-loop because there
 * actually IS more content past the visible edge until the rail
 * is reached). */

/* .hm-pc__action rules removed in Phase 7y. The single bottom-of-
 * carousel "Add to Cart" / "Choose Options" button was replaced by
 * per-card quick-add buttons (.hm-pc-card__quick-add) + qty-badges
 * — same visual vocabulary as the aisle carousel on /shop/. See
 * the .hm-pc-card__quick-add rule below. */

/* === Per-card quick-add button (Phase 7y) ===
 * Mirrors .hm-aisle-card__quick-add (the established pattern on
 * /shop/ aisle cards) so the homepage product cards read as part
 * of the same interaction system. 30px green circle, top-right of
 * the image, with hover/active states for tactile feedback.
 *
 * `+` glyph is HTML text (innerHTML) rather than an SVG icon —
 * matches aisle quick-add for parity. z-index keeps it above the
 * .hm-pc-card__labels overlay (z-index: 1) that may share the
 * top region of the image. */
.hm-pc-card__quick-add {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: rgba(241, 235, 191, 0.7);
    border: 0;
    padding: 0;
    cursor: pointer;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
    transition: background 180ms ease, transform 180ms ease, box-shadow 180ms ease;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
}

.hm-pc-card__add-bag-icon {
    width: 60%;
    height: 60%;
    object-fit: contain;
    display: block;
    pointer-events: none;
    transition: filter 180ms ease;
}

.hm-pc-card__quick-add:hover,
.hm-pc-card__quick-add:focus-visible {
    background: rgba(241, 235, 191, 1);
    transform: scale(1.05);
    box-shadow: 0 4px 12px rgba(45, 62, 16, 0.15);
    outline: none;
}

.hm-pc-card__quick-add:hover .hm-pc-card__add-bag-icon,
.hm-pc-card__quick-add:focus-visible .hm-pc-card__add-bag-icon {
    filter: drop-shadow(0 0 8px rgba(240, 160, 32, 0.5));
}

.hm-pc-card__quick-add:active {
    transform: scale(0.95);
}

.hm-pc-card__quick-add:disabled {
    background: rgba(181, 181, 181, 0.7);
    cursor: not-allowed;
}

.hm-pc-card__quick-add:disabled .hm-pc-card__add-bag-icon {
    filter: grayscale(1) opacity(0.5);
}

/* === Per-card qty badge (Phase 7y) ===
 * Mirrors .hm-aisle-card__qty-badge — pill at bottom-right of the
 * image with the current cart qty. cart-state.js manages textContent;
 * the :empty rule hides the badge when qty=0 (clean default state).
 * Updates pulse via @keyframes hm-qty-pulse (defined for the aisle
 * card; the same animation key applies here via the inline
 * style trigger cart-state writes). */
.hm-pc-card__qty-badge {
    position: absolute;
    bottom: 8px;
    right: 8px;
    min-width: 26px;
    height: 26px;
    padding: 0 8px;
    border-radius: 13px;
    background: #21703b;
    color: #fff;
    font-weight: 700;
    font-size: 13px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.18);
    z-index: 2;
    pointer-events: none; /* purely informational; clicks fall through */
}

.hm-pc-card__qty-badge:empty {
    display: none;
}

@media (prefers-reduced-motion: reduce) {
    .hm-pc-card__quick-add,
    .hm-pc-card__add-bag-icon {
        transition: none;
    }
    .hm-pc-card__quick-add:hover,
    .hm-pc-card__quick-add:focus-visible,
    .hm-pc-card__quick-add:active {
        transform: none;
    }
    .hm-pc-card__quick-add:hover .hm-pc-card__add-bag-icon,
    .hm-pc-card__quick-add:focus-visible .hm-pc-card__add-bag-icon {
        filter: none;
    }
}

@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);
}

/* §60 follow-up: vendor-carousel center-card focus visual REMOVED for
 * the same reason as `.hm-pc-card--focused` above. The
 * IntersectionObserver toggle in vendor-carousel.js keeps running as
 * a dead-letter hook for future visual states. Title golden-glow
 * (`.hm-vc__title` in §44.7 polish below) replaces it as the row's
 * spotlight treatment. */

/* 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);
}

/* === Phase 7-1f: low-count modes ===
 * Same logic as .hm-pc--static / .hm-pc--no-loop — see the product
 * carousel section for full rationale. Vendor carousel mirrors per
 * the §13.7 no-extract decision.
 */
.hm-vc--static .hm-vc__track {
    justify-content: center;
    overflow-x: hidden;
}

.hm-vc--static .hm-vc__nav,
.hm-vc--static .hm-vc__track-wrap::before,
.hm-vc--static .hm-vc__track-wrap::after {
    display: none;
}

/* === Browse-All-Vendors CTA (Phase 7y) ===
 * Sits under the vendor carousel inside the same .hm-vc block.
 * Visual vocabulary borrowed from .hm-shop__aisles-browse-all-cta
 * (the established "browse all" pill on /shop/ landing) for
 * consistency: rounded pill, Montserrat accent typography, brand
 * green with a slight lift on hover. Centered via flex on the
 * .hm-vc__footer wrapper.
 *
 * Chevron tail is a CSS ::after so the label text stays clean
 * (translation-friendly, admin-overridable via the schema's
 * view_all_label prop if needed).
 *
 * Phase 7y is homepage-only — the aisle carousel's own view-all
 * CTAs on /shop/ landing stay untouched. */
.hm-vc__footer {
    display: flex;
    justify-content: center;
    margin-top: 8px;
    padding: 0 clamp(0px, 1vw, 16px) 16px;
}

.hm-vc__view-all {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 12px 28px;
    background: var(--hm-color-green-dark);
    /* `color: #fff !important` defeats the stock bundle's
     * `a:link, .ui-state-* a:visited { color: #212121 }` rule
     * (specificity 0,1,1 — beats our `.hm-vc__view-all` 0,1,0
     * since the bundle loads AFTER our addon CSS per NOTES §8.4).
     * Without !important the link text rendered near-black on the
     * brand-green button: low contrast at rest, briefly visible
     * mid-transition because the bg lightens toward green-fresh and
     * the text contrast momentarily improves.
     *
     * Same pattern likely affects other anchor-styled buttons in the
     * addon. Defensive !important on color is appropriate here —
     * we're fighting third-party CSS we don't control. */
    color: #fff !important;
    font-family: var(--hm-font-accent);
    font-weight: 700;
    font-size: 14px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    text-decoration: none;
    border-radius: 999px;
    box-shadow: 0 4px 12px rgba(45, 62, 16, 0.18);
    transition: background 160ms ease, transform 160ms ease, box-shadow 160ms ease;
}

.hm-vc__view-all::after {
    content: '→';
    font-size: 16px;
    line-height: 1;
    transition: transform 160ms ease;
}

.hm-vc__view-all:hover,
.hm-vc__view-all:focus-visible {
    background: var(--hm-color-green-fresh);
    /* Same defensive !important — stock theme also has
     * `.ui-state-hover a:visited { color: #212121 }` that could
     * leak through on certain class chains. */
    color: #fff !important;
    transform: translateY(-1px);
    box-shadow: 0 6px 16px rgba(45, 62, 16, 0.24);
    text-decoration: none;
    outline: none;
}

.hm-vc__view-all:hover::after,
.hm-vc__view-all:focus-visible::after {
    transform: translateX(2px);
}

@media (prefers-reduced-motion: reduce) {
    .hm-vc__view-all,
    .hm-vc__view-all::after {
        transition: none;
    }
    .hm-vc__view-all:hover,
    .hm-vc__view-all:focus-visible {
        transform: none;
    }
    .hm-vc__view-all:hover::after,
    .hm-vc__view-all:focus-visible::after {
        transform: none;
    }
}

@media (max-width: 480px) {
    .hm-vc__view-all {
        font-size: 13px;
        padding: 10px 22px;
    }
}

@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: 720px;
    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;
}

/* Caveat treatment (Phase 7-1m §60). The form sits inside a white card
 * on top of the topographic cream→green gradient backdrop. Caveat at
 * this clamp range reads as a confident handwritten note — pairs with
 * the homepage hero tagline + intro tagline Caveat vocabulary, gives
 * the form a warm voice instead of the prior generic-section-heading
 * Open Sans 600. Single CSS rule covers both block instances per §41
 * (homepage embed + profiles standalone). */
.hm-zone__intro {
    margin: 0 0 12px;
    font-family: var(--hm-font-script);
    font-weight: 700;
    font-size: clamp(24px, 3vw, 32px);
    line-height: 1.15;
    letter-spacing: -0.01em;
    color: var(--hm-color-green-darkest);
    text-align: center;
}

.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%;
    }
    /* §60: mobile font-size override removed — `.hm-zone__intro` now uses
       clamp(24px, 3vw, 32px) which sits at the 24px floor below ~800px
       viewport, preserving the Caveat voice down to the smallest widths. */
}

/* ============================================================================
 * === Drawer (Phase 7a) ======================================================
 * ============================================================================
 *
 * Two drawer instances + one shared backdrop, all rendered globally by
 * hooks/index/body.post.tpl on every storefront page except those under the
 * checkout controller. One CSS rule set parameterised by data-drawer-side;
 * 7b/c/d will replace the placeholder body content but won't touch this
 * structure.
 *
 * Z-index policy: edge tabs (100000) sit above page content but below the
 * backdrop (100001) and drawer panel (100002), so the open drawer eclipses
 * the edge tab visually. Toasts (z-index: 2147483647) intentionally stay
 * above drawers so error feedback can surface during cart actions.
 *
 * Custom properties on the .hm-drawer root are the tuning surface — phases
 * 7b-7f and any future polish should adjust those rather than re-edit the
 * rule set.
 */

.hm-drawer {
    --hm-drawer-width-mobile: 85vw;
    --hm-drawer-width-desktop: 400px;
    --hm-drawer-bg: #fff;
    --hm-drawer-shadow-color: rgba(0, 0, 0, 0.18);
    --hm-drawer-slide-duration: 450ms;
    --hm-drawer-backdrop-color: rgba(0, 0, 0, 0.55);
    --hm-drawer-backdrop-blur: 8px;
    /* §50: drawer-tab tokens previously declared here are MOVED to
     * .hm-drawer-tab itself — the button is a SIBLING of .hm-drawer in
     * the DOM (both rendered side-by-side from body.post.tpl), not a
     * descendant, so CSS custom properties declared here don't cascade
     * to it. Bug present since §16; was masked by the prior
     * fixed-px-icon rule which sized the button to its 18×18 content.
     * §50.2's percentage-icon change exposed the broken cascade — the
     * SVG fell back to default replaced-element sizing (~300×150) and
     * the button auto-sized to match. Hardcoding the dimensions
     * directly on the button (below) removes the indirection. */

    position: fixed;
    top: 0;
    bottom: 0;
    width: var(--hm-drawer-width-mobile);
    max-width: 100vw;
    background: var(--hm-drawer-bg);
    box-sizing: border-box;
    overflow: hidden; /* §49: inner content owns scrolling so subtotal locks bottom */
    z-index: 100002;
    display: flex;
    flex-direction: column;
    /* font-family inherits from <body> via the responsive theme so 7b/c/d
     * placeholder text reads as part of the site, not as system UI. */
    font-family: 'Open Sans', 'Helvetica Neue', -apple-system, sans-serif;
    transition: transform var(--hm-drawer-slide-duration) ease-out;
    will-change: transform;
}

@media (min-width: 769px) {
    .hm-drawer {
        width: var(--hm-drawer-width-desktop);
    }
}

.hm-drawer[data-drawer-side="left"] {
    left: 0;
    transform: translateX(-100%);
    box-shadow: 4px 0 24px var(--hm-drawer-shadow-color);
}

.hm-drawer[data-drawer-side="right"] {
    right: 0;
    transform: translateX(100%);
    box-shadow: -4px 0 24px var(--hm-drawer-shadow-color);
}

.hm-drawer--open {
    transform: translateX(0) !important;
}

.hm-drawer-close {
    position: absolute;
    top: 8px;
    right: 8px;
    /* Phase 7-2b — bumped 36→44 to meet WCAG 2.5.5 / Apple HIG mobile
     * touch-target minimums. The visible × glyph stays the same 28px
     * font-size (≈24×24 ink), so the rendered icon doesn't change —
     * only the hit box (and the hover-state background, which is
     * mostly absent at rest on mobile). Applied to .hm-drawer-close
     * globally so both the nav drawer and the cart drawer benefit;
     * either drawer's close button has the same accuracy problem on
     * a phone in the upper-right corner where thumb-reach is hardest. */
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    background: transparent;
    color: #333;
    font-size: 28px;
    line-height: 1;
    cursor: pointer;
    border-radius: 4px;
}

.hm-drawer-close:hover,
.hm-drawer-close:focus {
    background: rgba(0, 0, 0, 0.06);
    color: #000;
    outline: none;
}

.hm-drawer-close:focus-visible {
    outline: 2px solid #21703b;
    outline-offset: 2px;
}

.hm-drawer-content {
    padding: 56px 20px 20px; /* top padding clears the close button */
    flex: 1 1 auto;
    min-height: 0; /* §49: lets nested flex children shrink for inner scroll */
    overflow-y: auto; /* default: drawer-content scrolls (works for left/nav drawer) */
    color: #2b2b2b;
    font-size: 15px;
    line-height: 1.5;
}


/* Nav edge tab — translucent glass pill at the left edge of the viewport.
 * §50 restyled this from solid brand-green to cream-translucent + backdrop-
 * blur. The cart-side tab moved to a corner FAB in §50 too; this rule set
 * now serves nav only (left-side), so the [data-drawer-side="right"]
 * overrides are gone. */
.hm-drawer-tab {
    position: fixed;
    top: 50%;
    /* §50.12: button is now a transparent host for the designer's
     * `Shop menu tapered.svg` vector — the SVG carries its own pill
     * shape + brand-green fill + "SHOP" letterforms. Earlier glass-
     * morphism construction (cream-translucent bg + backdrop-blur +
     * border-radius) is removed; would conflict with the SVG's
     * tapered outline at the bounding box edges. Drop-shadow follows
     * the SVG path via `filter: drop-shadow()` on the img child. */
    /* §64 (7-1q): bumped ~22% from the native 30×142 SVG size (designer
     * guidance: "no more than 25%, bigger call" — picked the upper-middle
     * of the range). The `Shop menu tapered.svg` child scales via
     * width/height:100%, so the taper + "SHOP" letterforms enlarge in
     * proportion; no aspect-ratio distortion (30:142 ≈ 37:173). */
    width: 37px;
    height: 173px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    z-index: 100000;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: translateY(-50%);
    transition: transform 150ms ease-out, filter 220ms ease-out;
}

.hm-drawer-tab[data-drawer-side="left"] {
    left: 0;
}

.hm-drawer-tab:hover,
.hm-drawer-tab:focus {
    outline: none;
}

.hm-drawer-tab[data-drawer-side="left"]:hover,
.hm-drawer-tab[data-drawer-side="left"]:focus {
    transform: translateY(-50%) translateX(2px);
}

.hm-drawer-tab:focus-visible {
    outline: 2px solid var(--hm-color-green-dark);
    outline-offset: -4px;
}

/* Hide the edge tab whose drawer is open so it doesn't sit underneath the
 * panel as a duplicate affordance. */
.hm-drawer-tab[aria-expanded="true"] {
    visibility: hidden;
}

/* §50.12: the icon IS the tab vector now (`Shop menu tapered.svg` — a
 * full tapered pill with embedded "SHOP" letters). Sized to fill the
 * button (which matches the SVG's native 30×142). Drop-shadow follows
 * the tapered path via `filter: drop-shadow()` rather than `box-shadow`
 * on the button (which would clip to the bounding rectangle and reveal
 * the corner gap between the taper and the rect). */
.hm-drawer-tab__icon {
    width: 100%;
    height: 100%;
    display: block;
    pointer-events: none; /* clicks always hit the button */
    filter: drop-shadow(2px 2px 6px rgba(0, 0, 0, 0.22));
    transition: filter 220ms ease-out, transform 220ms ease-out;
}

.hm-drawer-tab:hover .hm-drawer-tab__icon,
.hm-drawer-tab:focus .hm-drawer-tab__icon {
    filter: drop-shadow(3px 2px 10px rgba(0, 0, 0, 0.32));
}

/* §50: Phase 7m's `.hm-drawer-tab[data-drawer-target="cart"]` enlarged-
 * edge-tab + `.hm-cart-trigger__icon` Lottie wrapper + `.hm-cart-trigger__tooltip`
 * hover-tooltip rules removed. The cart trigger is now the corner FAB
 * (.hm-cart-fab) defined further down in this file. Lottie is gone
 * (Adam called it "too complex for the initial iteration"); the FAB
 * renders a static SVG cart icon. */

/* Shared backdrop — one element, used by whichever drawer is open. */
.hm-drawer-backdrop {
    position: fixed;
    inset: 0;
    background: var(--hm-drawer-backdrop-color, rgba(0, 0, 0, 0.55));
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    z-index: 100001;
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms ease-out;
}

.hm-drawer-backdrop--visible {
    opacity: 1;
    pointer-events: auto;
}

/* Visually-hidden helper for icon-only triggers' accessible names. Mirrors
 * the .ty-hidden pattern from the responsive theme so screen readers
 * announce "Open navigation" while sighted users see only the icon. */
.hm-drawer-tab .hm-sr-only,
.hm-cart-fab .hm-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

@media (prefers-reduced-motion: reduce) {
    .hm-drawer,
    .hm-drawer-backdrop,
    .hm-drawer-tab,
    .hm-drawer-tractor,
    .hm-cart-fab {
        transition: none;
    }
    .hm-cart-fab:hover,
    .hm-cart-fab:active {
        transform: none;
    }
}

/* === Cart FAB (§50) ========================================================
 *
 * Bottom-right circular floating action button. Replaces the Phase 7m
 * enlarged edge-tab. Same drawer-target wiring as before
 * (data-drawer-target="cart") — drawer.js finds it via selector and
 * binds click to open. Persistent across scroll. Cart-trigger.js
 * manages the count badge text content.
 *
 * Tap target: 56×56 desktop / 48×48 mobile — both well above the 44
 * Apple HIG / WCAG AA recommended minimum.
 *
 * Z-index 90: above general page content but below drawer backdrop
 * (100001), so the FAB is visually covered when the cart drawer opens.
 * Matches the old jump-to-aisles z-index (FAB replaced that one in the
 * same corner; same stacking semantics).
 * ========================================================================== */

.hm-cart-fab {
    position: fixed;
    /* Phase 7-3a — raised 4vh off the bottom (team request). The 24px
     * base keeps a fixed minimum gap on very short viewports; +4vh lifts
     * it proportionally on taller screens so it clears the floating footer
     * / SMS banner region more comfortably. Mobile mirrors this below. */
    bottom: calc(24px + 4vh);
    right: 24px;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    /* §50.14: fresh-green at rest (was dark-green). The bag SVG's
     * dark-olive stroke had insufficient contrast against the prior
     * dark-green background — bag outline blended into the FAB
     * (Adam, 2026-05-24). Fresh green keeps the bag outline
     * legible in both rest and hover states. */
    background: var(--hm-color-green-fresh);
    color: #fff;
    border: 0;
    padding: 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
    z-index: 90;
    transition: transform 120ms ease-out, box-shadow 220ms ease-out;
}

.hm-cart-fab:hover {
    /* §50.14: hover keeps fresh-green bg (so the bag visibility is
     * stable across states) and adds a warm-gold accent ring via
     * a layered box-shadow. Gold tone matches the §46 testimonials
     * marquee accent + §48.5 bag-icon-on-aisle-card glow — brand
     * pattern for "you're interacting with this surface".
     *
     * §52: the prior rule combined `:hover` + `:focus`. Mouse-clicking
     * the FAB to open the drawer set `:focus`, drawer.js returned focus
     * to the FAB on close, and the gold ring persisted until the user
     * clicked elsewhere — Adam's "the glow sticks" bug. Splitting hover
     * from focus (focus-visible only for keyboard users below) clears
     * the ring as soon as the mouse leaves. */
    transform: scale(1.06);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25),
                0 0 0 4px rgba(240, 160, 32, 0.42);
    outline: none;
}

.hm-cart-fab:active {
    transform: scale(0.96);
}

.hm-cart-fab:focus-visible {
    outline: 3px solid #fff;
    outline-offset: 3px;
}

/* Hide the FAB when its drawer is open (matches the edge-tab convention
 * from §16.5 — the drawer backdrop covers the FAB visually anyway, but
 * `visibility: hidden` removes it from tab order so focus doesn't loop
 * back to a covered control). */
.hm-cart-fab[aria-expanded="true"] {
    visibility: hidden;
}

/* Icon: percentage-based sizing so the bag scales with the button.
 * §50.13 dropped the `aspect-ratio: 1/1` constraint that the earlier
 * line-cart SVG required — the designer's `bag-alt-colorways.svg`
 * vector is 18×15 (wider than tall), so forcing a square would
 * stretch it. Width: 70% in a 56-button → 39-wide bag; height auto
 * preserves the 18:15 ratio → ~33 tall. Bumped from 50% on Adam's
 * call — the bag reads more substantial at 70% on the dark-green
 * FAB. */
.hm-cart-fab__icon {
    width: 70%;
    height: auto;
    display: block;
    pointer-events: none;
}

/* Count badge: small circle anchored to the top-right of the FAB.
 * cart-trigger.js writes the numeric textContent on cart-state-change.
 * `:empty { display: none }` hides the badge at qty=0 — no class toggle
 * needed (matches the .hm-aisle-card__qty-badge pattern from §21.5). */
.hm-cart-fab__count-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--hm-color-green-darkest);
    color: var(--hm-color-cream);
    font-family: var(--hm-font-display);
    font-size: 12px;
    font-weight: 700;
    line-height: 22px;
    text-align: center;
    box-sizing: border-box;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
    pointer-events: none;
    /* Sits above the FAB's icon. Setting aria-hidden on the markup keeps
     * the count out of the screen-reader stream so the .hm-sr-only span
     * remains the authoritative label. */
}

.hm-cart-fab__count-badge:empty {
    display: none;
}

/* §52 — hover tooltip showing the cart subtotal. Sits to the LEFT of
 * the FAB (FAB is pinned bottom-right). cart-trigger.js writes the
 * formatted total ("$45.20") into textContent on every cart change;
 * empty cart → empty textContent → `:empty { display: none }` hides it.
 *
 * Shown on `:hover` only (not `:focus` — same stickiness-bug logic as
 * the FAB ring; mouse-click returns focus to the FAB after drawer-close
 * and would leave the tooltip hanging visible). */
.hm-cart-fab__tooltip {
    position: absolute;
    top: 50%;
    right: calc(100% + 12px);
    transform: translateY(-50%);
    background: var(--hm-color-green-darkest);
    color: var(--hm-color-cream);
    padding: 6px 12px;
    border-radius: 999px;
    font-family: var(--hm-font-display);
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.2px;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 160ms ease-out, transform 160ms ease-out;
    box-shadow: -2px 2px 10px rgba(0, 0, 0, 0.20);
}

.hm-cart-fab__tooltip:empty {
    display: none;
}

.hm-cart-fab:hover .hm-cart-fab__tooltip {
    opacity: 1;
    transform: translateY(-50%) translateX(-2px);
}

@media (max-width: 576px) {
    .hm-cart-fab {
        /* Phase 7-3a — +4vh lift, mirroring the desktop rule. */
        bottom: calc(16px + 4vh);
        right: 16px;
        width: 48px;
        height: 48px;
    }
    .hm-cart-fab__count-badge {
        min-width: 20px;
        height: 20px;
        line-height: 20px;
        font-size: 11px;
    }
    /* Tooltip likely never shows on real mobile (no hover device) but
     * shrink it for tablet/desktop-on-mobile-emulation breakpoints. */
    .hm-cart-fab__tooltip {
        font-size: 12px;
        padding: 5px 10px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .hm-cart-fab__tooltip {
        transition: opacity 0ms linear;
    }
    .hm-cart-fab:hover .hm-cart-fab__tooltip {
        transform: translateY(-50%);
    }
}

/* ============================================================================
 * === Drawer tractor (Phase 7m — Lottie ride-along) ==========================
 * ============================================================================
 *
 * A Lottie tractor renders at the leading edge of each drawer panel and
 * rides alongside as the drawer slides in/out. The tractor element is
 * fixed-positioned at body level (not a child of the panel — drawer has
 * `overflow-y: auto` which would clip an outside-the-edge child).
 *
 * Sync mechanism: tractor uses the same transition duration + easing as
 * the drawer panel itself (450ms ease-out via --hm-drawer-slide-duration).
 * Closed-state transform mirrors the drawer's translateX(±drawer-width),
 * open-state collapses to translateX(0) — so when the drawer animates
 * from -100% → 0, the tractor animates the same delta in step.
 *
 * The source Lottie paints the tractor facing left. Left-drawer tractor
 * keeps that orientation; right-drawer gets `scaleX(-1)` to mirror.
 *
 * Pointer-events: none — tractor never intercepts clicks meant for the
 * page or the drawer's close affordance. */
.hm-drawer-tractor {
    position: fixed;
    bottom: 16vh;
    width: 132px;
    height: 132px;
    pointer-events: none;
    z-index: 100002; /* same plane as the drawer panel */
    opacity: 0;
    /* Default (closed) transition: opacity fades over the full slide
     * duration so the tractor melts away as the drawer closes. */
    transition: transform var(--hm-drawer-slide-duration, 450ms) ease-out,
                opacity var(--hm-drawer-slide-duration, 450ms) ease-out;
    will-change: transform, opacity;
}

/* Open-state transition: snap opacity to full immediately so the tractor
 * is fully visible the moment it starts sliding in (Adam's preference —
 * gives the "pulling the drawer out" effect its full weight on entry).
 * Transform still animates over the slide duration so tracking stays
 * locked to the panel.
 *
 * The asymmetry — instant-in, fade-out — comes from CSS resolving
 * transitions against the *current* matching rule: when --open is added
 * the more-specific .hm-drawer-tractor--open rule applies (opacity 0s),
 * and when --open is removed the element falls back to the base rule
 * (opacity 450ms). */
.hm-drawer-tractor--open {
    opacity: 1;
    transition: transform var(--hm-drawer-slide-duration, 450ms) ease-out,
                opacity 0s linear;
}

@media (max-width: 768px) {
    .hm-drawer-tractor {
        width: 96px;
        height: 96px;
        bottom: 14vh;
    }
}

/* Anchor distance from the drawer's pinned edge. On mobile the drawer
 * is 85vw wide, which on narrow viewports leaves less than the tractor's
 * width between the drawer's leading edge and the viewport edge — the
 * tractor would render half off-screen. min() caps the anchor at
 * (viewport - tractor-width - 8px gutter) so the tractor is always fully
 * inside the viewport. On viewports where 85vw fits comfortably the cap
 * is a no-op. On desktop the drawer is a fixed 400px so the anchor is
 * just the literal pixel value.
 *
 * Custom property used so `left:` and `transform: translateX(...)` share
 * exactly the same resolved value — that's what keeps the tractor's
 * closed-state translate matching the drawer's translateX(-100%), so
 * they move in lockstep during the slide. */
.hm-drawer-tractor[data-drawer-side="left"] {
    --hm-tractor-anchor: min(85vw, calc(100vw - 96px - 8px));
    left: var(--hm-tractor-anchor);
    transform: translateX(calc(-1 * var(--hm-tractor-anchor)));
}

@media (min-width: 769px) {
    .hm-drawer-tractor[data-drawer-side="left"] {
        --hm-tractor-anchor: 400px; /* matches --hm-drawer-width-desktop */
    }
}

/* Right drawer: mirrored. Same anchor cap; the SVG flips via scaleX. */
.hm-drawer-tractor[data-drawer-side="right"] {
    --hm-tractor-anchor: min(85vw, calc(100vw - 96px - 8px));
    right: var(--hm-tractor-anchor);
    transform: translateX(var(--hm-tractor-anchor)) scaleX(-1);
}

@media (min-width: 769px) {
    .hm-drawer-tractor[data-drawer-side="right"] {
        --hm-tractor-anchor: 400px;
    }
}

/* Open state: collapse the translate to 0 so the tractor parks at its
 * anchored position (just outside the drawer's leading edge). The
 * scaleX(-1) on right-side is preserved. */
.hm-drawer-tractor--open[data-drawer-side="left"] {
    transform: translateX(0);
}

.hm-drawer-tractor--open[data-drawer-side="right"] {
    transform: translateX(0) scaleX(-1);
}

.hm-drawer-tractor svg {
    display: block;
    width: 100%;
    height: 100%;
}

/* ============================================================================
 * === Drawer nav (Phase 7b — left drawer content) ============================
 * ============================================================================
 *
 * Search input (Searchanise auto-attaches via the existing
 * form[name="search_form"] input[name="q"] selector — see NOTES §17.1).
 * Category tree with chevron-toggled accordion.
 * About Harvestly + Other hardcoded link lists.
 *
 * Touch-target rule: every clickable row is ≥ 44px tall (a11y standard).
 */

/* Force the nav drawer to use flex column so sections lay out cleanly
 * and the search/categories stick to the top, About/Other below them. */
.hm-drawer-nav .hm-drawer-content {
    display: flex;
    flex-direction: column;
    gap: 22px;
    padding-top: 64px; /* clear the close button */
}

/* --- Sections / headers ---------------------------------------------------- */

.hm-drawer-section {
    flex: 0 0 auto;
}

.hm-drawer-section__header {
    margin: 0 0 8px;
    padding: 0;
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #6b6b6b;
}

/* --- Category tree --------------------------------------------------------- */

.hm-drawer-category-tree {
    /* No background — list rows draw their own borders. */
}

.hm-drawer-category-tree__list,
.hm-drawer-category-tree__sublist {
    list-style: none;
    margin: 0;
    padding: 0;
}

.hm-drawer-category-tree__item {
    border-bottom: 1px solid #ececec;
}

.hm-drawer-category-tree__item:last-child {
    border-bottom: 0;
}

.hm-drawer-category-tree__row {
    display: flex;
    align-items: stretch;
    min-height: 44px;
}

.hm-drawer-category-tree__link {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    padding: 10px 8px;
    color: #2b2b2b;
    text-decoration: none;
    font-size: 15px;
    font-weight: 500;
    line-height: 1.25;
    border-radius: 4px 0 0 4px;
    transition: background-color 120ms ease-out, color 120ms ease-out;
}

.hm-drawer-category-tree__link:hover,
.hm-drawer-category-tree__link:focus {
    background: rgba(33, 112, 59, 0.08);
    color: #21703b;
    text-decoration: none;
    outline: none;
}

.hm-drawer-category-tree__link:focus-visible {
    outline: 2px solid #21703b;
    outline-offset: -2px;
}

.hm-drawer-category-tree__chevron {
    flex: 0 0 auto;
    width: 44px; /* ≥44px touch target */
    border: 0;
    background: transparent;
    color: #6b6b6b;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0 4px 4px 0;
    transition: background-color 120ms ease-out, color 120ms ease-out;
}

.hm-drawer-category-tree__chevron svg {
    width: 18px;
    height: 18px;
    transition: transform 200ms ease-out;
}

.hm-drawer-category-tree__chevron[aria-expanded="true"] svg {
    transform: rotate(180deg);
}

.hm-drawer-category-tree__chevron:hover,
.hm-drawer-category-tree__chevron:focus {
    background: rgba(33, 112, 59, 0.08);
    color: #21703b;
    outline: none;
}

.hm-drawer-category-tree__chevron:focus-visible {
    outline: 2px solid #21703b;
    outline-offset: -2px;
}

.hm-drawer-category-tree__sublist {
    background: #fafafa;
    border-top: 1px solid #ececec;
    padding: 4px 0;
}

.hm-drawer-category-tree__sublist[hidden] {
    display: none;
}

.hm-drawer-category-tree__sublist li {
    border-bottom: 0;
}

.hm-drawer-category-tree__sublist a {
    display: flex;
    align-items: center;
    min-height: 40px;
    padding: 8px 16px 8px 24px;
    color: #444;
    text-decoration: none;
    font-size: 14px;
    line-height: 1.25;
    transition: background-color 120ms ease-out, color 120ms ease-out;
}

.hm-drawer-category-tree__sublist a:hover,
.hm-drawer-category-tree__sublist a:focus {
    background: rgba(33, 112, 59, 0.06);
    color: #21703b;
    text-decoration: none;
    outline: none;
}

.hm-drawer-category-tree__sublist a:focus-visible {
    outline: 2px solid #21703b;
    outline-offset: -2px;
}

.hm-drawer-category-tree__view-all a {
    color: #21703b;
    font-weight: 600;
}

/* Error state for tree fetch failure. */
.hm-drawer-category-tree__error {
    padding: 12px;
    background: #fef6e6;
    border: 1px solid #f0d28a;
    border-radius: 6px;
    text-align: center;
}

.hm-drawer-category-tree__error p {
    margin: 0 0 8px;
    color: #855e1a;
    font-size: 14px;
}

.hm-drawer-category-tree__retry {
    border: 1px solid #21703b;
    background: #fff;
    color: #21703b;
    padding: 6px 16px;
    border-radius: 4px;
    font-weight: 600;
    cursor: pointer;
    min-height: 36px;
    transition: background-color 120ms ease-out, color 120ms ease-out;
}

.hm-drawer-category-tree__retry:hover,
.hm-drawer-category-tree__retry:focus {
    background: #21703b;
    color: #fff;
    outline: none;
}

.hm-drawer-category-tree__empty {
    margin: 0;
    padding: 12px 0;
    color: #888;
    font-style: italic;
    font-size: 14px;
}

/* --- About / Other hardcoded lists ---------------------------------------- */

.hm-drawer-nav-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.hm-drawer-nav-list li {
    border-bottom: 1px solid #ececec;
}

.hm-drawer-nav-list li:last-child {
    border-bottom: 0;
}

.hm-drawer-nav-list a {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 10px 8px;
    color: #2b2b2b;
    text-decoration: none;
    font-size: 15px;
    line-height: 1.25;
    border-radius: 4px;
    transition: background-color 120ms ease-out, color 120ms ease-out;
}

.hm-drawer-nav-list a:hover,
.hm-drawer-nav-list a:focus {
    background: rgba(33, 112, 59, 0.08);
    color: #21703b;
    text-decoration: none;
    outline: none;
}

.hm-drawer-nav-list a:focus-visible {
    outline: 2px solid #21703b;
    outline-offset: -2px;
}

/* --- Skeleton loader ------------------------------------------------------- */

.hm-drawer-category-tree__skeleton {
    list-style: none;
    margin: 0;
    padding: 0;
}

.hm-skeleton {
    background: linear-gradient(90deg, #ececec 0%, #f5f5f5 50%, #ececec 100%);
    background-size: 200% 100%;
    animation: hm-skeleton-shimmer 1.4s ease-in-out infinite;
    border-radius: 4px;
}

.hm-skeleton--row {
    height: 44px;
    margin-bottom: 6px;
}

.hm-skeleton--row:last-child {
    margin-bottom: 0;
}

@keyframes hm-skeleton-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .hm-skeleton {
        animation: none;
        background: #ececec;
    }
    .hm-drawer-category-tree__chevron svg {
        transition: none;
    }
}

/* === Drawer nav additions (Phase 7c) === */

/* Harvestly logo at top of left drawer (decorative). Constrains by
 * height so the rectangular site logo doesn't overflow the drawer
 * width; aspect-ratio is preserved. If a future square mark replaces
 * the rectangular Harvestly_LogoTag_K.png, no CSS change needed. */
.hm-drawer-nav__logo {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 4px 0 8px;
    flex: 0 0 auto;
}

.hm-drawer-nav__logo img {
    max-width: 80%;
    max-height: 70px;
    height: auto;
    width: auto;
    display: block;
}

/* Collapsible section header (About / Other in the left drawer).
 * Visually mirrors the static .hm-drawer-section__header but is now
 * a button that toggles a chevron. Independent of the categories
 * accordion — opening one doesn't close the other. */
.hm-drawer-section--collapsible .hm-drawer-section__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    min-height: 44px;
    padding: 8px 0;
    margin: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    text-align: left;
    color: inherit;
    border-radius: 4px;
    transition: background-color 120ms ease-out;
}

.hm-drawer-section--collapsible .hm-drawer-section__toggle:hover,
.hm-drawer-section--collapsible .hm-drawer-section__toggle:focus {
    background: rgba(33, 112, 59, 0.06);
    outline: none;
}

.hm-drawer-section--collapsible .hm-drawer-section__toggle:focus-visible {
    outline: 2px solid #21703b;
    outline-offset: -2px;
}

/* Reset the inner __header span styling so it inherits the static
 * header look (uppercase, letter-spacing) — but as a span inside the
 * button, not its own h2. */
.hm-drawer-section--collapsible .hm-drawer-section__header {
    margin: 0;
    flex: 1 1 auto;
}

.hm-drawer-section__chevron {
    width: 18px;
    height: 18px;
    flex: 0 0 auto;
    color: #6b6b6b;
    transition: transform 200ms ease-out;
}

.hm-drawer-section__toggle[aria-expanded="true"] .hm-drawer-section__chevron {
    transform: rotate(180deg);
}

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

/* CS-Cart's loading spinner (.ty-ajax-loading-box) ships at z-index 100001
 * — the same as our backdrop (NOTES §16.5). When the cart drawer is open
 * and an in-drawer cm-ajax fires, the spinner is centered on the viewport
 * but blurred-out behind our backdrop. Bump it above the drawer panel
 * (100002) so it's visible during +/- and × clicks. Below the toast stack
 * (2147483647) so error toasts can still surface over it.
 *
 * Visual swap: spinner.js mounts a Lottie bunny into a <div.hm-bunny-spinner>
 * child of every CS-Cart spinner host (currently .ty-ajax-loading-box and
 * .owl-item.loading) and tags the host with `.hm-spinner-mounted`. The
 * wrapper child keeps the bunny isolated from CS-Cart's own
 * `a.html(statusContent)` writes — a MutationObserver re-appends the
 * wrapper if it gets wiped. CSS below: cream pill on the global box,
 * circle ::before suppressed on any host, bunny centered + sized inside
 * its wrapper. If the Lottie load fails, spinner.js removes the class +
 * wrapper and the stock GIF reappears. */
.ty-ajax-loading-box {
    z-index: 100100 !important;
}

.ty-ajax-loading-box.hm-spinner-mounted {
    background: #f5efdc !important;
    background-image: none !important;
    opacity: 1 !important;
    width: 96px !important;
    height: 96px !important;
    min-height: 96px !important;
    border-radius: 18px !important;
    box-shadow: 0 6px 24px rgba(33, 40, 47, 0.18);
    padding: 8px;
    box-sizing: border-box;
}

/* Suppress the stock rotating-circle ::before on any host carrying a bunny
 * — covers .ty-ajax-loading-box, .owl-item.loading, and any future surface
 * spinner.js mounts into. */
.hm-spinner-mounted::before {
    content: none !important;
}

/* The bunny wrapper is the actual lottie container. Fills its host and
 * keeps the SVG centered. Pointer-events off so it doesn't block clicks
 * on host content (matters for .owl-item.loading where the card itself
 * is interactive). */
.hm-bunny-spinner {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.hm-bunny-spinner svg {
    display: block;
    width: 100%;
    height: 100%;
    max-width: 80px;
    max-height: 80px;
}

.ty-ajax-loading-box.hm-spinner-mounted svg {
    display: block;
    width: 100% !important;
    height: 100% !important;
}

/* ============================================================================
 * === Cart drawer (Phase 7c) =================================================
 * ============================================================================
 *
 * Right drawer body. Layout (§49 simplified after auth-chrome removal):
 *   - Cart body (the cm-ajax slicing target #hm_cart_drawer_body):
 *     - Items list (scrollable when tall, flex: 1)
 *     - Footer (sticky bottom): subtotal + checkout-or-signup CTAs
 *
 * The flex column on .hm-drawer-cart .hm-drawer-content + overflow:hidden
 * on .hm-drawer (set higher up) drive the "items list scrolls, footer
 * stays" behavior. Account dropdown + greeting + login CTAs were removed
 * in §49 — account moved to the header (§34); guest-checkout +
 * signup CTAs live in the footer now (cart_drawer_content.tpl).
 */

.hm-drawer-cart .hm-drawer-content {
    overflow: hidden;
    display: flex;
    flex-direction: column;
    padding-top: 56px;
    padding-bottom: 0;
    gap: 0;
}

/* The cm-ajax slicing block: takes remaining vertical space and
 * lays out items + footer as a flex column itself. */
.hm-cart-drawer__body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0; /* allows nested flex children to overflow correctly */
}

.hm-cart-drawer__items {
    flex: 1 1 auto;
    list-style: none;
    margin: 0;
    padding: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

.hm-cart-drawer__item {
    display: grid;
    grid-template-columns: 80px 1fr auto;
    gap: 12px;
    padding: 12px 0;
    border-bottom: 1px solid #ececec;
    align-items: start;
}

.hm-cart-drawer__item-thumb {
    width: 80px;
    height: 80px;
    overflow: hidden;
    border-radius: 4px;
    background: #f5f5f5;
    display: flex;
    align-items: center;
    justify-content: center;
}

.hm-cart-drawer__item-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.hm-cart-drawer__item-details {
    min-width: 0; /* allows ellipsis on the title */
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.hm-cart-drawer__item-title {
    color: #2b2b2b;
    font-size: 14px;
    font-weight: 500;
    line-height: 1.3;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.hm-cart-drawer__item-title:hover,
.hm-cart-drawer__item-title:focus {
    color: #21703b;
    text-decoration: underline;
}

.hm-cart-drawer__item-price {
    font-size: 14px;
    font-weight: 600;
    color: #21703b;
}

.hm-cart-drawer__item-stepper {
    display: inline-flex;
    align-items: center;
    border: 1px solid #d0d0d0;
    border-radius: 4px;
    overflow: hidden;
    width: max-content;
    margin-top: 4px;
}

.hm-cart-drawer__qty-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background: #fff;
    color: #2b2b2b;
    text-decoration: none;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    transition: background-color 120ms ease-out;
}

.hm-cart-drawer__qty-btn:hover,
.hm-cart-drawer__qty-btn:focus {
    background: #21703b;
    color: #f5efdc;
    text-decoration: none;
    outline: none;
}

.hm-cart-drawer__qty-btn--disabled {
    color: #ccc;
    cursor: not-allowed;
    pointer-events: none;
}

.hm-cart-drawer__qty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    height: 32px;
    padding: 0 6px;
    font-size: 14px;
    font-weight: 600;
    color: #2b2b2b;
    border-left: 1px solid #d0d0d0;
    border-right: 1px solid #d0d0d0;
    background: #f8f8f8;
}

.hm-cart-drawer__item-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background: transparent;
    color: #999;
    text-decoration: none;
    font-size: 22px;
    line-height: 1;
    border-radius: 4px;
    transition: background-color 120ms ease-out, color 120ms ease-out;
}

.hm-cart-drawer__item-remove:hover,
.hm-cart-drawer__item-remove:focus {
    background: #fde8e8;
    color: #c43e3e;
    outline: none;
}

.hm-cart-drawer__item-remove:focus-visible {
    outline: 2px solid #c43e3e;
    outline-offset: -2px;
}

.hm-cart-drawer__footer {
    flex: 0 0 auto;
    padding: 12px 0 16px;
    border-top: 2px solid #ececec;
    background: #fff;
}

.hm-cart-drawer__subtotal {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 12px;
}

.hm-cart-drawer__subtotal-label {
    font-size: 14px;
    font-weight: 600;
    color: #6b6b6b;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.hm-cart-drawer__subtotal-amount {
    font-size: 18px;
    font-weight: 700;
    color: #21703b;
}

.hm-cart-drawer__checkout {
    display: block;
    width: 100%;
    padding: 14px 16px;
    background: #21703b;
    color: #f5efdc;
    font-size: 16px;
    font-weight: 700;
    text-align: center;
    text-decoration: none;
    border-radius: 6px;
    box-sizing: border-box;
    transition: background-color 120ms ease-out;
}

.hm-cart-drawer__checkout:hover,
.hm-cart-drawer__checkout:focus {
    background: #1a5a2e;
    color: #f5efdc;
    text-decoration: none;
    outline: none;
}

.hm-cart-drawer__checkout:focus-visible {
    outline: 2px solid #fff;
    outline-offset: -4px;
}

/* === §49 — Logged-out CTAs: signup primary, guest checkout secondary ===
 *
 * Replace the prior "Log in to checkout" + small "Sign up" pair with a
 * promo-driven signup CTA + understated guest-checkout link. The signup
 * CTA mirrors .hm-cart-drawer__checkout's visual weight (full-width,
 * brand green, prominent) but uses a script-flavored display font to
 * read as a promotional offer rather than a transactional button.
 * The guest-checkout link is plain text with a chevron — visible and
 * tappable, but visually deferential to the signup ask.
 *
 * Both live INSIDE the cm-ajax slicing block so the dynamic savings
 * amount refreshes on every qty change. */

.hm-cart-drawer__signup-save {
    display: block;
    width: 100%;
    padding: 14px 16px;
    background: var(--hm-color-green-dark);
    color: #fff;
    font-family: var(--hm-font-display);
    font-size: 16px;
    font-weight: 700;
    text-align: center;
    text-decoration: none;
    border-radius: 6px;
    box-sizing: border-box;
    transition: background-color 120ms ease-out, transform 120ms ease-out;
    box-shadow: 0 2px 8px rgba(45, 62, 16, 0.15);
}

.hm-cart-drawer__signup-save:hover,
.hm-cart-drawer__signup-save:focus {
    background: var(--hm-color-green-fresh);
    color: #fff;
    text-decoration: none;
    outline: none;
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(45, 62, 16, 0.18);
}

.hm-cart-drawer__signup-save:focus-visible {
    outline: 2px solid #fff;
    outline-offset: -4px;
}

.hm-cart-drawer__signup-save:active {
    transform: translateY(0);
    box-shadow: 0 1px 4px rgba(45, 62, 16, 0.15);
}

.hm-cart-drawer__guest-checkout {
    display: block;
    margin-top: 10px;
    padding: 4px 8px;
    text-align: center;
    color: var(--hm-color-green-darkest);
    font-family: var(--hm-font-body);
    font-size: 13px;
    font-weight: 500;
    text-decoration: none;
    opacity: 0.7;
    transition: opacity 120ms ease-out, color 120ms ease-out;
}

.hm-cart-drawer__guest-checkout:hover,
.hm-cart-drawer__guest-checkout:focus {
    color: var(--hm-color-green-dark);
    text-decoration: underline;
    opacity: 1;
    outline: none;
}

.hm-cart-drawer__guest-checkout:focus-visible {
    outline: 2px solid var(--hm-color-green-dark);
    outline-offset: 2px;
    text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
    .hm-cart-drawer__signup-save,
    .hm-cart-drawer__guest-checkout {
        transition: none;
    }
    .hm-cart-drawer__signup-save:hover,
    .hm-cart-drawer__signup-save:focus,
    .hm-cart-drawer__signup-save:active {
        transform: none;
    }
}

/* §52 — Drawer title "My [Harvestly logo] Bag". Sits OUTSIDE the
 * cm-ajax slicing block (inside .hm-drawer-content, sibling of
 * #hm_cart_drawer_body). Warm-gold drop-shadow filter gives the
 * "glow" Adam asked for — matches the §46 testimonials marquee +
 * §48.5 bag-icon-on-aisle-card + §50.14 cart-FAB hover ring brand
 * pattern. Logo SVG inherits color from the title's currentColor. */
.hm-cart-drawer__title {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 20px 16px 16px;
    color: var(--hm-color-green-dark);
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: 22px;
    line-height: 1;
    border-bottom: 1px solid var(--hm-color-linen);
    margin-bottom: 8px;
    /* Glow. Layered drop-shadows: a tight inner halo + a wider soft
     * outer glow. Warm gold matches the §46/§48 brand precedent. */
    filter: drop-shadow(0 0 4px rgba(240, 160, 32, 0.55))
            drop-shadow(0 0 12px rgba(240, 160, 32, 0.25));
}

.hm-cart-drawer__title-text {
    display: inline-block;
}

.hm-cart-drawer__title-logo {
    /* Height matches the surrounding text size; width auto-scales by
     * SVG viewBox aspect (500:102 = ~4.9:1, so 28-tall → ~138-wide).
     * Color inherits from the parent's currentColor — the inline SVG
     * paths use fill="currentColor". */
    width: auto;
    height: 28px;
    color: currentColor;
    display: inline-block;
    vertical-align: middle;
}

@media (max-width: 576px) {
    .hm-cart-drawer__title {
        font-size: 18px;
        padding: 16px 12px 12px;
        gap: 6px;
    }
    .hm-cart-drawer__title-logo {
        height: 22px;
    }
}

/* Empty state */
.hm-cart-drawer__empty {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 40px 20px;
    gap: 16px;
}

/* §52 — Lottie animation container above the "Your Bag's Empty"
 * message. cart-empty.js mounts the `Grocery.json` animation into
 * this element when the empty-state markup is present in the DOM.
 * Native Lottie canvas is 48×48 — we scale up 3× for hero presence
 * without losing crispness (Lottie SVG renderer is vector). */
.hm-cart-drawer__empty-lottie {
    width: 160px;
    height: 160px;
    margin: 0 auto;
    pointer-events: none;
}

.hm-cart-drawer__empty-message {
    margin: 0;
    color: var(--hm-color-green-darkest);
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: 18px;
    letter-spacing: 0.01em;
}

.hm-cart-drawer__empty-cta {
    display: inline-block;
    padding: 10px 20px;
    background: transparent;
    color: #21703b;
    border: 1px solid #21703b;
    border-radius: 6px;
    font-weight: 600;
    text-decoration: none;
    transition: background-color 120ms ease-out, color 120ms ease-out;
}

.hm-cart-drawer__empty-cta:hover,
.hm-cart-drawer__empty-cta:focus {
    background: #21703b;
    color: #fff;
    text-decoration: none;
    outline: none;
}

/* ============================================================================
 * === Shop page (Phase 7e — /shop/<category>/ route + page shell) ===========
 * ============================================================================
 *
 * Renders the page heading + a vertical stack of aisle skeletons.
 * Skeleton card dimensions match the eventual carousel-card footprint
 * (7e2) so that swap-in doesn't cause layout shift. Reuses .hm-skeleton
 * from §17.7 (drawer nav) for the shimmer + reduced-motion behavior.
 * ========================================================================= */

/* Phase 7-1h follow-up — Break the Bootstrap-2.3 .span16 fixed-width
 * snap above our shop wrappers. CS-Cart's responsive theme renders
 * page bodies inside `<div class="span16">`; .span16's compiled
 * widths are 940px/1175px/726px at three breakpoints, with no
 * continuous range between them.
 *
 * Pre-7-1h this didn't matter — aisle cards were fixed at 200px and
 * the carousel just clipped at whichever .span16 width was active.
 * 7-1h's aisle-carousel.js ResizeObserver reads section width to
 * compute card width; without this override the observed width only
 * jumps at .span16 breakpoints, never matches viewport movement,
 * and dynamic sizing reads as "snaps at the same Bootstrap
 * boundaries the rest of the theme uses."
 *
 * Same mechanism §45.12j applied to the footer's .span16 wrapper.
 * The general rule (worth banking): when overlaying our own blocks
 * over stock CS-Cart containers that have Bootstrap-grid widths,
 * audit every layer of the parent chain. .span16 sets fixed widths
 * via media queries; we have to !important them off to get truly
 * fluid sizing.
 *
 * Scope to the four shop wrappers (.hm-shop landing/category,
 * .hm-vendor slug page, .hm-vendor-directory, .hm-product PDP). The
 * homepage, drawer, checkout, and other surfaces keep stock Bootstrap
 * behavior. */
.span16:has(.hm-shop),
.span16:has(.hm-vendor),
.span16:has(.hm-vendor-directory),
.span16:has(.hm-product) {
    float: none !important;
    width: auto !important;
    max-width: none !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
}

.hm-shop {
    max-width: 1280px;
    margin: 0 auto;
    padding: 24px 16px 48px;
}

/* Breadcrumb — Shop > <Top> [> <Sub>]. Renders above the h1 on every
 * shop category page. Last item (the current page) is non-linked and
 * carries aria-current="page". Separators are aria-hidden chevrons. */
/* Breadcrumb — Open Sans via --hm-font-body, brand-tinted muted
 * color (Phase 7-1m §60). The `rgba(45,62,16,0.65)` is derived from
 * `--hm-color-green-darkest` #2D3E10 at 65% alpha — reads as
 * intentional "below-the-page-name navigation" rather than the prior
 * neutral grey #6a6a6a. NOT applied via `opacity` because that would
 * dim the child link/separator/current-item colors too; we want the
 * children to remain at full strength when they override `color`. */
.hm-shop__breadcrumb {
    margin: 0 0 6px;
    font-family: var(--hm-font-body);
    font-size: 13px;
    color: rgba(45, 62, 16, 0.65);
}

.hm-shop__breadcrumb-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

.hm-shop__breadcrumb-item {
    display: inline-flex;
    align-items: center;
    line-height: 1.4;
}

.hm-shop__breadcrumb-sep {
    margin: 0 8px;
    color: rgba(45, 62, 16, 0.32);
    font-size: 14px;
    line-height: 1;
}

.hm-shop__breadcrumb-link {
    color: var(--hm-color-green-dark);
    text-decoration: none;
    transition: color 120ms ease, text-decoration-color 120ms ease;
    /* Underline appears on hover only — keeps the row visually quiet
     * by default since the chain can run 3 levels deep. */
    text-decoration: underline transparent;
    text-underline-offset: 2px;
}
.hm-shop__breadcrumb-link:hover,
.hm-shop__breadcrumb-link:focus-visible {
    color: var(--hm-color-green-fresh);
    text-decoration-color: currentColor;
    outline: none;
}
.hm-shop__breadcrumb-link:focus-visible {
    outline: 2px solid var(--hm-color-green-dark);
    outline-offset: 2px;
    border-radius: 2px;
}

.hm-shop__breadcrumb-item--current {
    color: var(--hm-color-green-darkest);
    font-weight: 600;
}

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

/* Page H1 — Poppins via --hm-font-headline (Phase 7-1m §60). Page-defining
 * heading role: Poppins's geometric confidence reads as "this is the
 * page" and pairs with Nunito-driven section headings beneath. Tight
 * tracking + 1.15 line-height matches the homepage's confident-display
 * pattern (-0.01em on display sizes; tighter still on the hero). */
.hm-shop__heading {
    margin: 8px 0 12px;
    font-family: var(--hm-font-headline);
    font-size: clamp(28px, 3.5vw, 36px);
    font-weight: 700;
    color: var(--hm-color-green-dark);
    line-height: 1.15;
    letter-spacing: -0.01em;
}

/* Subtitle: the leading <h1>/<h2> extracted from the category
 * description (the rest is dropped — see hm_shop.php). Render at a
 * comfortable mid-weight; the inner <h2><strong>...</strong></h2>
 * markup carries its own font weight via the stock heading style,
 * we just neutralize the default heading margins so it sits closer
 * to the h1 above. */
.hm-shop__subtitle {
    margin: 0 0 28px;
    color: #4a4a4a;
}

.hm-shop__subtitle h1,
.hm-shop__subtitle h2 {
    margin: 0;
    font-size: 18px;
    font-weight: 500;
    line-height: 1.3;
    color: #4a4a4a;
}

.hm-shop__empty {
    padding: 48px 16px;
    text-align: center;
    color: #6b6b6b;
    font-size: 16px;
}

.hm-shop__empty p {
    margin: 0;
}

.hm-shop__aisles {
    display: flex;
    flex-direction: column;
    gap: 40px;
}

/* --- Individual aisle section --------------------------------------------- */

.hm-aisle {
    /* No background / border by design — aisles read as parts of one page,
     * not as cards. Separation comes from inter-aisle gap. */
}

/* Aisle section divider — leafy-tinted (Phase 7-1m §60). Matches the
 * `.hm-aisle-card` border tone so the row's chrome reads as one
 * coherent brand-restrained outline system. */
.hm-aisle__header {
    margin-bottom: 14px;
    padding-bottom: 6px;
    border-bottom: 1px solid rgba(45, 62, 16, 0.08);
}

/* Aisle heading — Nunito via --hm-font-display (Phase 7-1m §60). Matches
 * the homepage carousel titles (.hm-pc__title / .hm-vc__title at lines
 * 4307-4315). The aisle row plays the same role across pages — a
 * carousel-of-products group label — so it gets the same Nunito 700
 * green-darkest treatment with -0.01em tracking and clamp(20-26px) for
 * fluid sizing. Border-bottom divider preserved (set on `.hm-aisle__header`). */
.hm-aisle__heading {
    margin: 0;
    font-family: var(--hm-font-display);
    font-size: clamp(20px, 2.4vw, 26px);
    font-weight: 700;
    color: var(--hm-color-green-darkest);
    line-height: 1.2;
    letter-spacing: -0.01em;
}

/* --- Skeleton row (replaced by carousel in 7e2) --------------------------- */

.hm-aisle__skeleton {
    display: flex;
    gap: 12px;
    overflow: hidden; /* match the horizontal-scroll viewport of the future carousel */
}

.hm-aisle__skeleton-card {
    /* Phase 7-1h — skeleton picks up the same --hm-aisle-card-width
     * variable the real cards use, so first-paint (server-rendered
     * skeleton, pre-JS) reads at the clamp() fallback width, and the
     * moment JS mounts and computes a precise width via ResizeObserver,
     * the skeleton resizes in lockstep with the real cards that
     * replace it. aspect-ratio 5/7 approximates the natural card
     * shape (was 200×280 = 5:7) so the carousel's vertical bound is
     * stable across the skeleton-to-real-cards swap. */
    flex: 0 0 auto;
    width: var(--hm-aisle-card-width, clamp(160px, 18vw, 240px));
    aspect-ratio: 5 / 7;
}

/* Mobile: page padding tightens; headings shrink. Card sizing is
 * driven by --hm-aisle-card-width now (Phase 7-1h) so the previous
 * discrete 200→160 skeleton override at 576px is gone — the skeleton
 * shares the dynamic variable with the real cards. */
@media (max-width: 576px) {
    .hm-shop {
        padding: 16px 12px 32px;
    }

    .hm-shop__aisles {
        gap: 32px;
    }
    /* §60: discrete `.hm-shop__heading` 32→26 and `.hm-aisle__heading`
     * 22→18 mobile overrides removed — both selectors now use clamp()
     * which handles narrow viewports at the 28px / 20px floor and
     * doesn't need a separate breakpoint rule. */
}

/* ============================================================================
 * === Aisle carousel (Phase 7e2 — virtualized aisle products on /shop/) =====
 * ============================================================================
 *
 * Horizontal carousel per aisle. Lazy fetched + DOM-appended; once all
 * products are loaded the JS clones cards to enable invisible wrap.
 *
 * Card layout contract for 7f's quick-add button: each card splits
 * into __top (image + reserved quick-add slot) and __bottom (anchor
 * to product page holding title + price). The slot at
 * .hm-aisle-card__quick-add-slot is empty in 7e2; 7f fills it.
 *
 * Dimensions match the §19.7 skeleton contract: 200x280 desktop /
 * 160x220 at <=576px. The swap-in from skeleton to real cards causes
 * no layout shift.
 * ========================================================================= */

.hm-aisle__carousel {
    /* Phase 7-1h — fixed min-height dropped. Cards size intrinsically
     * now (aspect-ratio on the image region + content-height body),
     * and the skeleton uses the same aspect-ratio shape, so the
     * carousel wrapper height equals whatever the cards/skeleton
     * naturally occupy. No reflow on skeleton-to-real swap because
     * both share the --hm-aisle-card-width variable and the same
     * aspect-ratio fallback. */
}

.hm-aisle-carousel {
    /* Positioning context for the absolute-positioned chevrons.
     * The wheel-locked outline is rendered on .hm-aisle (the parent
     * <section>) so it frames the header + carousel together. */
    position: relative;
}

.hm-aisle-carousel__track {
    display: flex;
    /* Phase 7-1h — gap reads from --hm-aisle-card-gap (set by
     * aisle-carousel.js's computeSizing, proportional to card width
     * at gapRatio 0.06). Fallback 12px matches the pre-7-1h fixed
     * value so the pre-JS render stays close. */
    gap: var(--hm-aisle-card-gap, 12px);
    overflow-x: auto;
    overflow-y: hidden;
    /* Proximity over mandatory: snapping only kicks in near scroll-
     * end, mid-flick the user isn't fighting the snap engine. Matches
     * .hm-pc__track's choice (NOTES §10) which optimizes for "feels
     * fluid" over "always card-aligned." */
    scroll-snap-type: x proximity;
    /* Scrollbar hidden — chevrons + edge fades are the visual cue
     * that there's more content. Firefox via scrollbar-width:none,
     * WebKit/Blink via ::-webkit-scrollbar { display:none } below. */
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* pan-x: horizontal swipes drive the carousel; vertical/diagonal
     * pass through to the page so the user can scroll past the row
     * with a normal vertical drag. Same convention as .hm-pc__track. */
    touch-action: pan-x;
    /* overscroll-behavior-x: contain prevents a swipe at the edge
     * from chaining into a page-level horizontal pull (iOS rubber-
     * band). Same as .hm-pc__track. */
    overscroll-behavior-x: contain;
}

.hm-aisle-carousel__track::-webkit-scrollbar {
    display: none;
}

/* Phase 7-1f: no-loop hook for low-count aisles. The aisle carousel
 * doesn't carry edge-fade pseudo-elements (unlike .hm-pc/.hm-vc), so
 * the class is currently empty — exists purely as a CSS-side marker
 * that activateWrap() decided NOT to clone. Add visual treatments
 * here (e.g. swap snap-type, hide chevrons at very low counts) if
 * future tuning needs to differentiate the modes visually. */
.hm-aisle-carousel__track--no-loop {
    /* no visual change; chevrons stay, native scroll clamping at rails */
}

/* --- Grid layout (no-subcategory fallback) -----------------------------
 *
 * When /shop/<category>/ has no subcategories (or all are empty), the
 * controller synthesizes a single self-referential aisle with
 * data-hm-aisle-layout="grid". aisle-carousel.js attaches
 * .hm-aisle-carousel--grid / .hm-aisle-carousel__track--grid; this
 * rule swaps the flex track for a wrapping CSS grid.
 *
 * auto-fill with minmax(180px, 1fr): cards size between 180px and the
 * available column width, so on a 1280px page we get ~6 columns, on a
 * narrow 320px viewport we get 1-2 columns — no media query needed.
 * The card's fixed 280px height (from the carousel layout above) is
 * inherited; only the track's row/column shape changes.
 *
 * Sentinel: a 1px-tall element pinned at the end of the grid that
 * IntersectionObserver watches to trigger next-batch fetch. No
 * visual footprint. */
.hm-aisle-carousel__track--grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 16px;
    /* Override the horizontal-scroll properties from the base rule —
     * grid doesn't scroll horizontally; vertical page scroll is what
     * gets the user through the rows. */
    overflow: visible;
    scroll-snap-type: none;
    touch-action: auto;
    overscroll-behavior-x: auto;
}

.hm-aisle-carousel--grid .hm-aisle-card {
    /* In grid mode the carousel-rule's `width: var(--hm-aisle-card-width)`
     * fights `1fr` sizing. Override to let the grid track width win;
     * each cell sizes from `repeat(auto-fill, minmax(180px, 1fr))`
     * and the card fills its cell. Height is intrinsic post-7-1h —
     * aspect-ratio on the image region plus body content, both stable
     * enough across products that grid rows align cleanly. */
    flex: 1 1 auto;
    width: 100%;
}

.hm-aisle-carousel__grid-sentinel {
    /* Sentinel intersection target. Spans the full grid row width
     * (grid-column: 1 / -1) so it never sits between cards as a
     * one-cell gap. height:1 + visibility-style keeps it invisible
     * but observable. */
    grid-column: 1 / -1;
    height: 1px;
}

/* Mobile: keep one or two columns at narrow viewports. minmax(150px)
 * gives a tighter fit than the desktop 180px. */
@media (max-width: 576px) {
    .hm-aisle-carousel__track--grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: 12px;
    }
}

/* Grid mode has no aisle header — the page h1 already names the
 * category. Drop the outer section's top spacing so the cards start
 * just below the subtitle. */
.hm-shop__aisles--grid {
    gap: 0;
}

.hm-aisle-card {
    /* Phase 7-1h — dynamic sizing. Width comes from --hm-aisle-card-width
     * (set by aisle-carousel.js's ResizeObserver via computeSizing).
     * Fallback clamp() covers the pre-mount window so server-rendered
     * skeletons read close to the final width before JS lands. Height
     * is intrinsic now — driven by the aspect-ratio'd top region plus
     * the body's natural content height (2-line clamp on the title
     * keeps it stable across products). Previous fixed 200×280 left
     * dead space at carousel right edges when content width didn't
     * divide evenly into the visible region — §7-1h findings A.
     *
     * §60 brand polish: border tinted from `#ececec` gray to
     * `rgba(45,62,16,0.08)` (derived from --hm-color-green-darkest
     * #2D3E10 at 8% alpha — "leafy outline" against the cream page
     * wash), border-radius bumped 6→12 to match homepage `.hm-pc-card`
     * / `.hm-vc-card`, base box-shadow added (was hover-only) so cards
     * feel "lifted" at rest like the homepage carousels. Transitions
     * extended to include border-color so the hover state animates
     * the border tint shift along with shadow + transform.
     *
     * CRITICAL: width is preserved (still reads --hm-aisle-card-width).
     * §53 three-state carousel thresholds (static/no-loop/loop) and
     * §55 fluid sizing both depend on the card's width staying stable
     * — only border/radius/shadow/hover-transform changed. */
    flex: 0 0 auto;
    width: var(--hm-aisle-card-width, clamp(160px, 18vw, 240px));
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    background: #fff;
    border: 1px solid rgba(45, 62, 16, 0.08);
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(45, 62, 16, 0.04);
    /* §63 (Phase 7-1p): cursor: pointer now that the whole card is
     * clickable to product page (matches .hm-pc-card line 530). */
    cursor: pointer;
    transition:
        box-shadow 180ms ease-out,
        transform 180ms ease-out,
        border-color 180ms ease-out;
}

/* §60 brand polish: lift on hover matches homepage `.hm-pc-card:hover` /
 * `.hm-vc-card:hover` (translateY -3px + green-tinted shadow + border
 * tint deepens). Pure transform — no layout impact, no carousel scroll
 * position interference. Gated by `prefers-reduced-motion` below. */
.hm-aisle-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 8px 24px rgba(45, 62, 16, 0.12);
    border-color: rgba(45, 62, 16, 0.15);
}

.hm-aisle-card__top {
    /* Image region + quick-add slot (top-right corner) + qty badge
     * (bottom-right) + labels overlay (top-left). Position:relative
     * is the positioning context for those absolutely-placed children.
     *
     * Phase 7-1h — aspect-ratio: 10/9 replaces the previous fixed
     * `flex: 0 0 180px` so the image area scales proportionally with
     * the card's dynamic width (was 200×180 = 10:9; now any width ×
     * width × 9/10). flex-shrink:0 keeps the row's flex layout from
     * compressing the image region under tight space. */
    position: relative;
    aspect-ratio: 10 / 9;
    flex-shrink: 0;
    background: #f7f7f7;
    overflow: hidden;
}

.hm-aisle-card__image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* --- Quick-add plus button (Phase 7f, top-right of image) ---------------
 *
 * Replaces the empty .hm-aisle-card__quick-add-slot placeholder 7e2
 * shipped. Always-visible, brand-green circle, ~36px (~44px touch
 * target with the active scale). Disabled state for OOS products
 * stays visible but greyed.
 *
 * z-index 3: above the image (z=0) and labels overlay (z=2) so a
 * label pill never partially occludes the button.
 */
.hm-aisle-card__quick-add {
    position: absolute;
    top: 8px;
    right: 8px;
    /* §64 (7-1q): bumped 30 → 36px. The image is now click-through to
     * the PDP (§63 / 7-1p), so a too-small bag button sitting on top of
     * a large clickable image is a real misclick hazard. 36px (icon
     * scales at 60% = ~22px glyph) gives a comfortable touch target —
     * with the :active scale it lands near 40px — while staying lighter
     * than a full 44×44 so it doesn't dominate the card (Adam's "moderate
     * bump" call, preserving the §48 light-weight intent). */
    width: 36px;
    height: 36px;
    border-radius: 50%;
    /* Cream at 70% alpha so the underlying product image reads
     * through subtly. Hover restores opacity to 100% (§48). */
    background: rgba(241, 235, 191, 0.7);
    border: 0;
    padding: 0;
    cursor: pointer;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
    transition: transform 180ms ease, background-color 180ms ease, box-shadow 180ms ease;
    z-index: 3;
    display: flex;
    align-items: center;
    justify-content: center;
}

.hm-aisle-card__add-bag-icon {
    width: 60%;
    height: 60%;
    object-fit: contain;
    display: block;
    pointer-events: none;
    transition: filter 180ms ease;
}

.hm-aisle-card__quick-add:hover:not([disabled]) {
    background: rgba(241, 235, 191, 1);
    transform: scale(1.05);
    box-shadow: 0 4px 12px rgba(45, 62, 16, 0.15);
}

.hm-aisle-card__quick-add:hover:not([disabled]) .hm-aisle-card__add-bag-icon {
    filter: drop-shadow(0 0 8px rgba(240, 160, 32, 0.5));
}

.hm-aisle-card__quick-add:active:not([disabled]) {
    transform: scale(0.95);
}

.hm-aisle-card__quick-add:focus-visible {
    outline: 2px solid #2D3E10;
    outline-offset: 2px;
}

.hm-aisle-card__quick-add[disabled] {
    background: rgba(181, 181, 181, 0.7);
    cursor: not-allowed;
    box-shadow: none;
}

.hm-aisle-card__quick-add[disabled] .hm-aisle-card__add-bag-icon {
    filter: grayscale(1) opacity(0.5);
}

/* --- Qty badge (bottom-right of image, hidden when empty) --------------
 *
 * cart-state.js writes the qty as the badge's textContent. When qty=0
 * the textContent is empty string, and :empty hides the badge —
 * cleaner than toggling a class because it removes a state-sync risk
 * (forgetting to remove a class on rollback).
 *
 * z-index 3: same layer as the quick-add button. They occupy diagonally
 * opposite corners of the image and never overlap.
 */
.hm-aisle-card__qty-badge {
    position: absolute;
    bottom: 8px;
    right: 8px;
    min-width: 26px;
    height: 26px;
    padding: 0 8px;
    border-radius: 13px;
    background: #21703b;
    color: #fff;
    font-weight: 700;
    font-size: 13px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.18);
    z-index: 3;
    box-sizing: border-box;
    line-height: 1;
}

.hm-aisle-card__qty-badge:empty {
    display: none;
}

/* Pulse animation on badge update — single-shot, 240ms. cart-state.js
 * adds the --pulse class on every accepted tryAdd, then removes it
 * after the animation duration so subsequent taps re-trigger. */
.hm-aisle-card__qty-badge--pulse {
    animation: hm-qty-pulse 240ms ease-out;
}

@keyframes hm-qty-pulse {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.22); }
    100% { transform: scale(1); }
}

/* Reduced motion: strip animations on the quick-add button + badge.
 * State changes still happen — just instantly. */
@media (prefers-reduced-motion: reduce) {
    .hm-aisle-card__quick-add,
    .hm-aisle-card__add-bag-icon,
    .hm-aisle-card__qty-badge {
        transition: none;
    }
    .hm-aisle-card__quick-add:hover:not([disabled]),
    .hm-aisle-card__quick-add:active:not([disabled]) {
        transform: none;
    }
    .hm-aisle-card__quick-add:hover:not([disabled]) .hm-aisle-card__add-bag-icon {
        filter: none;
    }
    .hm-aisle-card__qty-badge--pulse {
        animation: none;
    }
}

/* Product labels overlay (csc_product_labels — "Cal Poly Alumni",
 * "Organic", "Nonprofit", "Availability" etc.). Server-shaped into
 * {text, bg, fg} so the client renders text-only pills with admin-
 * configured colors. Positioned over the image at the top-left;
 * pointer-events:none on the container keeps image-area clicks from
 * being intercepted (7f's quick-add slot occupies top-right of the
 * same region). Stacks multiple labels horizontally with wrap. */
.hm-aisle-card__labels {
    position: absolute;
    top: 8px;
    left: 8px;
    right: 56px; /* leave room for the top-right quick-add slot */
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    pointer-events: none;
    z-index: 2;
}

.hm-aisle-card__label {
    /* Same visual treatment as .hm-pc-card__label — small pill with
     * brand-green default colors, overridable per-label via inline
     * style. Compact at 10px so multi-label rows fit on a 200px card. */
    display: inline-block;
    padding: 2px 7px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    background: #21703b;
    color: #f5efdc;
    border-radius: 10px;
    /* Inline-styled bg/fg from admin colors arrive via the .style
     * attribute set in JS — those take precedence over these defaults
     * because inline > class specificity. */
}

.hm-aisle-card__bottom {
    /* ~35% of card height. No longer a single anchor — multi-storefront
     * convention adds a separate vendor link, so the bottom is now a
     * container with three children: vendor anchor, title anchor, price
     * (non-clickable). Each child has its own touch target. */
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    padding: 6px 10px 10px;
    color: #2c2c2c;
    gap: 2px;
}

/* Vendor eyebrow — Montserrat 600 UPPERCASE via --hm-font-accent
 * (Phase 7-1m §60). Matches the homepage `.hm-pc-card__vendor`
 * pattern (Montserrat 600 11px UPPERCASE 0.05em green-dark). The
 * shift from grey #6b6b6b to brand green-dark makes vendor
 * attribution feel like a branded link rather than a neutral label
 * — pairs with the carousel card vocabulary on the homepage. */
.hm-aisle-card__vendor {
    font-family: var(--hm-font-accent);
    font-size: 11px;
    font-weight: 600;
    color: var(--hm-color-green-dark);
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: color 120ms ease-out;
}

.hm-aisle-card__vendor:hover,
.hm-aisle-card__vendor:focus {
    color: var(--hm-color-green-fresh);
    text-decoration: none;
    outline: none;
}

/* Product title — Open Sans via --hm-font-body (Phase 7-1m §60).
 * Anchor displayed as block so the click target spans the full
 * bottom width. 2-line clamp keeps card heights uniform across
 * varying name lengths. Color shifted to brand green-darkest;
 * weight bumped 500→600 to match homepage `.hm-pc-card__name`. */
.hm-aisle-card__title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-family: var(--hm-font-body);
    font-size: 13px;
    font-weight: 600;
    /* Absolute line-height (= 13px × 1.3) — NOT unitless 1.3 — so each
     * line-box stays 16.9px tall regardless of what fit-text shrinks the
     * font to. With unitless `1.3` the line-box shrank with the font, so
     * at the 11px floor 2 lines = 28.6px while the min-height slot was
     * still 33.8px → a 5.2px slice of line 3 peeked through `overflow:
     * hidden` on long, fit-text-shrunk titles (the "Prepared Foods" /
     * "Grass-Fed Beef Ragu…" recurrence of the §68 3-line bug, on the
     * aisle-card sibling selector this time). Pinning line-height in
     * absolute px makes 2 line-boxes = 33.8 at any font size, so the
     * slot exactly fits 2 lines and line 3 starts at the clip boundary —
     * no peek. Cost: slightly looser inter-line whitespace on shrunk
     * cards (16.9/11 ≈ 1.54 vs the design's 1.3), which only the
     * longest titles experience and reads as "small text, generous
     * spacing" — the lesser evil. */
    line-height: 16.9px;
    color: var(--hm-color-green-darkest);
    text-decoration: none;
    flex: 1 1 auto;
    /* §64 (7-1q): reserve a fixed 2-line slot (13px × 1.3 line-height ×
     * 2 = 33.8px), pinned in absolute px for the same reason §61 pinned
     * .hm-pc-card__name to 36.4px — so the slot does NOT shrink when
     * fit-text drops the font toward its 11px floor. §61 left this off
     * the aisle title, trusting the flex track's `align-items: stretch`
     * to equalize heights. But stretch only equalizes to the tallest
     * CURRENTLY-loaded card: with no 2-line card yet present the track
     * is 1-line tall, then a 2-line card lazy-appends (or fit-text
     * re-measures) and every card re-stretches a full line taller —
     * the visible "bounce." Reserving 2 lines makes each card's
     * intrinsic height constant from first paint, so the track height
     * never changes on append. (Single px value covers the 12px mobile
     * size too — its 2-line title fits inside 33.8px uniformly.) */
    min-height: 33.8px;
}

.hm-aisle-card__title:hover,
.hm-aisle-card__title:focus {
    color: var(--hm-color-green-fresh);
    text-decoration: none;
    outline: none;
}

.hm-aisle-card__title:focus-visible {
    outline: 2px solid var(--hm-color-green-dark);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Price — Nunito 700 via --hm-font-display (Phase 7-1m §60). Matches
 * the homepage `.hm-pc-card__price` pattern. Discount-mode spans
 * (`__price-list` strikethrough + `__price-current`) inherit color
 * via their own rules below. */
.hm-aisle-card__price {
    font-family: var(--hm-font-display);
    font-size: 15px;
    font-weight: 700;
    color: var(--hm-color-green-dark);
    display: flex;
    align-items: baseline;
    gap: 6px;
    flex-wrap: wrap;
}

.hm-aisle-card__price-list {
    /* The crossed-out original price. Lighter weight + neutral color
     * so it visually recedes; the current price is what the eye lands
     * on. line-through via text-decoration-thickness so the strike is
     * visible even at small font-size. */
    font-size: 12px;
    font-weight: 500;
    color: #999;
    text-decoration: line-through;
    text-decoration-thickness: 1px;
}

.hm-aisle-card__price-current {
    /* Same color/size as undiscounted price — keeps the visual rhythm
     * across cards regardless of whether a discount is present. */
    color: #21703b;
}

/* --- Chevron nav (hover-revealed; always-on when wheel-locked) ----------- */

/* Aisle chevron — solid brand-green + white chevron + green-fresh
 * hover scale (Phase 7-1m §60). Brings the aisle carousel nav into
 * the §44.7 vocabulary used by `.hm-pc__nav` / `.hm-vc__nav` on the
 * homepage: solid `--hm-color-green-dark` background, true white
 * chevron, green-tinted shadow `rgba(45,62,16,0.18)`. Hover lifts to
 * `--hm-color-green-fresh` (#56A430 lime) with a gentle scale 1.05.
 * Disabled state added: linen-gray bg + #999 chevron (matches
 * homepage). The "appears on hover" UX is preserved via opacity:0/1
 * gated by `.hm-aisle:hover` below. */
.hm-aisle-carousel__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    padding: 0;
    background: var(--hm-color-green-dark);
    color: #fff;
    border: 0;
    border-radius: 50%;
    font-size: 20px;
    line-height: 1;
    box-shadow: 0 4px 12px rgba(45, 62, 16, 0.18);
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition:
        opacity 160ms ease,
        transform 120ms ease,
        background-color 160ms ease;
    z-index: 3;
}

.hm-aisle-carousel__nav--prev {
    left: 8px;
}

.hm-aisle-carousel__nav--next {
    right: 8px;
}

/* Phase 7-1y — chevrons are now ALWAYS visible on devices with a
 * pointer (desktop / hybrid laptops with mice). `(hover: hover)` is
 * the standards-blessed feature query for "this device has a hover-
 * capable primary input." Touch-only mobile devices fail the query,
 * so chevrons stay opacity:0 there (touch swipe is the primary
 * input, the chevrons would just occlude product images). Keyboard
 * focus always reveals regardless, so AT users always reach them.
 *
 * Replaces the previous `.hm-aisle:hover` reveal — discoverability
 * matters more than minimalism now that we've dropped the
 * hover-to-engage wheel-hijack: the chevrons are now THE explicit
 * horizontal-scroll affordance (alongside drag-to-scroll). */
@media (hover: hover) {
    .hm-aisle-carousel__nav {
        opacity: 1;
        pointer-events: auto;
    }
}
.hm-aisle-carousel__nav:focus-visible {
    opacity: 1;
    pointer-events: auto;
}

.hm-aisle-carousel__nav:hover {
    background: var(--hm-color-green-fresh);
    transform: translateY(-50%) scale(1.05);
}

.hm-aisle-carousel__nav:active {
    transform: translateY(-50%) scale(0.95);
}

/* §60: disabled state when chevron is rendered but cannot scroll
 * further. Aisle-carousel.js does not yet toggle :disabled on these
 * buttons (NOTES §20 — chevrons step within the scroll wrap), but
 * the rule is defensive groundwork for any future state that does. */
.hm-aisle-carousel__nav:disabled {
    background: var(--hm-color-linen);
    color: #999;
    cursor: not-allowed;
    box-shadow: none;
}

.hm-aisle-carousel__nav:disabled:hover {
    background: var(--hm-color-linen);
    transform: translateY(-50%);
}

/* Phase 7-1y — drag-to-scroll affordances. The hover-to-lock +
 * vertical-wheel-hijack interaction is gone; the track now offers a
 * universal "grab and drag" gesture (Maps / Figma / kiosk pattern).
 * `cursor: grab` is the visible-on-hover invitation; `--dragging`
 * locks in `cursor: grabbing` while the user is panning AND disables
 * text selection (otherwise text + image drag-select fight the pan)
 * AND turns off scroll-snap (proximity-snap mid-drag would feel like
 * the row is fighting the user — re-enabled the moment pointer is
 * released). Hover query is `(hover: hover)` for the same reason as
 * the chevron reveal above: touch-only devices skip the grab cursor
 * and use their native swipe. */
@media (hover: hover) {
    .hm-aisle-carousel__track:not(.hm-aisle-carousel__track--grid) {
        cursor: grab;
    }
}
.hm-aisle-carousel__track--dragging {
    cursor: grabbing !important;
    user-select: none;
    -webkit-user-select: none;
    scroll-snap-type: none;
}

/* Keyboard focus ring on the track itself. tabindex=0 makes it
 * focusable; without a visible ring keyboard users would Tab to an
 * invisible target. focus-visible (not :focus) keeps mouse clicks
 * from drawing the ring. */
.hm-aisle-carousel__track:focus-visible {
    outline: 2px solid var(--hm-color-green-dark, #21703b);
    outline-offset: 2px;
    border-radius: 4px;
}

/* Empty-state copy when the subcategory has zero in-stock products. */
.hm-aisle-carousel__empty {
    padding: 32px 12px;
    color: #6b6b6b;
    font-size: 14px;
    text-align: center;
    width: 100%;
}

/* Reduced motion: kill hover transitions. Scroll-snap behavior
 * itself is not an animation — it's an instant snap — so it stays
 * functional. The chevron's smooth-scroll step in JS also branches
 * to instant via prefersReducedMotion() (see aisle-carousel.js).
 * §60: card-hover translateY suppressed; chevron hover scale
 * suppressed. Both match the homepage `.hm-pc-card` / `.hm-pc__nav`
 * reduced-motion overrides. */
@media (prefers-reduced-motion: reduce) {
    .hm-aisle-card,
    .hm-aisle-carousel__nav {
        transition: none;
    }
    .hm-aisle-card:hover {
        transform: none;
    }
    .hm-aisle-carousel__nav:hover,
    .hm-aisle-carousel__nav:active {
        transform: translateY(-50%);
    }
}

/* Hide chevrons at the smallest viewports where touch is dominant —
 * matches the .hm-pc__nav <=575px rule. Touch users still drive the
 * carousel via swipe; chevrons would just crowd the row. The wheel-
 * hijack also doesn't engage here (WHEEL_HIJACK_MIN_WIDTH=800 in JS),
 * so the "always-visible-when-locked" override never triggers. */
@media (max-width: 575px) {
    .hm-aisle-carousel__nav {
        display: none;
    }
}

/* Mobile typography + control sizing. Phase 7-1h removed the discrete
 * card-width / card-height / top-flex overrides here — those are now
 * driven by --hm-aisle-card-width across all viewports, no breakpoint
 * snap. The remaining rules tune text + button sizes for narrow
 * screens; they're independent of card width and stay as a step-down
 * at the 576px breakpoint. */
@media (max-width: 576px) {
    .hm-aisle-card__title {
        font-size: 12px;
    }

    .hm-aisle-card__vendor {
        font-size: 10px;
    }

    .hm-aisle-card__price {
        font-size: 14px;
    }

    .hm-aisle-card__price-list {
        font-size: 11px;
    }

    .hm-aisle-card__label {
        font-size: 9px;
        padding: 2px 5px;
    }

    /* §64 (7-1q): bumped 26 → 34px for touch-target safety now that the
     * card image is click-through to the PDP (§63). With the :active
     * scale the effective target clears ~38px — close to the WCAG 44
     * intent without the heavy visual weight a literal 44px circle would
     * carry on a small mobile card. Desktop sits at 36px (rule above). */
    .hm-aisle-card__quick-add {
        width: 34px;
        height: 34px;
        font-size: 16px;
    }

    .hm-aisle-card__qty-badge {
        min-width: 22px;
        height: 22px;
        padding: 0 6px;
        font-size: 12px;
        border-radius: 11px;
    }
}

/* ============================================================================
 * === Hero block (Phase 7g → 7v — video background) =========================
 * ============================================================================
 *
 * Video background hero with admin-tunable copy overlay. Phase 7v replaced
 * the animated SVG scene (Lottie sun, three tree-silhouette path layers,
 * drifting particles, scroll parallax — all of §23) with a single 17MB
 * MP4 video. Same DOM hierarchy, simpler visual.
 *
 * Stacking (z-index inside .hm-hero):
 *   .hm-hero (background-color brand-green fallback)  — base
 *   .hm-hero__video (object-fit cover MP4)            z-index: 1
 *   .hm-hero__overlay (gradient dim for text contrast) z-index: 2
 *   .hm-hero__content (headline + subhead + CTA)       z-index: 3
 *
 * Video filter chain (CSS `filter` property):
 *   - saturate(0.85): removes ~15% color saturation — slight elegance,
 *     not desaturated-to-grey. The footage reads cinematic vs candy-bright.
 *   - contrast(1.08): adds ~8% contrast — subtle pop on shadows + highlights
 *     without crushing blacks or blowing out whites.
 * Combined the video looks intentional; without this it competes with
 * the text overlay for visual attention.
 *
 * Overlay gradient: top-down vignette darker at top + bottom, lighter
 * in the middle (where most text sits). Keeps the headline readable
 * against any video frame. text-shadow on the headline + subheadline
 * is the safety net for the rare too-bright frame.
 *
 * Reduced-motion handling lives in hero.js — explicit video.pause() +
 * removeAttribute('autoplay'). CSS does NOT also gate the video with
 * `animation: none` because that wouldn't stop <video> playback; JS is
 * the right knob.
 *
 * Asset swap-out: drop a new MP4 at /js/addons/harvestly_modern/data/
 * harvestly_hero.mp4 and update the poster image if needed. Filter
 * values may need re-tuning per the new footage's color profile.
 * See NOTES §42.
 * ========================================================================= */

.hm-hero {
    position: relative;
    width: 100%;
    height: 35vh;
    /* min-height bumped 280 → 360 (Phase 7w-application feedback pass).
       The original 280 was too short for the 7w-foundation logo + title
       + tagline + CTA stack at desktop — content centered with
       `justify-content: center` overflowed top + bottom equally, and
       `overflow: hidden` clipped the CTA when viewport was tall enough
       that 35vh resolved to the floor. 360 gives ~80px of breathing room. */
    min-height: 360px;
    max-height: 480px;
    overflow: hidden;
    /* Brand-green fallback. Shows for the instant before the poster
       image paints (or as the final layer if the video and poster
       both fail to load). */
    background-color: #21703b;
}

/* Video fills the hero and stays behind the overlay + content. The
   subtle filter combo gives it a more cinematic, less candy-saturated
   look without making the content look grey/dead. */
.hm-hero__video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    z-index: 1;
    /* Filters are GPU-accelerated by modern browsers; no measurable
       perf cost on the page repaint. Safari pre-15 may not honor
       `contrast()` on <video>; that's an acceptable graceful-degrade
       (the video just looks slightly flatter). */
    filter: saturate(0.85) contrast(1.08);
}

/* Top-down gradient that darkens the edges where text might overflow
   and lightens the middle where the headline sits. Lighter than a
   uniform dim — preserves the video's visual energy while keeping
   text legible. pointer-events: none so the gradient div doesn't
   intercept any clicks targeted at the CTA below. */
.hm-hero__overlay {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background: linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0.45) 0%,
        rgba(0, 0, 0, 0.15) 40%,
        rgba(0, 0, 0, 0.10) 70%,
        rgba(0, 0, 0, 0.35) 100%
    );
}

.hm-hero__content {
    position: relative;
    z-index: 3;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    /* Padding tightened clamp(16,4vw,48) → clamp(12,2.5vw,28) so the
       CTA reliably fits inside the hero (7w-application feedback pass).
       Loses ~40px of vertical real estate that the original heavier
       padding was eating without visual benefit at desktop sizes. */
    padding: clamp(12px, 2.5vw, 28px);
    /* max-width 720 → 820 so "Fresh from your community." fits on one
       line at desktop with the new (slightly smaller) title size. */
    max-width: 820px;
    margin: 0 auto;
}

/* Brand mark + wordmark above the headline. Scales between 180px on
 * the smallest viewports and 320px at desktop, hovering around 24vw
 * in between — small enough that the headline is the centerpiece,
 * large enough to read as deliberate branding rather than as a
 * navigation favicon.
 *
 * Height auto'd off the 500:102 viewBox aspect. Bottom margin keeps
 * the headline visually anchored to the logo without crowding.
 *
 * Drop-shadow matches the text-shadow vibe — soft halo for legibility
 * against bright video frames without reading as a heavy effect. The
 * shadow falls on the leaf + wordmark independently (filter on the
 * SVG element wraps both groups). */
.hm-hero__logo {
    width: clamp(180px, 24vw, 320px);
    height: auto;
    /* Bottom margin tightened 12-20 → 8-14 (7w-application feedback) to
       reclaim ~6px of vertical room for the CTA. */
    margin-bottom: clamp(8px, 1vw, 14px);
    filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.45));
}

/* Title — Nunito Bold via --hm-font-display. Sized via clamp; tightened
 * tracking by 0.025em (Nunito at large weights benefits from slightly
 * tighter letter-spacing). Text-shadow preserved from the 7v safety
 * net — the video's frame-to-frame brightness variation needs the
 * halo against worst-case bright frames.
 *
 * 7w-application feedback pass: dropped from clamp(36,6vw,64) to
 * clamp(28,5vw,50) — about 22% off the ceiling. The previous 64px
 * was too large to fit the default "Fresh from your community."
 * headline (~26 chars) on one line at desktop within the 720px
 * content area. Letter-spacing tightened from -0.02em to -0.025em
 * to recover a touch more horizontal density at the new size.
 * Bottom margin dropped 8 → 6px to give the CTA more vertical room. */
.hm-hero__title {
    margin: 0 0 6px 0;
    max-width: 820px;
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: clamp(28px, 5vw, 50px);
    line-height: 1.1;
    letter-spacing: -0.025em;
    color: #fff;
    text-shadow: 0 2px 16px rgba(0, 0, 0, 0.6);
}

/* Tagline — Caveat Bold via --hm-font-script. Meaningfully smaller than
 * the title: Caveat at large sizes starts feeling like a wedding
 * invitation; at this range it reads as a confident handwritten note.
 * line-height a touch looser than the title (1.3) to suit script
 * glyphs' ascender/descender behavior.
 *
 * 7w-application feedback pass: bottom margin tightened 28 → 18px so
 * the CTA always fits inside the hero bounds even at the new
 * 360px min-height. */
.hm-hero__tagline {
    margin: 0 0 18px 0;
    max-width: 600px;
    font-family: var(--hm-font-script);
    font-weight: 700;
    font-size: clamp(22px, 3vw, 36px);
    line-height: 1.3;
    color: #fff;
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.55);
}

.hm-hero__cta {
    display: inline-block;
    padding: 14px 32px;
    background: var(--hm-brand-green, #21703b);
    color: #fff;
    font-weight: 600;
    font-size: clamp(15px, 1.2vw, 17px);
    border-radius: 8px;
    text-decoration: none;
    /* Slightly heavier shadow than §23 — the video background needs
       the CTA to feel anchored, not floating. */
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
    transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease;
}
.hm-hero__cta:hover {
    background: #2a8748;
    transform: translateY(-1px);
    box-shadow: 0 6px 22px rgba(0, 0, 0, 0.35);
    text-decoration: none;
    color: #fff;
}
.hm-hero__cta:active {
    transform: translateY(0);
}
.hm-hero__cta:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 3px;
}

/* Reduced motion: video playback is killed by hero.js's pause() +
   removeAttribute('autoplay'). The poster image (the shop hero
   flatlay) stays visible. CSS here just kills the CTA transition
   so hover-induced motion is also suppressed. */
@media (prefers-reduced-motion: reduce) {
    .hm-hero__cta {
        transition: none;
    }
}

@media (max-width: 576px) {
    .hm-hero {
        /* Phase 7-2b — hero content was clipping at 320/375 widths.
         * The desktop pattern (height: 30vh; min-height: 220px) capped
         * the box at ~240px on an 800px-tall mobile portrait viewport,
         * while the (logo + title + tagline + CTA) stack measured ~264px
         * at narrow widths — the CTA's bottom edge landed past the
         * hero's overflow:hidden boundary, clipping it. height: auto
         * lets the stack render fully; the bumped min-height keeps the
         * hero tactile when title wraps to a single line. */
        height: auto;
        min-height: 280px;
    }
}

/* ============================================================================
 * === harvestly_intro block (Phase 7w-application) ==========================
 * ============================================================================
 *
 * Three-tier typographic block placed under the homepage hero. Replaces
 * the stock html_block "Fresh Local is Better" (block 451, see NOTES §44).
 *
 * Typography roles:
 *   title   → Nunito Bold (--hm-font-display), dark-green-darkest #2D3E10
 *             — authoritative headline, the page's voice.
 *   tagline → Caveat Bold (--hm-font-script), green-dark #21703B
 *             — warm/personal supporting note, ties to homepage hero tagline.
 *   body    → Open Sans 400 (--hm-font-body), darkest at 0.85 opacity
 *             — readable paragraph, slightly muted so the title leads.
 *
 * Container is 800px max-width centered. Asymmetric vertical padding
 * (7w-app feedback pass 2026-05-21): top tighter than bottom so the
 * block reads as flowing visually from the hero, with a softer break
 * before the zone-checker topo strip below. No background (sits on
 * the page cream wash).
 *
 * Padding math (from the original symmetric 48px top + 48px bottom):
 *   - top    48 → 17  (~65% closer to the hero)
 *   - bottom 48 → 34  (~30% closer to the zone-checker)
 * Mobile rule scales proportionally (32 → 12 top, 32 → 22 bottom).
 * ========================================================================= */

.hm-intro {
    max-width: 800px;
    margin: 0 auto;
    padding: 17px 24px 34px 24px;
    text-align: center;
}

/* Title typography matches the hero's treatment (7w-app feedback pass
 * 2026-05-21): same letter-spacing -0.025em and line-height 1.1 the
 * hero adopted for its confident-display look.
 *
 * Phase 7-2d — sizes reduced (was 26-36 / 20-28 / 15-17 across the
 * three tiers) so the intro reads as supporting subtext to the hero,
 * not co-equal with it. The previous title cap of 36px landed ~72% of
 * the hero's 50px ceiling, which felt competing; 24px is ~48% — clear
 * hierarchy. Reductions also help the line-clamp targets Adam asked
 * for: title to 1 line where feasible, tagline + body to 1-2 lines
 * max across the viewport range.
 *
 * `text-wrap: balance` (Chromium 114+, Firefox 121+, Safari 17.5+)
 * makes short multi-line text wrap evenly when it does break — e.g.
 * the title splits "Over 100 Central Coast Farms /
 * & Kitchens in One Place" rather than "Over 100 Central Coast Farms
 * & Kitchens / in One Place." Falls back gracefully (text just wraps
 * normally) on older browsers. Body uses `pretty` instead — same
 * family of properties but optimized for paragraph-length content
 * (avoids orphans/widows). */
.hm-intro__title {
    margin: 0 0 12px 0;
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: clamp(19px, 2.3vw, 26px);
    color: var(--hm-color-green-darkest);
    line-height: 1.2;
    letter-spacing: -0.025em;
    text-wrap: balance;
}

.hm-intro__tagline {
    /* Phase 7-2f — meaningful bump from the 7-2d 14-22 range. Caveat at
     * 14px was too tiny relative to the block's vertical padding; the
     * middle row felt cramped vs. the breathing room around it. 17-24
     * keeps the 2-line target at 375px+ mobile widths while restoring
     * the script font's intended visual presence. */
    margin: 0 0 16px 0;
    font-family: var(--hm-font-script);
    font-weight: 700;
    font-size: clamp(17px, 2.1vw, 24px);
    color: var(--hm-color-green-dark);
    line-height: 1.3;
    text-wrap: balance;
}

.hm-intro__body {
    /* Body's 99 chars don't fit on 2 lines at 320px without dropping
     * below ~10px (unreadable for Open Sans). 320px will see 3 lines;
     * 375px and up are 2 lines or fewer per Adam's clamp target.
     * Min stays at 13px to preserve the 7-2d 2-line target at 375px;
     * max bumped to 16 so the desktop rendering pairs with the larger
     * title/tagline above without the body reading as too-small. */
    font-family: var(--hm-font-body);
    font-weight: 400;
    font-size: clamp(13px, 1.5vw, 16px);
    color: var(--hm-color-green-darkest);
    line-height: 1.55;
    opacity: 0.85;
    text-wrap: pretty;
}

@media (max-width: 576px) {
    .hm-intro {
        padding: 12px 20px 22px 20px;
    }
}

/* ============================================================================
 * === Carousel polish (Phase 7w-application) ================================
 * ============================================================================
 *
 * Brand-vocabulary polish for the TWO homepage carousels: product (.hm-pc)
 * and vendor (.hm-vc). Augments/overrides the base rules earlier in this
 * file (lines ~391-823 for product, ~825-1085 for vendor).
 *
 * Scope is strictly homepage: aisle carousel (.hm-aisle, /shop/-only)
 * deliberately NOT targeted here — its styling stays as-is until a future
 * sweep brings it under the same vocabulary. See NOTES §44 for rationale.
 *
 * What's NOT in this section:
 *  - Pagination dots: zero carousels in the addon render dots in their
 *    markup (chevron-only navigation across the board). Spec called for
 *    dot styling; we skip rather than write dead CSS.
 *  - Chevron rendering swap (SVG vs HTML entities): preserved as-is
 *    (HTML entities &#10094;/&#10095; styled via color/background). A
 *    future polish phase could replace with inline SVG; not in scope here.
 *
 * Card "lift" on hover changes from the base -4px translateY to -3px and
 * picks up a darker brand-tinted shadow (rgba(45,62,16,0.12) — derived
 * from --hm-color-green-darkest #2D3E10) plus a subtle 1px border that
 * darkens on hover. The shadow color reads as "shadow of a leafy thing"
 * against the new cream page background — rgba(0,0,0,...) reads as grey
 * which looked dirty against the warm wash.
 * ========================================================================= */

/* === Titles (both carousels) ===
 *
 * §60 follow-up: golden-glow takes over from the prior center-card
 * `--focused` highlight as the carousel row's spotlight treatment.
 * The card-level highlight stopped making sense after §46 removed
 * the bottom Add-to-Cart button (no more "this card is the action
 * button's referent" relationship); the title is the right place
 * for the row's emphasis now.
 *
 * Warm-gold matches the §52 cart drawer title pattern + §47
 * testimonials quote-glyph + §48.5 bag-icon-on-aisle-card + §59
 * partners tile glow — the addon's established "spotlighted text"
 * gold (#F0A020 / rgba(240, 160, 32, X)). Text color stays
 * green-darkest for readability against the cream page wash; the
 * glow is the gold accent.
 *
 * Layered drop-shadow filter: a tight 4px halo at 0.55 alpha (sharp
 * inner ring) + a wider 14px outer glow at 0.28 alpha (soft
 * atmosphere). Slightly stronger than the cart drawer's pair
 * (0.55/0.25) since the carousel title sits in a brighter context
 * where the glow has to fight more ambient light. `filter:
 * drop-shadow` instead of `text-shadow` matches the cart drawer
 * pattern and composites cleaner on GPU. */
.hm-pc__title,
.hm-vc__title {
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: clamp(20px, 2.4vw, 26px);
    color: var(--hm-color-green-darkest);
    letter-spacing: -0.01em;
    line-height: 1.2;
    filter:
        drop-shadow(0 0 4px rgba(240, 160, 32, 0.55))
        drop-shadow(0 0 14px rgba(240, 160, 32, 0.28));
}

/* === Cards — subtle border, brand-tinted lift === */
.hm-pc-card,
.hm-vc-card {
    border: 1px solid rgba(45, 62, 16, 0.08);
    transition:
        transform 180ms ease,
        box-shadow 180ms ease,
        border-color 180ms ease,
        filter 180ms ease;
}

.hm-pc-card:hover,
.hm-vc-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 8px 24px rgba(45, 62, 16, 0.12);
    border-color: rgba(45, 62, 16, 0.15);
}

/* §60 follow-up: `--focused` visual rules removed. The class is still
 * toggled by IntersectionObserver in product-carousel.js +
 * vendor-carousel.js but no longer carries CSS — the carousel row's
 * spotlight is now the title's golden-glow treatment below. */

/* === Card sub-element typography (product carousel) === */
.hm-pc-card__name {
    font-family: var(--hm-font-body);
    font-weight: 600;
    color: var(--hm-color-green-darkest);
}

/* Vendor link below product name — small-caps brand accent.
 * Smaller than category headings (11px) because it's a tertiary label
 * inside an already-compact card, not a section header. */
.hm-pc-card__vendor {
    font-family: var(--hm-font-accent);
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--hm-color-green-dark);
}

.hm-pc-card__vendor:hover,
.hm-pc-card__vendor:focus {
    color: var(--hm-color-green-fresh);
}

.hm-pc-card__price {
    font-family: var(--hm-font-display);
    font-weight: 700;
    color: var(--hm-color-green-dark);
}

/* === Card sub-element typography (vendor carousel) === */
/* Vendor name IS the vendor identity on the vc card (no nested "vendor
 * link"), so it leads with the Nunito display treatment rather than the
 * Montserrat accent. City/count below get the body font. */
.hm-vc-card__name {
    font-family: var(--hm-font-display);
    font-weight: 700;
    color: var(--hm-color-green-darkest);
}

.hm-vc-card__city,
.hm-vc-card__count {
    font-family: var(--hm-font-body);
}

/* === Chevron arrows — brand-green circles with white chevron text === */
/* Base rules (lines ~698 / ~1036) set the dark-green-at-88%-alpha
 * background and the cream chevron. 7w-app moves to a solid brand-green
 * background + true white chevron (more confident; the cream felt
 * vintage-postcard against the new modern brand palette). Hover lifts to
 * the lime/green-fresh #56A430 and a gentle scale; disabled goes flat
 * linen-gray. The "appears on hover" UX is preserved (opacity: 0 → 1 on
 * .hm-*__track-wrap:hover) — applies via the base rules. */
.hm-pc__nav,
.hm-vc__nav {
    background: var(--hm-color-green-dark);
    color: #fff;
    box-shadow: 0 4px 12px rgba(45, 62, 16, 0.18);
}

.hm-pc__nav:hover,
.hm-vc__nav:hover {
    background: var(--hm-color-green-fresh);
    transform: translateY(-50%) scale(1.05);
}

.hm-pc__nav:active,
.hm-vc__nav:active {
    /* Override base rule's translateY(-50%) scale(0.94) with a slightly
     * smaller bounce that pairs with the hover scale. */
    transform: translateY(-50%) scale(0.95);
}

.hm-pc__nav:disabled,
.hm-vc__nav:disabled {
    background: var(--hm-color-linen);
    color: #999;
    cursor: not-allowed;
    box-shadow: none;
}

.hm-pc__nav:disabled:hover,
.hm-vc__nav:disabled:hover {
    transform: translateY(-50%);
    background: var(--hm-color-linen);
}

/* "Browse all" CTA — uppercase Montserrat semibold, applies wherever
 * the aisle-style view-all anchor appears. Currently /shop/-only (no
 * homepage carousel renders a view-all). The rule is kept here as a
 * forward-compat hook for any future homepage carousel that does. */
.hm-pc__view-all,
.hm-vc__view-all {
    font-family: var(--hm-font-accent);
    font-weight: 600;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--hm-color-green-dark);
    text-decoration: none;
    transition: color 120ms ease;
}

.hm-pc__view-all:hover,
.hm-vc__view-all:hover {
    color: var(--hm-color-green-fresh);
}

@media (prefers-reduced-motion: reduce) {
    .hm-pc-card,
    .hm-vc-card {
        transition: none;
    }
    .hm-pc-card:hover,
    .hm-vc-card:hover {
        transform: none;
    }
    .hm-pc__nav:hover,
    .hm-vc__nav:hover,
    .hm-pc__nav:active,
    .hm-vc__nav:active {
        transform: translateY(-50%);
    }
}

/* ============================================================================
 * === Drawer additions (Phase 7h) ============================================
 * ============================================================================
 *
 * Two new elements in the LEFT nav drawer:
 *   1. Sign-in CTA — logged-out only, between search and Categories
 *   2. Donate bar — always visible, pinned to bottom of the nav drawer
 *
 * Pinning approach: scope the change to .hm-drawer-nav so the cart drawer
 * is untouched. .hm-drawer-nav becomes overflow:hidden (panel doesn't
 * scroll), .hm-drawer-content inside it becomes the scroll area, and the
 * donate bar is a sibling of .hm-drawer-content at position:absolute;
 * bottom:0 so it stays anchored to the visible drawer bottom regardless
 * of how far the content scrolled. The base .hm-drawer overflow-y:auto
 * is preserved for the cart drawer (which has its own scroll handling
 * inside .hm-cart-drawer__body).
 */

/* Move the scroll container from the panel to the content area, NAV
 * drawer only. min-height:0 lets the flex child shrink below content
 * size so overflow-y actually triggers. */
.hm-drawer-nav {
    overflow: hidden;
}

.hm-drawer-nav .hm-drawer-content {
    overflow-y: auto;
    min-height: 0;
    /* Reserve space at the bottom so the last item in the "Other"
     * section is scrollable into view past the donate bar (~72px) +
     * a small breathing buffer. */
    padding-bottom: 88px;
}

/* --- Sign-in CTA ----------------------------------------------------------- */

.hm-drawer__signin {
    flex: 0 0 auto;
    padding-bottom: 8px;
    border-bottom: 1px solid #e5e5e5;
}

.hm-drawer__signin-cta {
    display: block;
    width: 100%;
    padding: 12px 16px;
    background: #21703b;
    color: #f5efdc;
    font-weight: 600;
    font-size: 15px;
    text-align: center;
    text-decoration: none;
    border-radius: 8px;
    box-sizing: border-box;
    transition: background-color 120ms ease-out, box-shadow 120ms ease-out;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}

.hm-drawer__signin-cta:hover,
.hm-drawer__signin-cta:focus {
    background: #1a5a2e;
    color: #f5efdc;
    text-decoration: none;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
    outline: none;
}

.hm-drawer__signin-cta:focus-visible {
    outline: 2px solid #fff;
    outline-offset: -4px;
}

/* Sign-up tertiary link sits centered below the primary CTA. Matches the
 * cart drawer's hm-cart-drawer__signup-link visual (§18.5). */
.hm-drawer__signup-link {
    display: block;
    margin-top: 8px;
    text-align: center;
    color: #6b6b6b;
    font-size: 13px;
    text-decoration: none;
}

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

/* §49 — `.hm-drawer__donate*` rules removed alongside the chip-in markup
 * in body.post.tpl. Donate CTA is canonically the corner floating button
 * in the footer (§45). */

/* --- Go-to-shop CTA (Phase 7-1z) ------------------------------------------
 * Logged-in counterpart to the sign-in CTA above — same slot in the nav
 * drawer (between the logo banner and the Categories section), same brand-
 * green pill so the drawer's first-impression chrome stays consistent
 * across auth states. Diverged only by the arrow glyph, which gently
 * nudges right on hover/focus to read as "destination link, not modal
 * trigger." */
.hm-drawer__goto-shop {
    flex: 0 0 auto;
    padding-bottom: 8px;
    border-bottom: 1px solid #e5e5e5;
}

.hm-drawer__goto-shop-cta {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    padding: 12px 16px;
    background: #21703b;
    color: #f5efdc;
    font-weight: 600;
    font-size: 15px;
    text-decoration: none;
    border-radius: 8px;
    box-sizing: border-box;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
    transition: background-color 160ms ease-out, box-shadow 160ms ease-out;
}

.hm-drawer__goto-shop-cta-arrow {
    display: inline-block;
    transition: transform 160ms ease-out;
}

.hm-drawer__goto-shop-cta:hover,
.hm-drawer__goto-shop-cta:focus {
    background: #1a5a2e;
    color: #f5efdc;
    text-decoration: none;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
    outline: none;
}

.hm-drawer__goto-shop-cta:hover .hm-drawer__goto-shop-cta-arrow,
.hm-drawer__goto-shop-cta:focus .hm-drawer__goto-shop-cta-arrow {
    transform: translateX(3px);
}

.hm-drawer__goto-shop-cta:focus-visible {
    outline: 2px solid #fff;
    outline-offset: -4px;
}

@media (prefers-reduced-motion: reduce) {
    .hm-drawer__signin-cta,
    .hm-drawer__goto-shop-cta,
    .hm-drawer__goto-shop-cta-arrow {
        transition: none;
    }
}

/* ============================================================================
 * === Help button (Phase 7i) =================================================
 * ============================================================================
 *
 * Circular "?" trigger in the header strip alongside the countdown banner.
 * Click → anchored dropdown below the trigger. Click a question → modal
 * bubble anchored near the trigger (NOT viewport-centered) + shared
 * .hm-modal-backdrop overlay.
 *
 * Polish-pass (post-placement, 2026-05-11): redesigned trigger from a
 * neutral-gray ghost button to a brand-aligned info button — white fill +
 * brand-green ring + brand-green "?" at rest, brand-green fill + parchment
 * "?" on hover/active. Bubble repositioned from viewport-center to
 * trigger-anchored. Trigger active-state lifts via .hm-help__trigger--floating
 * portal clone (see help.js comments — escapes stacking-context trap).
 *
 * Z-index ladder:
 *   .hm-drawer-backdrop          100001 (§16.5)
 *   .hm-drawer panel             100002 (§16.5)
 *   .hm-modal-backdrop           100100 (above drawers — focal modal layer)
 *   .hm-help__bubble             100101 (above its backdrop)
 *   .hm-help__trigger--floating  100102 (above bubble; trigger clone)
 *   .ty-ajax-loading-box         100100 (§18.13 — same plane as modal backdrop;
 *                                          spinner only fires during cm-ajax
 *                                          flight, not a competing modal)
 *   toast                        2147483647 (intentionally always-on-top)
 */

.hm-help {
    position: relative;
    display: inline-flex;
    align-items: center;
}

/* === Trigger (modern brand-aligned info button) ===
 *
 * Sized to fit alongside the countdown pill (~28px tall) without pushing
 * the TOP_PANEL row height. Diameter 26px, glyph 13px — matches the pill's
 * visual weight. Block container is inline-flex align-items:center so the
 * circle sits vertically centered in whichever grid slot admin drops it
 * into, regardless of the slot's height.
 */
.hm-help__trigger {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: #fff;
    color: #21703b;
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
    border: 1.5px solid #21703b;
    cursor: pointer;
    transition: background-color 160ms ease, color 160ms ease, transform 80ms ease, box-shadow 160ms ease, border-color 160ms ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
    /* The "?" glyph reads slightly left-of-center in most UI fonts —
     * 1px nudge centers it visually inside the circle. */
    text-indent: 1px;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
    /* Keep the box compact: prevents any inherited line-height from the
     * surrounding header row from stretching the trigger's clickable area. */
    vertical-align: middle;
}

.hm-help__trigger:hover {
    background: #21703b;
    color: #f5efdc;
    box-shadow: 0 2px 5px rgba(33, 112, 59, 0.25);
}

.hm-help__trigger:active {
    transform: scale(0.92);
}

.hm-help__trigger:focus-visible {
    outline: 2px solid #21703b;
    outline-offset: 2px;
}

/*
 * Active state — dropdown open OR (for the floating clone) bubble open.
 * Brand-green fill, parchment glyph, halo ring to communicate "this is
 * the source of the open thing." Halo uses box-shadow spread so it's
 * outside layout flow and doesn't push the row height.
 */
.hm-help__trigger[aria-expanded="true"],
.hm-help__trigger--floating {
    background: #21703b;
    color: #f5efdc;
    border-color: #21703b;
    box-shadow:
        0 0 0 3px rgba(33, 112, 59, 0.18),
        0 3px 8px rgba(33, 112, 59, 0.28);
}

/*
 * Floating clone — the body-level portal copy that lifts above the
 * .hm-modal-backdrop. position:fixed coords are set inline by JS from
 * the original trigger's bounding rect.
 *
 * z-index here also overrides any inherited stacking from the ancestor
 * the clone is appended to (document.body — clean). The clone is
 * conceptually always "active": it only exists while the bubble is open.
 */
.hm-help__trigger--floating {
    z-index: 100102;
}

/* === Dropdown === */

.hm-help__dropdown {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    width: 320px;
    max-width: 90vw;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
    padding: 8px;
    box-sizing: border-box;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 160ms ease, transform 160ms ease, visibility 0s linear 160ms;
    z-index: 100;
}

.hm-help__dropdown[aria-hidden="false"] {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition: opacity 160ms ease, transform 160ms ease, visibility 0s linear 0s;
}

.hm-help__questions {
    list-style: none;
    margin: 0;
    padding: 0;
}

.hm-help__question {
    display: block;
    width: 100%;
    padding: 12px 14px;
    text-align: left;
    background: transparent;
    color: #222;
    font-size: 14px;
    font-weight: 500;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: background-color 120ms ease;
    font-family: inherit;
}

.hm-help__question:hover,
.hm-help__question:focus-visible {
    background: rgba(33, 112, 59, 0.08);
    outline: none;
}

.hm-help__question:focus-visible {
    box-shadow: inset 0 0 0 2px #21703b;
}

/*
 * Phase 7k — "Show Tooltips" re-trigger entry. Same row layout as a
 * regular question but with a subtle brand-tinted left accent + leading
 * mascot icon so users recognize it as a different kind of action
 * (re-runs the onboarding tour) rather than yet another FAQ question.
 * Visual distinction is intentionally light — bold accent would compete
 * with the trigger's active state for visual hierarchy.
 */
.hm-help__question--restart-tour {
    margin-top: 4px;
    padding-top: 12px;
    border-top: 1px solid #e5e5e5;
    color: #21703b;
    font-weight: 600;
}

.hm-help__question--restart-tour::before {
    content: "↻";
    display: inline-block;
    margin-right: 8px;
    font-size: 14px;
    transform: translateY(-1px);
}

.hm-help__more {
    display: block;
    padding: 10px 14px;
    margin-top: 4px;
    border-top: 1px solid #e5e5e5;
    color: #21703b;
    font-size: 13px;
    font-weight: 500;
    text-decoration: none;
    text-align: center;
}

.hm-help__more:hover,
.hm-help__more:focus {
    text-decoration: underline;
    color: #1a5a2e;
}

/* === Modal backdrop (shared primitive — used by 7i, reusable by 7k tooltips) === */

.hm-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.7); /* darker than .hm-drawer-backdrop (0.55) */
    backdrop-filter: blur(12px);    /* stronger than drawer's 8px */
    -webkit-backdrop-filter: blur(12px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 200ms ease, visibility 0s linear 200ms;
    z-index: 100100;
}

.hm-modal-backdrop[aria-hidden="false"] {
    opacity: 1;
    visibility: visible;
    transition: opacity 200ms ease, visibility 0s linear 0s;
}

/* === Help bubble ===
 *
 * Trigger-anchored, NOT viewport-centered. JS sets `top` / `right` from
 * trigger.getBoundingClientRect() on each open + on resize/scroll. The
 * bubble appears just below the trigger, right-aligned to the trigger's
 * right edge — so it visually "originates" from the icon.
 *
 * The bubble is portaled to document.body at JS mount (one-time) to
 * escape any ancestor stacking-context trap; see help.js comments.
 */

.hm-help__bubble {
    position: fixed;
    /* top / right / width are set inline by JS from the trigger rect.
     * The CSS defaults below are fallbacks for unstyled (initial) state —
     * they keep the bubble out of view before its first positioning pass. */
    top: -9999px;
    right: 16px;
    width: min(420px, calc(100vw - 32px));
    max-height: 80vh;
    background: #fff;
    border-radius: 14px;
    padding: 28px 24px 22px;
    box-shadow:
        0 24px 64px rgba(0, 0, 0, 0.32),
        0 0 0 1px rgba(33, 112, 59, 0.08);
    box-sizing: border-box;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px) scale(0.98);
    transform-origin: top right;
    transition: opacity 200ms ease, transform 200ms ease, visibility 0s linear 200ms;
    z-index: 100101;
    overflow-y: auto;
}

.hm-help__bubble[aria-hidden="false"] {
    opacity: 1;
    visibility: visible;
    transform: translateY(0) scale(1);
    transition: opacity 200ms ease, transform 200ms ease, visibility 0s linear 0s;
}

.hm-help__bubble-close {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: transparent;
    border: none;
    color: #6b6b6b;
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color 120ms ease, color 120ms ease;
    padding: 0;
}

.hm-help__bubble-close:hover {
    background: rgba(33, 112, 59, 0.08);
    color: #21703b;
}

.hm-help__bubble-close:focus-visible {
    outline: 2px solid #21703b;
    outline-offset: 2px;
}

.hm-help__bubble-heading {
    margin: 0 0 12px 0;
    padding-right: 32px; /* clear the close button */
    font-size: 20px;
    font-weight: 700;
    color: #222;
    line-height: 1.3;
}

.hm-help__bubble-answer {
    margin: 0;
    font-size: 15px;
    line-height: 1.55;
    color: #444;
    white-space: pre-line; /* honor manual line breaks if a future answer adds them */
}

@media (prefers-reduced-motion: reduce) {
    .hm-help__trigger,
    .hm-help__trigger--floating,
    .hm-help__dropdown,
    .hm-modal-backdrop,
    .hm-help__bubble,
    .hm-help__question,
    .hm-help__bubble-close {
        transition: none;
    }
    .hm-help__trigger:active {
        transform: none;
    }
    .hm-help__bubble {
        transform: none;
    }
    .hm-help__bubble[aria-hidden="false"] {
        transform: none;
    }
}

@media (max-width: 576px) {
    .hm-help__dropdown {
        /* Phase 7-2b — viewport-anchored positioning to fix the prod-
         * only L/R-scroll bug Adam reported at exactly 575px.
         *
         * Previous (§64 / 7-1q): `position: absolute` with `width:
         * calc(100vw - 24px); right: -55px`. The -55px back-extension
         * was tuned against one specific local header layout where the
         * trigger's right edge sits 67px from the viewport's right edge.
         * On prod the trigger sits closer to the viewport-right edge,
         * so -55px back-extends the dropdown 30-40px PAST the viewport
         * right edge → page-level horizontal scroll on every mobile
         * page (the dropdown is always in the DOM with aria-hidden=true,
         * absolute-positioned descendants still contribute to scrollWidth).
         *
         * Fix: switch to position: fixed at the mobile breakpoint, anchor
         * to viewport edges (12px gutters), and let JS in help.js set
         * the `top` from the trigger's bounding rect at open time. The
         * dropdown can no longer overflow in either direction regardless
         * of where the trigger sits in the live header layout.
         *
         * The `top` fallback (64px) is for the brief window after the
         * dropdown opens before JS sets the inline style — it positions
         * the dropdown below an approximate header height so the
         * fallback render isn't visibly jarring. */
        position: fixed;
        top: 64px;
        left: 12px;
        right: 12px;
        width: auto;
        max-width: none;
    }
    /*
     * Bubble width on mobile: the JS positioner already clamps width to
     * min(420px, 100vw - 32px), so on a 360px phone the bubble is 328px
     * wide with 16px gutters on both sides — fits cleanly. Just tighten
     * the inner padding and heading size for the smaller surface.
     */
    .hm-help__bubble {
        padding: 24px 18px 18px;
    }
    .hm-help__bubble-heading {
        font-size: 17px;
    }
}

/* ============================================================================
 * === Onboarding tour (Phase 7k) =============================================
 * ============================================================================
 *
 * Lottie mascot + speech bubble that walks logged-in users through five
 * spotlighted UI targets the first time they land on the homepage. The
 * page-behind is dimmed via a 9999px box-shadow on the active target (the
 * "spotlight" trick), so attention focuses on one element at a time
 * without an opaque overlay element intercepting clicks.
 *
 * Two distinct overlay primitives in the addon now:
 *   - .hm-modal-backdrop (Phase 7i)  — page-wide blur + dim with bubble UI
 *                                       at the focal center. Used by the help
 *                                       Q&A modal.
 *   - tour spotlight (this section)  — target-element box-shadow with the
 *                                       guide (mascot + bubble) attached
 *                                       relative to the target. NO separate
 *                                       backdrop element; the dim IS the
 *                                       target's own shadow.
 *
 * Z-index policy:
 *   - .hm-tour wrapper:     pointer-events:none (clicks pass through to
 *                            spotlighted target). No z-index needed; the
 *                            wrapper is transparent except for its child
 *                            guide.
 *   - .hm-tour__guide:      2147483601 (just below toast at MAX_SAFE_INT)
 *   - tour target inline:   2147483600 (z-index applied by JS for the
 *                            duration of the step; restored on advance)
 *
 * Reduced motion:
 *   - .hm-tour__guide       transition: none (no smooth slide between
 *                            steps; positions jump)
 *   - Mascot                paused at frame 0 by tour.js (Lottie itself
 *                            doesn't honor prefers-reduced-motion)
 */

.hm-tour {
    position: fixed;
    inset: 0;
    z-index: 2147483600;
    /* Clicks pass through the empty surface so the spotlighted target
     * stays interactive. The guide re-enables pointer events on itself. */
    pointer-events: none;
}

.hm-tour__guide {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    align-items: flex-end;
    gap: 12px;
    z-index: 2147483601;
    pointer-events: auto;
    /* Smooth slide between step positions. JS sets top/left; the transition
     * animates the movement so the mascot appears to walk to the next
     * target rather than teleport. */
    transition: top 280ms ease, left 280ms ease;
}

.hm-tour__mascot {
    /* Mascot sized at 360px on desktop (3× the original 120px — Adam
     * iterated: spec was 120, "100% bigger" pushed to 240, then "50%
     * bigger again" lands here). On mobile the guide stacks vertically
     * and the mascot drops to 200px so it doesn't dominate the small
     * viewport — see the @media block below. */
    width: 360px;
    height: 360px;
    flex-shrink: 0;
    /* §67: smooth the horizontal flip between steps so the donkey "turns"
     * rather than snapping. */
    transition: transform 220ms ease;
    /* Lottie injects an <svg> sized to its container; height/width on this
     * div is what the rendered mascot ends up at. */
}

/* §67 team art-direction: mirror the donkey so it faces the element it's
 * introducing. tour.js toggles this per step. */
.hm-tour__mascot--flip {
    transform: scaleX(-1);
}

/*
 * Tour backdrop — full-viewport dim shown only on steps with no
 * spotlight target (completion "All set!" frame, and the goodbye frame
 * if admin hasn't placed the help block). Steps that DO have a target
 * rely on the spotlight's box-shadow for dim — adding a backdrop on
 * top of that would double-dim the target itself.
 *
 * z-index sits just below the guide so the mascot + bubble stay above
 * the dim. The 0.85 alpha matches the spotlight shadow alpha for
 * visual continuity between dim-via-shadow and dim-via-backdrop.
 */
.hm-tour__backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.85);
    z-index: 2147483599;
    pointer-events: none;
    opacity: 0;
    transition: opacity 220ms ease;
}

.hm-tour__backdrop[aria-hidden="false"] {
    opacity: 1;
}

/* Lottie's injected svg may add aria attributes we don't need. */
.hm-tour__mascot svg {
    width: 100%;
    height: 100%;
    display: block;
}

/* Directional arrow Lottie (§67). Positioned by tour.js in the gap
 * between the guide and the spotlighted element, pointing at it. Same
 * z-index as the guide; clicks pass through (the target stays
 * interactive). Smooth reposition matches the guide's slide. Size kept
 * in sync with ARROW_SIZE in tour.js. */
.hm-tour__arrow {
    position: absolute;
    top: 0;
    left: 0;
    /* Default size; tour.js sets width/height inline per render (responsive
     * 3× — 192px desktop / 110px mobile, §67) so the layout math and the
     * rendered size always match. */
    width: 192px;
    height: 192px;
    z-index: 2147483601;
    pointer-events: none;
    filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35));
    transition: top 280ms ease, left 280ms ease;
}

.hm-tour__arrow svg {
    width: 100%;
    height: 100%;
    display: block;
}

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

.hm-tour__bubble {
    position: relative;
    background: #fff;
    border-radius: 16px;
    padding: 22px 22px 18px;
    box-shadow:
        0 12px 40px rgba(0, 0, 0, 0.35),
        0 0 0 1px rgba(33, 112, 59, 0.08);
    max-width: 320px;
    font-size: 14px;
    line-height: 1.45;
    color: #2a2a2a;
}

/*
 * Optional per-step heading — currently used only by step 0 ("Howdy!")
 * for the friendly tour opener. Bold, slightly larger, brand-green to
 * read as a warm mascot greeting rather than a generic dialog title.
 * tour.js flips display:none → display:'' based on step.heading
 * presence.
 */
.hm-tour__bubble-heading {
    margin: 0 28px 8px 0;
    font-size: 20px;
    font-weight: 700;
    color: #21703b;
    line-height: 1.2;
}

.hm-tour__close {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: none;
    background: rgba(0, 0, 0, 0.06);
    color: #444;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color 120ms ease, color 120ms ease;
}

.hm-tour__close:hover,
.hm-tour__close:focus-visible {
    background: rgba(0, 0, 0, 0.12);
    color: #000;
    outline: none;
}

.hm-tour__bubble-text {
    margin: 0 28px 14px 0;  /* right room avoids the close-button overlap */
}

.hm-tour__next {
    display: inline-block;
    padding: 9px 18px;
    background: #21703b;
    color: #f5efdc;
    font-weight: 600;
    font-size: 13px;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    font-family: inherit;
    transition: background-color 120ms ease, transform 80ms ease;
}

.hm-tour__next:hover,
.hm-tour__next:focus-visible {
    background: #2a8748;
    outline: none;
}

.hm-tour__next:focus-visible {
    box-shadow: 0 0 0 3px rgba(33, 112, 59, 0.35);
}

.hm-tour__next:active {
    transform: scale(0.97);
}

/* Phase 7-2c — this rule used to hide the Next button on
 * action-driven steps so users would learn the in-context interaction
 * (tap the spotlighted drawer trigger). Two problems emerged in
 * practice: (1) some users couldn't click the spotlighted target —
 * mobile touch precision, assistive tech, drawer overlay leftovers
 * from a `closes:` step — and got stuck with no advancement path
 * ("beginning-of-tour lockout"), (2) older testers reported the
 * "tap the highlighted thing" pattern as confusing in usability
 * feedback. Now Next is shown on every step as a primary advancement
 * affordance; object-click still works as a parallel path so the
 * in-context learning behavior is preserved for users who follow it.
 * See tour.js renderStep + advance() re-entrancy guard. */

/* §54.10: flip mascot to the right side of the guide when the guide
 * is placed to the right of its anchor (e.g. nav_categories: guide
 * sits to the right of the open left-edge nav drawer). Without this,
 * the default DOM order (mascot first, bubble second) puts the mascot
 * adjacent to the drawer, overlapping it visually. row-reverse pushes
 * the mascot to the far side of the guide so the drawer stays
 * uncovered. Rule placed BEFORE the mobile @media block so the
 * mobile-layout column-direction (same specificity, later in source)
 * still wins on small viewports. */
.hm-tour__guide--mascot-right {
    flex-direction: row-reverse;
}

/* §67 team art-direction: stack the bubble UNDER the donkey (donkey on
 * top) or ABOVE it. column-reverse puts the bubble first (on top) since
 * DOM order is mascot → bubble. Centered so the bubble sits beneath/above
 * the donkey rather than off to one side. */
.hm-tour__guide--stack-below {
    flex-direction: column;
    align-items: center;
}
.hm-tour__guide--stack-above {
    flex-direction: column-reverse;
    align-items: center;
}

/* Reduced-motion override: position changes are instant; the mascot still
 * animates by default but tour.js freezes it on frame 0 in this mode. */
@media (prefers-reduced-motion: reduce) {
    .hm-tour__guide {
        transition: none;
    }
    .hm-tour__next {
        transition: none;
    }
    .hm-tour__close {
        transition: none;
    }
}

/* Mobile — stack the guide vertically (mascot above bubble). A 240px
 * mascot side-by-side with a 280px bubble doesn't fit on small
 * viewports; stacking gives both elements their own row and lets the
 * bubble use most of the horizontal space for readable text. The
 * mascot drops to 140px on mobile — still visibly the mascot, but not
 * dominating the viewport. */
@media (max-width: 576px) {
    .hm-tour__guide {
        flex-direction: column;
        align-items: center;
        gap: 8px;
    }
    .hm-tour__mascot {
        width: 200px;
        height: 200px;
    }
    .hm-tour__bubble {
        max-width: calc(100vw - 32px);
        padding: 20px 18px 16px;
        font-size: 13.5px;
    }
    .hm-tour__bubble-heading {
        font-size: 18px;
        margin-right: 24px;
    }
    .hm-tour__bubble-text {
        margin-right: 24px;
    }
}

/* ============================================================================
 * === Vector logo block (Phase 7l, refined Phase 7o) =========================
 * ============================================================================
 *
 * Inline-SVG logo. Source SVG is 500×102 (≈4.9:1).
 *
 * Sizing: locked at width 140px (height auto → ~28.6px). Phase 7o
 * initially tried a clamp()-driven fluid curve (140-253px), but the
 * static 140px sits cleaner in the header strip — the mark stays
 * prominent at every viewport without forcing the row to grow on
 * desktop. max-width: 100% guards against the SVG overflowing if the
 * admin shrinks the cell below 140px.
 *
 * Vertical alignment: the .tygh-top-panel row uses align-items: center,
 * so the row naturally hugs the tallest child (countdown pill, help
 * button, or logo) and the others re-center automatically. No explicit
 * header height or vertical padding — flex does it all.
 * ============================================================================ */

.hm-logo-vector {
    display: inline-block;
    line-height: 0;
}

.hm-logo-vector__link {
    display: inline-block;
    line-height: 0;
    text-decoration: none;
}

/* SVG carries width="140" height="29" attrs in logo_vector.tpl so the
 * mark has intrinsic dimensions; this rule pins the rendered size so
 * the proportional Bootstrap .span2 cell can't shrink it. No
 * max-width: 100% — the goal is "always 140px, even if the cell is
 * narrower". If the cell ever clips, the row's flex-wrap kicks in
 * before that becomes visible. */
.hm-logo-vector__svg {
    display: block;
    width: 140px;
    height: 29px;
}

/* Vertical alignment of the header row (logo + countdown + help). The
 * row lives at `.tygh-top-panel > .container-fluid.top-grid >
 * .row-fluid:first-child` per the admin layout. Horizontal placement
 * (column widths, distribution) is handled by the admin grid config;
 * this rule only contributes align-items: center so cell-to-cell
 * vertical alignment stays consistent regardless of which child is
 * tallest.
 *
 * Stock `.tygh-top-panel > div` has zero vertical padding, so without
 * an override the chrome row sits flush against the strip's top edge
 * AND against the discount-banner row underneath. Symmetric 8px
 * top/bottom gives the logo / countdown / help / cart breathing room
 * above and below, and separates the chrome row from the banner row
 * without disturbing horizontal layout.
 *
 * margin-left: auto on the help cell pushes it to the right edge of
 * the flex row. The .ty-float-right wrapper inside the cell doesn't
 * help here because floats don't apply in flex containers — auto
 * margin is the flex-equivalent. */
.tygh-top-panel .top-grid > .row-fluid:first-child {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    padding-top: 8px;
    padding-bottom: 8px;
}

.tygh-top-panel .top-grid > .row-fluid:first-child > .top-links-grid {
    margin-left: auto;
}

@media (max-width: 575px) {
    .tygh-top-panel .top-grid > .row-fluid {
        flex-basis: 180px;
    }
}

/* ============================================================================
 * === Shop landing (Phase 7j) ===============================================
 * ============================================================================
 *
 * Three blocks live on the bare /shop/ landing (and are admin-placeable on
 * any other layout): harvestly_shop_hero, harvestly_shop_features,
 * harvestly_shop_aisles. All three suppress on /shop/<category>/ via the
 * render_block_pre hook (func.php). See NOTES §24.
 *
 *   .hm-shop-hero      — small sibling of the homepage .hm-hero
 *   .hm-shop-features  — 1 hero card + 2 supporting cards editorial grid
 *   .hm-shop__aisles--landing — same DOM as /shop/<cat>/ aisles, no overrides
 *
 * The aisles block reuses the existing .hm-aisle / .hm-aisle-card rule
 * sets (NOTES §20). The .hm-shop__aisles wrapper is reused unchanged.
 * ========================================================================= */

/* ---- Shop hero ----------------------------------------------------------- */

/* Phase 7o swap: the prior SVG layered scene (linear sky-to-grass
 * gradient + 3 tree silhouette layers + ground band + sun disc) was
 * replaced with a single photographic background — a brand flat-lay
 * (canvas tote, fresh bread, milk, juice, produce, prepared meals,
 * eggs on wood plank) at 2000px wide. The image speaks louder than
 * the vector scene and reads as the actual product, not a
 * stylized illustration of it.
 *
 * background-size: cover + center keeps the flat-lay framed across
 * viewport widths. The overlay rule below adds a super-subtle radial
 * dim at the center band so the white welcome line and subheadline
 * stay legible against the brighter areas of the image (the bread,
 * milk bottle, and pie are bright spots that fall under the text).
 *
 * Reduced-motion + low-bandwidth: the image is a static JPG, no
 * parallax, no animations. About 836KB — comparable to a couple of
 * the product carousel cards' images and well under the
 * hero-asset budget. */
.hm-shop-hero {
    position: relative;
    width: 100%;
    height: 25vh;
    min-height: 200px;
    max-height: 360px;
    overflow: hidden;
    background-color: #2a4a30;
    background-image: url('/js/addons/harvestly_modern/data/harvestly-flatlay.jpg');
    background-size: cover;
    background-position: center center;
    background-repeat: no-repeat;
}

/* Super-subtle center-weighted dim. The image is the star — this
 * exists only to lift the white text's contrast against the
 * brightest highlights (bread crust, milk bottle, pie crust). The
 * welcome text already carries its own white-halo + dark drop-shadow
 * stack (see .hm-shop-hero__welcome-text), so this overlay just
 * tilts the odds a hair further. 0.18 at center fading to 0 at 60%
 * lands in "you barely notice it" territory.
 *
 * Earlier draft used rgba(0,0,0,0.25) which read as visibly dim —
 * dialed back per Adam's "make it super subtle because the image is
 * beautiful" direction. If text legibility ever needs more help,
 * the cheap move is to raise the center alpha here (0.22, 0.25)
 * rather than reach back for a heavy gradient. */
.hm-shop-hero__overlay {
    position: absolute;
    inset: 0;
    z-index: 2;
    background: radial-gradient(ellipse at center,
        rgba(0, 0, 0, 0.18) 0%,
        rgba(0, 0, 0, 0) 60%);
    pointer-events: none;
}

.hm-shop-hero__content {
    position: relative;
    z-index: 3;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 0 clamp(20px, 5vw, 60px);
    max-width: 1200px;
    margin: 0 auto;
}

/* "Welcome to the [logo] Shop" centerpiece. The <h1> is a flex
 * container so the inline logo SVG sits in the text baseline
 * naturally and the wrap (on narrow screens) keeps "Welcome to the",
 * the logo, and "Shop" together. Font is Montserrat with a Helvetica
 * fallback chain — see hooks/index/links.post.tpl for the Google
 * Fonts load.
 *
 * Glow strategy:
 *   - Text gets a soft white text-shadow halo + a dark shadow for
 *     contrast lift against bright sky portions of the gradient.
 *   - Logo gets a slightly stronger drop-shadow glow per Adam's
 *     spec ("slightly more Glow") so it reads as the focal point. */
/* "Welcome to the [logo] Shop" — Poppins via --hm-font-headline
 * (Phase 7-1m §60). The page-defining title of /shop/. Same family
 * as the page H1s on the inner shop surfaces (`.hm-shop__heading`,
 * `.hm-vendor-directory__heading`, `.hm-vendor-identity__name`,
 * `.hm-shop__aisles-title`) so the shop's brand voice is coherent
 * from the landing hero down to deeper pages.
 *
 * Weight shifted 500→600 — Poppins SemiBold reads better at this
 * clamp range against the photographic flatlay backdrop than the
 * Montserrat 500 it replaces; the glow stack on the framing words
 * still keeps the line legible across bright/dark image areas.
 * Tracking flipped +0.01em → -0.005em to match Poppins's display
 * preference (slightly tighter at large sizes). */
.hm-shop-hero__welcome {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.35em;
    margin: 0 0 12px 0;
    padding: 0;
    color: #fff;
    font-family: var(--hm-font-headline);
    font-size: clamp(20px, 3.5vw, 42px);
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: -0.005em;
}

/* If the welcome line is the only content (admin left subheadline and
 * CTA blank), drop the bottom margin so the centered flex layout
 * doesn't render with the line a few px above true center. */
.hm-shop-hero__welcome:last-child {
    margin-bottom: 0;
}

.hm-shop-hero__welcome-text {
    /* Slight extra weight on the framing words so the eye reads the
     * line as a single phrase even when the logo is between them.
     * §60: bumped 500→600 to match the Poppins parent weight. */
    font-weight: 600;
    text-shadow:
        0 0 20px rgba(255, 255, 255, 0.45),
        0 0 6px rgba(255, 255, 255, 0.55),
        0 2px 10px rgba(0, 0, 0, 0.45);
}

.hm-shop-hero__welcome-logo {
    /* Sized to roughly 1.6em tall so the wordmark reads at a similar
     * weight to the surrounding text. width:auto preserves the
     * 500:102 aspect; vertical-align centers the baseline-rendered
     * SVG against the flex line. The dropShadow chain layers a soft
     * white glow on top of a darker depth shadow for the same
     * dual-purpose effect as the text-shadow stack. */
    height: 1.6em;
    width: auto;
    flex-shrink: 0;
    color: #fff;
    filter:
        drop-shadow(0 0 24px rgba(255, 255, 255, 0.55))
        drop-shadow(0 0 8px rgba(255, 255, 255, 0.65))
        drop-shadow(0 2px 8px rgba(0, 0, 0, 0.35));
}

/* Phase 7o final: Caveat Bold (handwritten script), bumped ~6pt over
 * the prior body-text size. The white welcome line above keeps its
 * halo-glow stack (reads as luminous brand statement); this script
 * subheadline instead uses a single heavy drop-shadow (reads as
 * handwritten ink sitting above the image).
 *
 * Caveat is loaded via Google Fonts in hooks/index/links.post.tpl
 * alongside Montserrat. Fallback chain falls through to a generic
 * cursive then sans-serif so a slow font load doesn't show a bare
 * system serif before swap. */
.hm-shop-hero__subheadline {
    margin: 0 0 16px 0;
    max-width: 640px;
    font-family: 'Caveat', 'Brush Script MT', cursive, 'Helvetica Neue', sans-serif;
    font-size: clamp(26px, 2.6vw, 30px);
    font-weight: 700;
    line-height: 1.25;
    color: #fff;
    text-shadow: 0 4px 10px rgba(0, 0, 0, 0.6);
    text-align: center;
}

.hm-shop-hero__cta {
    display: inline-block;
    padding: 12px 26px;
    background: var(--hm-brand-green, #21703b);
    color: #fff;
    font-weight: 600;
    font-size: clamp(14px, 1.1vw, 16px);
    border-radius: 8px;
    text-decoration: none;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease;
}
.hm-shop-hero__cta:hover {
    background: #2a8748;
    transform: translateY(-1px);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
    text-decoration: none;
    color: #fff;
}
.hm-shop-hero__cta:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 3px;
}

@media (max-width: 576px) {
    /* Phase 7-2h (§81) — was height:20vh / min-height:160px, the same
     * fixed-height-clips-centered-content pattern that clipped the
     * homepage hero in 7-2b (§75.1). At 375px the content stack (welcome
     * line + Caveat subheadline + "Browse the aisles" CTA) measures
     * ~177px but the box was capped at 160px; overflow:hidden +
     * justify-content:center spilled it both ends — welcome line lost
     * ~8px off its top (Adam's reported clip), CTA ~9px off its bottom.
     * Let the hero grow to fit (height:auto), keep a tactile min-height
     * floor, and drop the desktop max-height:360px cap so a tall stack
     * isn't re-clipped. Vertical padding on the content gives the
     * background-image room to breathe around the text. */
    .hm-shop-hero {
        height: auto;
        min-height: 160px;
        max-height: none;
    }
    .hm-shop-hero__content {
        padding-top: 24px;
        padding-bottom: 24px;
    }
}

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

/* ---- Shop features ------------------------------------------------------- */

/* Mobile-first single column; desktop is 2fr/1fr (hero card left, two
 * supporting cards stacked right). Empty slots collapse naturally — the
 * supporting column hides via :empty/no-render in the template, and the
 * hero column extends full-width when only it is populated. */
.hm-shop-features {
    margin: 32px 0 24px 0;
    padding: 0 var(--hm-page-padding, 16px);
}

/* Section title: leaf mark + "Features" wordmark with a slow shimmer
 * sweep. Sized to read as a section header on its own, not just a
 * label above the grid — keeps the eye anchored before the editorial
 * cards take over. */
.hm-shop-features__title {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    max-width: 1200px;
    margin: 0 auto 20px auto;
    padding: 0;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-weight: 700;
    font-size: 28px;
    line-height: 1;
    letter-spacing: 0.01em;
}

.hm-shop-features__title-mark {
    width: 38px;
    height: 28px;
    flex-shrink: 0;
    /* Subtle wiggle on the leaves — matches the editorial-feel of the
     * shimmer text without competing for attention. */
    animation: hm-leaf-bob 4.5s ease-in-out infinite;
    transform-origin: 50% 80%;
}

/* Text-only shimmer: a single light band sweeps left → right across
 * the text, then a pause off-screen before the next sweep. Brand
 * green base shows whenever the light band isn't in view (the
 * background-color paints under the clipped text region; no-repeat
 * ensures the gradient doesn't tile and show extra bands).
 *
 * Animation timeline:
 *   0%  → 60%   sweep light band from off-left to off-right (linear)
 *   60% → 100%  rest at off-right (no light visible, solid green)
 * This gives a noticeable pause between sweeps and guarantees the
 * band never stops mid-text. */
.hm-shop-features__title-text {
    background-color: #21703b;
    background-image: linear-gradient(
        100deg,
        rgba(33, 112, 59, 0) 0%,
        rgba(33, 112, 59, 0) 40%,
        #b9e6a6 50%,
        rgba(33, 112, 59, 0) 60%,
        rgba(33, 112, 59, 0) 100%
    );
    background-size: 200% 100%;
    background-repeat: no-repeat;
    background-position: -150% 0;
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
            color: transparent;
    animation: hm-shimmer-sweep 4s linear infinite;
}

@keyframes hm-shimmer-sweep {
    0%   { background-position: -150% 0; }  /* light off-left */
    60%  { background-position:  250% 0; }  /* light off-right */
    100% { background-position:  250% 0; }  /* rest off-right */
}

@keyframes hm-leaf-bob {
    0%, 100% { transform: rotate(-3deg); }
    50%      { transform: rotate(3deg); }
}

@media (prefers-reduced-motion: reduce) {
    .hm-shop-features__title-mark,
    .hm-shop-features__title-text {
        animation: none;
    }
    .hm-shop-features__title-text {
        /* When motion is off, fall back to a static brand-green fill
         * so the gradient doesn't freeze mid-sweep with partial reveal. */
        background-image: none;
        -webkit-text-fill-color: #21703b;
                color: #21703b;
    }
}

@media (max-width: 575px) {
    .hm-shop-features__title {
        font-size: 22px;
        gap: 10px;
    }
    .hm-shop-features__title-mark {
        width: 30px;
        height: 22px;
    }
}

.hm-shop-features__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
    max-width: 1200px;
    margin: 0 auto;
}

/* Phase 7-1h — flip point dropped from 768 to 600. At 767px the
 * hero card was a wide-stretched single column above two stacked
 * supporting cards; the layout snapped to 2fr 1fr at 768. Bringing
 * the flip earlier (600px) lands the two-column proportion at the
 * width where the hero card stops looking awkward as a full-width
 * single. Hero-vs-supporting visual hierarchy (2fr 1fr) preserved
 * over `auto-fit` equal-column alternative. */
@media (min-width: 600px) {
    .hm-shop-features__grid {
        grid-template-columns: 2fr 1fr;
        gap: 20px;
        /* Phase 7-3a — the hero (feature #1) sits in the 2fr column beside
         * the 1fr supporting column (two stacked cards). The grid's default
         * `align-items: stretch` was inflating whichever card is shorter to
         * match the taller column — leaving dead white space inside the
         * hero card. `start` lets each column take its natural height; the
         * leftover space becomes page gap, not deadspace inside the card. */
        align-items: start;
    }
}

.hm-shop-features__supporting {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Phase 7o final: each card carries its own brand-green border + soft
 * outward glow that breathes on a 5s ease-in-out pulse. Moved here
 * from the grid container (which had wrapped all three cards) per
 * Adam's feedback — per-card framing reads more product-card-like
 * and less "billboard frame".
 *
 * Shadow stack layers:
 *   - 0 2px 8px black @ 0.08 — soft drop shadow (kept from v1; gives
 *     the card depth against the white page bg)
 *   - 1px crisp green halo (0.22 / 0.32 alpha resting / peak)
 *   - 14-18px tight green glow (0.24 / 0.36)
 *   - 32-42px wide soft green halo (0.13 / 0.20)
 *
 * Hover state previously had its own box-shadow change; dropped
 * because animation always wins over transition on the same
 * property. The translateY(-2px) lift stays — that's the tactile
 * feedback. The pulse continues running through hover. */
.hm-shop-feature {
    display: block;
    background: #fff;
    border: 2px solid var(--hm-brand-green, #21703b);
    border-radius: 12px;
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    box-shadow:
        0 2px 8px rgba(0, 0, 0, 0.08),
        0 0 0 1px rgba(33, 112, 59, 0.22),
        0 0 14px rgba(33, 112, 59, 0.24),
        0 0 32px rgba(33, 112, 59, 0.13);
    animation: hm-feature-pulse 5s ease-in-out infinite;
    transition: transform 180ms ease;
}
.hm-shop-feature:hover {
    transform: translateY(-2px);
    text-decoration: none;
    color: inherit;
}
.hm-shop-feature:focus-visible {
    outline: 2px solid var(--hm-brand-green, #21703b);
    outline-offset: 3px;
}

@keyframes hm-feature-pulse {
    0%, 100% {
        box-shadow:
            0 2px 8px rgba(0, 0, 0, 0.08),
            0 0 0 1px rgba(33, 112, 59, 0.22),
            0 0 14px rgba(33, 112, 59, 0.24),
            0 0 32px rgba(33, 112, 59, 0.13);
    }
    50% {
        box-shadow:
            0 2px 8px rgba(0, 0, 0, 0.08),
            0 0 0 1px rgba(33, 112, 59, 0.32),
            0 0 18px rgba(33, 112, 59, 0.36),
            0 0 42px rgba(33, 112, 59, 0.20);
    }
}

@media (prefers-reduced-motion: reduce) {
    .hm-shop-feature {
        animation: none;
        transition: none;
    }
}

.hm-shop-feature__image {
    width: 100%;
    overflow: hidden;
    background: #f0f0f0;
}
.hm-shop-feature__image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.hm-shop-feature__content {
    padding: 16px 18px;
}

.hm-shop-feature__headline {
    margin: 0 0 6px 0;
    font-size: 18px;
    font-weight: 700;
    color: #222;
    line-height: 1.25;
}

.hm-shop-feature__subhead {
    margin: 0 0 10px 0;
    font-size: 14px;
    line-height: 1.5;
    color: #555;
}

.hm-shop-feature__cta {
    display: inline-block;
    color: var(--hm-brand-green, #21703b);
    font-weight: 600;
    font-size: 14px;
}

/* Hero card — taller image, larger typography. */
.hm-shop-feature--hero .hm-shop-feature__image {
    aspect-ratio: 16 / 10;
}
.hm-shop-feature--hero .hm-shop-feature__headline {
    font-size: 24px;
}
.hm-shop-feature--hero .hm-shop-feature__subhead {
    font-size: 15px;
}

/* Supporting cards — 4:3 image, tighter typography, line-clamp on subhead
 * so the two cards have predictable heights even when admin copy is
 * uneven length. */
.hm-shop-feature--small .hm-shop-feature__image {
    aspect-ratio: 4 / 3;
}
.hm-shop-feature--small .hm-shop-feature__headline {
    font-size: 16px;
}
/* Phase 7-2h (§81) — features card reformat. Was -webkit-line-clamp:2,
 * which truncated the body copy at ~16 words on the supporting cards (the
 * cramped ones in the 1fr column beside the 2fr hero card). Per Adam: let
 * the copy flow naturally and accept the uneven card heights the clamp was
 * guarding against. The smaller body font (13→12.5px) + tighter leading
 * (1.5→1.45) keeps the now-unclamped copy from ballooning the card in the
 * narrow column. Option A (pure CSS) per scoping — the §61 fit-text route
 * is reserved for a follow-up if this proves insufficient. (There was no
 * max-height on .hm-shop-feature to relax — the cards were already
 * flex-height; the line-clamp was the sole truncation mechanism.) */
.hm-shop-feature--small .hm-shop-feature__subhead {
    font-size: 12.5px;
    line-height: 1.45;
    display: block;
    -webkit-line-clamp: unset;
    overflow: visible;
}

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

/* ---- Shop aisles (landing variant) -------------------------------------- */

/* The .hm-aisle / .hm-aisle-card rule sets from NOTES §20 do the heavy
 * lifting. Only thing to do for the landing variant is the outer
 * spacing — slightly different than /shop/<cat>/ to give the hero +
 * features room above. */
.hm-shop__aisles--landing {
    margin: 24px 0 48px 0;
    padding: 0 var(--hm-page-padding, 16px);
    max-width: 1200px;
    margin-left: auto;
    margin-right: auto;
}

/* "Shop the Aisles" title above the landing aisle list. Same vertical
 * rhythm + alignment as the features title but without the shimmer
 * (one shimmery title per page; this one stays calm). */
/* "Shop the Aisles" — page-defining title on /shop/ landing. Poppins
 * via --hm-font-headline (Phase 7-1m §60), same family as the other
 * page H1s. Tracking flipped from +0.01em to -0.005em — Poppins display
 * sizes look more confident with very slightly tighter tracking, but
 * less aggressive than the H1 family's -0.01em since this title is a
 * centered editorial label rather than a left-anchored page heading. */
.hm-shop__aisles-title {
    max-width: 1200px;
    margin: 16px auto 16px auto;
    padding: 0 var(--hm-page-padding, 16px);
    font-family: var(--hm-font-headline);
    font-weight: 700;
    font-size: clamp(24px, 3vw, 32px);
    line-height: 1.1;
    letter-spacing: -0.005em;
    color: var(--hm-color-green-dark);
    text-align: center;
}

@media (max-width: 575px) {
    .hm-shop__aisles-title {
        font-size: 22px;
        margin-top: 24px;
    }
}

/* Aisle header layout: title left, "Browse all <Cat> →" link right.
 * Originally scoped to .hm-shop__aisles--landing only because the
 * CTA lived solely on the /shop/ landing. Phase 7m extends the CTA
 * to subcategory aisles within /shop/<top>/ pages (drill-down into
 * /shop/<top>/<sub>/), so the layout rules now apply unscoped —
 * every aisle header with a CTA child renders the same way. Headers
 * without a CTA child (none currently) still flex correctly with a
 * single h2 inside.
 *
 * Mobile drop: at narrow widths the link wraps under the heading
 * naturally (flex-wrap), so it never gets visually squeezed against
 * the title. */
.hm-aisle__header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px 16px;
}

/* "Browse all <Cat> →" — Montserrat accent eyebrow role via
 * --hm-font-accent (Phase 7-1m §60). Sized 13px UPPERCASE 0.05em
 * matches the homepage `.hm-pc__view-all` / `.hm-vc__view-all` rule
 * (lines 4431-4445). The accent vocabulary signals "this is a
 * view-all CTA" consistently across homepage + shop surfaces.
 * Trailing arrow stays inline in the anchor text; letter-spacing
 * pulse on hover preserved for the subtle "step into it" affordance. */
.hm-aisle__view-all {
    flex-shrink: 0;
    color: var(--hm-color-green-dark);
    font-family: var(--hm-font-accent);
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    text-decoration: none;
    transition: color 120ms ease, letter-spacing 120ms ease;
}
.hm-aisle__view-all:hover,
.hm-aisle__view-all:focus-visible {
    color: var(--hm-color-green-fresh);
    text-decoration: none;
    letter-spacing: 0.07em;
    outline: none;
}
.hm-aisle__view-all:focus-visible {
    outline: 2px solid var(--hm-color-green-dark);
    outline-offset: 3px;
    border-radius: 2px;
}

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

.hm-shop--landing {
    /* Landing wrapper — visible content is supplied by the three blocks
     * placed via the layout system. The wrapper itself only exists so
     * the controller's view.tpl has something to render into the
     * location's 'main' block. Zero-height by default so it doesn't add
     * empty vertical space between blocks. */
    min-height: 0;
}

/* === Shop landing mode picker (Phase 7p) ==================================
 *
 * Three-tab tab-style control above the aisles content (Aisles /
 * Vendors / On Sale). Visually a pill-grouped chip with the active
 * tab in brand green. Tabs are role="tab" buttons; aria-selected
 * drives the active visual via attribute selector so JS only has to
 * flip one DOM attribute to update both the class-driven and a11y-
 * driven state. */

.hm-shop-aisles {
    /* Outer wrapper carries no spacing of its own — the inner
     * .hm-shop__aisles--landing div in the rendered mode sub-template
     * keeps its existing margins. The wrapper's purpose is structural
     * (one element [data-hm-shop-aisles] for shop-aisles.js to find,
     * one stable scroll target for the floating jump button). */
}

.hm-shop-aisles__modes {
    display: flex;
    gap: 4px;
    padding: 6px;
    background: #f3f3f3;
    border-radius: 999px;
    margin: 24px auto 16px auto;
    max-width: 420px;
    /* Match the inner aisle list's gutter so the picker visually
     * aligns with the carousel content below. */
    padding-left: 6px;
    padding-right: 6px;
}

.hm-shop-aisles__mode {
    flex: 1;
    padding: 9px 16px;
    background: transparent;
    color: #555;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 14px;
    font-weight: 600;
    border: none;
    border-radius: 999px;
    cursor: pointer;
    letter-spacing: 0.02em;
    text-align: center;
    /* Transition extended to include box-shadow + transform so the
     * active-tab glow ramps in/out smoothly instead of snapping. */
    transition:
        background 200ms ease,
        color 140ms ease,
        box-shadow 240ms ease,
        transform 200ms ease;
    /* Reset CS-Cart's stock button styles which add gradients,
     * outlines, and shadows on .ty-btn. We're inside the responsive
     * theme but the global button reset isn't comprehensive enough
     * for this control. */
    box-shadow: none;
    text-shadow: none;
}

.hm-shop-aisles__mode:hover {
    background: rgba(33, 112, 59, 0.08);
    color: #333;
}

/* Active tab — layered brand-green glow.
 *
 * Four-stop box-shadow stack:
 *   1. 1px inner-tight ring (defines the pill edge against the gray pill-group)
 *   2. 10px soft halo at 50% brand-green alpha (the "lit up" feel)
 *   3. 22px wider halo at 25% (atmosphere / depth)
 *   4. 0 3px 8px drop shadow (subtle grounding so the tab reads "raised")
 *
 * Subtle linear-gradient on the background gives the pill a hint of
 * dimensionality — flat solid + glow can read a bit synthetic on
 * high-DPI displays. Tiny -1px lift on transform completes the
 * "popping forward" effect.
 *
 * A 2.4s breathing animation cycles the halo intensity between the
 * base + a slightly brighter peak. Disabled under prefers-reduced-
 * motion (see media query below). */
.hm-shop-aisles__mode--active,
.hm-shop-aisles__mode[aria-selected="true"] {
    background: linear-gradient(135deg, #2a8748 0%, var(--hm-brand-green, #21703b) 100%);
    color: #fff;
    box-shadow:
        0 0 0 1px rgba(33, 112, 59, 0.35),
        0 0 10px rgba(33, 112, 59, 0.50),
        0 0 22px rgba(33, 112, 59, 0.25),
        0 3px 8px rgba(0, 0, 0, 0.12);
    transform: translateY(-1px);
    animation: hm-shop-aisles__mode-breathe 2400ms ease-in-out infinite;
}

@keyframes hm-shop-aisles__mode-breathe {
    0%, 100% {
        box-shadow:
            0 0 0 1px rgba(33, 112, 59, 0.35),
            0 0 10px rgba(33, 112, 59, 0.50),
            0 0 22px rgba(33, 112, 59, 0.25),
            0 3px 8px rgba(0, 0, 0, 0.12);
    }
    50% {
        box-shadow:
            0 0 0 1px rgba(33, 112, 59, 0.40),
            0 0 14px rgba(33, 112, 59, 0.65),
            0 0 28px rgba(33, 112, 59, 0.35),
            0 3px 8px rgba(0, 0, 0, 0.12);
    }
}

.hm-shop-aisles__mode:focus-visible {
    outline: 2px solid var(--hm-brand-green, #21703b);
    outline-offset: 2px;
}

.hm-shop-aisles__content {
    /* Container for the mode-specific aisle list. innerHTML is
     * replaced on mode swap; the transition only affects opacity
     * during the in-flight fetch so users see a soft fade rather
     * than a stutter when the new HTML lands. */
    transition: opacity 160ms ease;
}

.hm-shop-aisles__content--loading {
    opacity: 0.55;
}

@media (prefers-reduced-motion: reduce) {
    .hm-shop-aisles__mode,
    .hm-shop-aisles__content {
        transition: none;
    }
    /* Reduced-motion: keep the glow STATIC (no breathing), and skip
     * the lift transform — the static halo is still visible as an
     * "active" signal, just without the animation that prompted the
     * preference. */
    .hm-shop-aisles__mode--active,
    .hm-shop-aisles__mode[aria-selected="true"] {
        animation: none;
        transform: none;
    }
}

@media (max-width: 575px) {
    .hm-shop-aisles__modes {
        max-width: none;
        margin-left: var(--hm-page-padding, 16px);
        margin-right: var(--hm-page-padding, 16px);
    }
    .hm-shop-aisles__mode {
        padding: 8px 8px;
        font-size: 13px;
        letter-spacing: 0;
    }
}

/* On Sale empty state — soft "nothing on sale" message. Auntie
 * voice, not preachy. Renders only when fn_harvestly_modern_get_sale_categories()
 * returns zero qualifying categories (no current discounts in the
 * catalog). */
.hm-shop__no-sale-empty {
    max-width: 1200px;
    margin: 24px auto 48px auto;
    padding: 64px var(--hm-page-padding, 16px);
    text-align: center;
    color: #666;
    font-size: 15px;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.hm-shop__no-sale-empty p {
    margin: 0;
}

/* §50: `.hm-shop-jump-to-aisles*` rules removed — the floating bottom-
 * right FAB was replaced by an inline `.hm-shop-hero__cta--browse` link
 * inside the hero, freeing the corner for the new cart FAB. The inline
 * anchor reuses `.hm-shop-hero__cta` styling. */

/* === Vendor pages (Phase 7q) ==============================================
 *
 * /shop/vendors/<vendor-slug>/ — designed vendor page under the
 * harvestly_modern umbrella. Identity strip (logo + name + location)
 * + authored story + filter dropdown + one aisle carousel per top-
 * level category the vendor sells in.
 *
 * Visual goal: substantial logo (120/100px), generous spacing,
 * first-person voice in the story section. The ED's "vendor helping
 * out" vibe carries via the descriptions themselves (96% coverage,
 * vendors writing in first person) — no photo extraction or
 * portrait fields. NOTES §36.1 documents the layout decision and
 * the data shape that drove it. */

.hm-vendor {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 var(--hm-page-padding, 16px) 48px var(--hm-page-padding, 16px);
}

/* --- Identity strip --- */

.hm-vendor-identity {
    display: flex;
    align-items: center;
    gap: 24px;
    padding: 24px 0 32px 0;
}

.hm-vendor-identity__logo {
    flex-shrink: 0;
    width: 120px;
    height: 120px;
    border-radius: 16px;
    overflow: hidden;
    background: #f5f5f5;
    /* Centering for logos that don't fully fill — see object-fit
     * comment below. The flex centering inside is for the rare case
     * a vendor's "theme" image is small and we want it centered
     * rather than top-left. */
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}

.hm-vendor-identity__logo img {
    width: 100%;
    height: 100%;
    /* object-fit: contain (NOT cover) — vendor logos are mostly
     * non-square landscape with brand marks; cropping destroys
     * recognition. Light-gray container background letterboxes the
     * difference. NOTES §12.3. */
    object-fit: contain;
}

.hm-vendor-identity__main {
    flex: 1;
    min-width: 0;
}

/* Vendor identity H1 — Poppins via --hm-font-headline (Phase 7-1m §60).
 * Same family as .hm-shop__heading + .hm-vendor-directory__heading. */
.hm-vendor-identity__name {
    margin: 0 0 6px 0;
    font-family: var(--hm-font-headline);
    font-size: clamp(28px, 3.5vw, 36px);
    font-weight: 700;
    line-height: 1.15;
    color: var(--hm-color-green-dark);
    letter-spacing: -0.01em;
}

.hm-vendor-identity__location {
    margin: 0;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 15px;
    font-weight: 500;
    color: #666;
}

/* --- Favorite vendor button (Phase 7r polish) ---
 *
 * Right-aligned within the identity strip on desktop (pushes to the
 * end of the flex row). On mobile (stacked identity strip), the
 * button drops below the name + location as a full-width band so
 * thumbs can reach it without crowding the logo.
 *
 * Two visual states:
 *   - Default ("Add to favorites"): outline heart, brand-green
 *     border + label, transparent background. Reads as an invite.
 *   - Active ("Favorited"): solid brand-green fill, white heart +
 *     label. Reads as committed.
 *
 * Logged-out variant (.--signin) shares the visual chrome but
 * renders as an <a> instead of a <button>; identical styling
 * works because we don't differentiate hover affordances. */

.hm-vendor-favorite {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    background: transparent;
    color: var(--hm-brand-green, #21703b);
    border: 1.5px solid var(--hm-brand-green, #21703b);
    border-radius: 999px;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 0.01em;
    cursor: pointer;
    text-decoration: none;
    /* Reset CS-Cart's stock button gradients/shadows. */
    box-shadow: none;
    text-shadow: none;
    transition: background 160ms ease, color 160ms ease, transform 120ms ease, box-shadow 160ms ease;
}

.hm-vendor-favorite:hover,
.hm-vendor-favorite:focus-visible {
    background: rgba(33, 112, 59, 0.08);
    color: var(--hm-brand-green, #21703b);
    text-decoration: none;
    outline: none;
}

.hm-vendor-favorite:focus-visible {
    box-shadow: 0 0 0 3px rgba(33, 112, 59, 0.18);
}

.hm-vendor-favorite:active {
    transform: scale(0.97);
}

/* Active state — favorited. Solid fill, white text + heart. The
 * heart icon picks up `color` via fill="currentColor" in the SVG. */
.hm-vendor-favorite--active {
    background: var(--hm-brand-green, #21703b);
    color: #fff;
    border-color: var(--hm-brand-green, #21703b);
}

.hm-vendor-favorite--active:hover,
.hm-vendor-favorite--active:focus-visible {
    background: #2a8748;
    color: #fff;
    border-color: #2a8748;
}

.hm-vendor-favorite__icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    transition: transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Subtle "pop" animation on the heart when entering active state.
 * The .--active modifier fires on the parent; the icon scales up
 * briefly via the cubic-bezier overshoot. The animation runs
 * once on class addition. */
.hm-vendor-favorite--active .hm-vendor-favorite__icon {
    animation: hm-vendor-favorite-pop 320ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes hm-vendor-favorite-pop {
    0%   { transform: scale(1); }
    50%  { transform: scale(1.25); }
    100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .hm-vendor-favorite,
    .hm-vendor-favorite__icon {
        transition: none;
        animation: none;
    }
    .hm-vendor-favorite:active {
        transform: none;
    }
}

/* --- Story (vendor description, rendered as-authored) --- */

.hm-vendor-story {
    padding: 0 0 32px 0;
    border-bottom: 1px solid #eee;
    margin-bottom: 32px;
}

/* Vendor story body — Open Sans via --hm-font-body (Phase 7-1m §60).
 * Long-form vendor bios (200-1500 words) read tiringly in Montserrat
 * (display/accent face) — Open Sans is purpose-built for long-form
 * body copy. Color shifted to brand green-darkest at 0.85 opacity
 * matching the homepage `.hm-intro__body` pattern. Max-width 760
 * preserved (~70 char measure for comfortable line length). */
.hm-vendor-story__content {
    font-family: var(--hm-font-body);
    font-size: 16px;
    line-height: 1.65;
    color: var(--hm-color-green-darkest);
    opacity: 0.85;
    max-width: 760px;
    /* §51: smooth collapse/expand. Reduced-motion override below kills
     * the transition so toggle is instant. */
    transition: max-height 360ms ease;
}

/* §51 — collapsible long bios. vendor-story.js measures the rendered
 * content height on mount; if it exceeds the COLLAPSE_THRESHOLD (240
 * px / ~10 lines), the section gets the `--collapsed` class and a
 * "Read more" toggle button is appended. CSS mask fades the bottom of
 * the content into transparency so the cutoff reads as "this continues"
 * rather than a hard clip — background-agnostic via `mask-image`, no
 * matching the page color. */
.hm-vendor-story--collapsed .hm-vendor-story__content {
    max-height: 240px;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to bottom, black 0%, black 65%, transparent 100%);
            mask-image: linear-gradient(to bottom, black 0%, black 65%, transparent 100%);
}

.hm-vendor-story--expanded .hm-vendor-story__content {
    /* High explicit ceiling for the transition to animate against —
     * `max-height: none` would make the transition no-op. Most vendor
     * bios fit comfortably under this; the novella case (~3k chars)
     * still fits at typical 1.65 line-height. */
    max-height: 4000px;
    overflow: visible;
    -webkit-mask-image: none;
            mask-image: none;
}

.hm-vendor-story__toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 14px;
    padding: 8px 18px;
    background: transparent;
    color: var(--hm-color-green-dark);
    font-family: var(--hm-font-display);
    font-weight: 600;
    font-size: 14px;
    line-height: 1.2;
    border: 1px solid currentColor;
    border-radius: 999px;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease, transform 80ms ease;
}

.hm-vendor-story__toggle:hover,
.hm-vendor-story__toggle:focus {
    background: var(--hm-color-green-dark);
    color: #fff;
    outline: none;
}

.hm-vendor-story__toggle:focus-visible {
    outline: 2px solid var(--hm-color-green-dark);
    outline-offset: 2px;
}

.hm-vendor-story__toggle:active {
    transform: scale(0.97);
}

.hm-vendor-story__toggle-chevron {
    display: inline-block;
    transition: transform 220ms ease;
    line-height: 1;
}

.hm-vendor-story--expanded .hm-vendor-story__toggle-chevron {
    transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
    .hm-vendor-story__content,
    .hm-vendor-story__toggle,
    .hm-vendor-story__toggle-chevron {
        transition: none;
    }
    .hm-vendor-story__toggle:active {
        transform: none;
    }
}

/* Defensive rules for vendor-authored HTML in description. Most
 * descriptions are <p>-wrapped paragraphs; a small fraction include
 * <br>, <b>, <strong>, <em>. One vendor on this install embeds <img>.
 * Zero use <a> (verified during 7q investigation; NOTES §36.1).
 * Cover the long tail with conservative resets. */
.hm-vendor-story__content p {
    margin: 0 0 14px 0;
}
.hm-vendor-story__content p:last-child {
    margin-bottom: 0;
}
.hm-vendor-story__content img {
    max-width: 100%;
    height: auto;
    border-radius: 12px;
    margin: 16px 0;
    display: block;
}
.hm-vendor-story__content ul,
.hm-vendor-story__content ol {
    margin: 12px 0;
    padding-left: 24px;
}
.hm-vendor-story__content a {
    color: var(--hm-brand-green, #21703b);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}
.hm-vendor-story__content a:hover,
.hm-vendor-story__content a:focus-visible {
    color: #2a8748;
}

/* --- Filter bar --- */

.hm-vendor__filters-bar {
    /* Reuses .hm-shop__filters-bar styles from §33 (position:relative
     * anchor + flex layout). This selector adds vendor-page-specific
     * spacing — sits below the story divider with breathing room before
     * the first aisle. */
    margin-top: 0;
    margin-bottom: 16px;
}

/* --- Aisles container --- */

.hm-vendor__aisles {
    /* §51: flex+gap stack matches .hm-shop__aisles (NOTES §20 — `.hm-shop__aisles`
     * carries the same rule). Earlier comment claimed "no additional rules
     * needed" but the rule that drives spacing lives on the PARENT, not on
     * `.hm-aisle` itself. Without flex+gap here, adjacent aisle sections
     * stacked with zero margin and the next aisle header clipped against
     * the previous carousel's bottom. Mobile shrinks 40→32 px to match
     * /shop/'s mobile media query (NOTES §20). */
    display: flex;
    flex-direction: column;
    gap: 40px;
}

@media (max-width: 576px) {
    .hm-vendor__aisles {
        gap: 32px;
    }
}

/* --- Empty state (vendor with no qualifying products) --- */

.hm-vendor__empty {
    padding: 80px 16px;
    text-align: center;
    color: #666;
    font-size: 15px;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.hm-vendor__empty p {
    margin: 0;
}

/* --- Breadcrumb "Vendors" non-link span (Phase 7q) ---
 *
 * Sibling to .hm-shop__breadcrumb-link, used for intermediate
 * breadcrumb items that don't have a destination yet (the vendor
 * index page is deferred to a future phase). Matches the visual
 * weight of a link but without underline / hover affordances. */
.hm-shop__breadcrumb-pending {
    color: #666;
    /* No cursor: pointer — this is decisively non-interactive. */
}

/* --- Mobile --- */

@media (max-width: 575px) {
    .hm-vendor-identity {
        flex-direction: column;
        align-items: center;
        text-align: center;
        gap: 16px;
        padding: 20px 0 24px 0;
    }
    .hm-vendor-identity__logo {
        width: 100px;
        height: 100px;
        border-radius: 12px;
    }
    .hm-vendor-identity__name {
        font-size: 26px;
    }
    .hm-vendor-identity__location {
        font-size: 14px;
    }
    /* Phase 7r polish — at mobile widths the identity strip stacks
     * vertically (logo → name/location → favorite button). Full
     * width tap target for the favorite button so thumbs hit it
     * comfortably; center the content. */
    .hm-vendor-favorite {
        width: 100%;
        max-width: 320px;
        justify-content: center;
        padding: 12px 18px;
    }
    .hm-vendor-story {
        padding-bottom: 24px;
        margin-bottom: 20px;
    }
    .hm-vendor-story__content {
        font-size: 15px;
        line-height: 1.6;
    }
}

/* === Shop lander Vendors mode "Browse All Vendors" CTA (Phase 7r polish) ===
 *
 * Appears at the end of the 20-vendor stack in /shop/'s Vendors
 * tab. Pulls users to the full directory at /shop/vendors/ rather
 * than scrolling endlessly through carousels. Generous vertical
 * spacing above so it reads as a section-end CTA, not an inline
 * link. Center-aligned, brand-green pill. */

.hm-shop__aisles-browse-all {
    display: flex;
    justify-content: center;
    padding: 40px 16px 24px 16px;
}

.hm-shop__aisles-browse-all-cta {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 14px 32px;
    background: var(--hm-brand-green, #21703b);
    color: #fff;
    border-radius: 999px;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-decoration: none;
    box-shadow: 0 4px 12px rgba(33, 112, 59, 0.18);
    transition: background 140ms ease, transform 140ms ease, box-shadow 180ms ease;
}

.hm-shop__aisles-browse-all-cta:hover,
.hm-shop__aisles-browse-all-cta:focus-visible {
    background: #2a8748;
    color: #fff;
    text-decoration: none;
    transform: translateY(-1px);
    box-shadow: 0 6px 18px rgba(33, 112, 59, 0.28);
    outline: none;
}

.hm-shop__aisles-browse-all-cta:active {
    transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
    .hm-shop__aisles-browse-all-cta {
        transition: none;
    }
    .hm-shop__aisles-browse-all-cta:hover,
    .hm-shop__aisles-browse-all-cta:active {
        transform: none;
    }
}

@media (max-width: 575px) {
    .hm-shop__aisles-browse-all {
        padding: 32px 16px 16px 16px;
    }
    .hm-shop__aisles-browse-all-cta {
        padding: 12px 24px;
        font-size: 14px;
    }
}

/* === §62 (Phase 7-1o) — slim top-of-vendors CTA + back-to-top utility ====
 *
 * The original Browse-All-Vendors CTA from §38 lived at the END of the
 * Vendors-mode 20-vendor stack. Phase 7-1o hoisted that CTA to the TOP
 * of the stack (entry-point affordance) and repurposed the bottom slot
 * as a back-to-top utility button. Two new visual treatments:
 *
 *   --top + --slim   modifier pair on the original .hm-shop__aisles-
 *                    browse-all-cta. Same brand-green pill, but smaller
 *                    padding + smaller font + lighter shadow so the
 *                    CTA doesn't compete with the carousels below for
 *                    visual weight. Reads as "tap to skip the curated
 *                    stack and see everything."
 *
 *   .hm-shop__aisles-back-to-top
 *   .hm-shop__aisles-back-to-top-btn
 *                    outlined pill in the secondary-action vocabulary
 *                    used elsewhere by .hm-vendor-story__toggle (§51).
 *                    Transparent bg + green-dark border/text + Montserrat
 *                    accent. Hover fills green with white text, same
 *                    treatment as the §51 toggle. Pure utility — clicking
 *                    smooth-scrolls the document to top via shop-aisles.js
 *                    (event delegation on [data-hm-shop-aisles]).
 * ========================================================================= */

.hm-shop__aisles-browse-all--top {
    /* Tighter vertical padding than the bottom-of-page placement —
     * the slim CTA sits between the mode picker and the first carousel
     * heading, so it reads as part of the chrome rather than as a
     * page-ending CTA. */
    padding: 12px 16px 20px 16px;
}

.hm-shop__aisles-browse-all-cta--slim {
    /* Slim variant: ~60% of the bottom CTA's padding, 13px text instead
     * of 15px, lighter shadow. Still a solid brand-green pill — the
     * link semantics are the same as the original, just visually
     * understated. */
    padding: 8px 22px;
    font-size: 13px;
    letter-spacing: 0.03em;
    box-shadow: 0 2px 6px rgba(33, 112, 59, 0.14);
}

.hm-shop__aisles-browse-all-cta--slim:hover,
.hm-shop__aisles-browse-all-cta--slim:focus-visible {
    box-shadow: 0 3px 10px rgba(33, 112, 59, 0.22);
}

.hm-shop__aisles-back-to-top {
    display: flex;
    justify-content: center;
    padding: 40px 16px 24px 16px;
}

.hm-shop__aisles-back-to-top-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 24px;
    background: transparent;
    color: var(--hm-color-green-dark);
    border: 1.5px solid var(--hm-color-green-dark);
    border-radius: 999px;
    font-family: var(--hm-font-accent);
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
    /* Reset CS-Cart .ty-btn gradients + shadows so the outline
     * treatment reads cleanly. */
    box-shadow: none;
    text-shadow: none;
    transition:
        background 140ms ease,
        color 140ms ease,
        transform 140ms ease,
        box-shadow 180ms ease;
}

.hm-shop__aisles-back-to-top-btn:hover,
.hm-shop__aisles-back-to-top-btn:focus-visible {
    background: var(--hm-color-green-dark);
    color: #fff;
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(33, 112, 59, 0.18);
    outline: none;
}

.hm-shop__aisles-back-to-top-btn:focus-visible {
    box-shadow: 0 0 0 3px rgba(33, 112, 59, 0.22);
}

.hm-shop__aisles-back-to-top-btn:active {
    transform: translateY(0);
}

/* Arrow glyph: line-height 1 so the ↑ doesn't pull the button taller
 * than the text. Sized slightly above the label for visual balance. */
.hm-shop__aisles-back-to-top-arrow {
    display: inline-block;
    font-size: 15px;
    line-height: 1;
    transform: translateY(-1px);
}

@media (prefers-reduced-motion: reduce) {
    .hm-shop__aisles-back-to-top-btn {
        transition: none;
    }
    .hm-shop__aisles-back-to-top-btn:hover,
    .hm-shop__aisles-back-to-top-btn:active {
        transform: none;
    }
}

@media (max-width: 575px) {
    .hm-shop__aisles-browse-all--top {
        padding: 8px 16px 14px 16px;
    }
    .hm-shop__aisles-browse-all-cta--slim {
        padding: 7px 18px;
        font-size: 12px;
    }
    .hm-shop__aisles-back-to-top {
        padding: 32px 16px 16px 16px;
    }
    .hm-shop__aisles-back-to-top-btn {
        padding: 9px 20px;
        font-size: 12px;
    }
}

/* === Vendor directory (Phase 7r) ==========================================
 *
 * /shop/vendors/ — editorial directory of all active vendors with
 * search + city-chip filtering. List style, one substantial row
 * per vendor (logo + name + location + count + snippet). The whole
 * row is an <a> link to the vendor's slug page.
 *
 * Filter axis decision (search + city) chose over A-Z because A-Z
 * would be lumpy on this catalog (5 dead letters, 6 with one
 * vendor each). City filter aligns with Harvestly's hyper-local
 * brand voice — SLO has 24 vendors, Paso Robles 8, etc. NOTES §37
 * documents the data + decision. */

.hm-vendor-directory {
    /* Phase 7-1h — cap bumped 1000 → 1200 to match the rest of the
     * shop surfaces (.hm-shop is 1280; .hm-vendor and .hm-product are
     * both 1200). Pre-7-1h the directory showed ~220px more whitespace
     * each side than its sibling pages at wide viewports, reading as
     * "the page can't decide how wide it wants to be" when navigating
     * /shop/ → /shop/vendors/ → /shop/vendors/<slug>/. Rows are
     * full-width-of-container so they grow naturally to fill the
     * extra space; no markup changes. */
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 var(--hm-page-padding, 16px) 64px var(--hm-page-padding, 16px);
}

/* --- Page chrome --- */

.hm-vendor-directory__header {
    padding: 24px 0 8px 0;
}

/* Page H1 — Poppins via --hm-font-headline (Phase 7-1m §60). Matches the
 * .hm-shop__heading + .hm-vendor-identity__name page-H1 family across
 * shop surfaces. Negative tracking replaces the prior +0.005em — Poppins
 * Bold benefits from slightly tightened tracking at display sizes, same
 * pattern as the Nunito hero/intro titles. */
.hm-vendor-directory__heading {
    margin: 0 0 12px 0;
    font-family: var(--hm-font-headline);
    font-size: clamp(28px, 3.5vw, 36px);
    font-weight: 700;
    line-height: 1.15;
    color: var(--hm-color-green-dark);
    letter-spacing: -0.01em;
}

.hm-vendor-directory__intro {
    margin: 0;
    max-width: 640px;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 16px;
    line-height: 1.55;
    color: #555;
}

/* --- Filter bar (natural flow) ---
 *
 * Originally sticky (§37) so search + chips stayed reachable as
 * the list scrolled past. Adam reverted to natural flow in 7-1k
 * follow-up — overlap with the stock site chrome at the top
 * (announcement banner + Harvestly site header + site-wide
 * search) was too messy; cleaner to let the filter live at its
 * natural position. Border-bottom + opaque background retained
 * to preserve the visual divider between filter and list. */

.hm-vendor-directory__filters {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 16px 0 16px 0;
    margin-bottom: 16px;
    background: #fff;
    border-bottom: 1px solid #eee;
}

.hm-vendor-directory__search-wrap {
    position: relative;
}

.hm-vendor-directory__search {
    width: 100%;
    padding: 11px 16px;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 15px;
    color: #333;
    background: #fff;
    border: 1px solid #ddd;
    border-radius: 10px;
    transition: border-color 120ms ease, box-shadow 120ms ease;
    box-shadow: none;
}

.hm-vendor-directory__search::placeholder {
    color: #999;
}

.hm-vendor-directory__search:focus {
    outline: none;
    border-color: var(--hm-brand-green, #21703b);
    box-shadow: 0 0 0 3px rgba(33, 112, 59, 0.12);
}

/* --- City chips (desktop) + collapsed dropdown (mobile) ---
 *
 * Desktop/tablet (>575px): the chip strip renders inline as a
 * flex-wrap row, one chip per city plus "All". The toggle button
 * is hidden via the media query at the bottom of this file.
 *
 * Mobile (≤575px): chips collapse behind a "Browse by city · X"
 * toggle button. Tapping the toggle drops a panel of chips that
 * dismisses on outside-click, Escape, or chip selection.
 *
 * The .--wrap container anchors the dropdown's absolute
 * positioning at mobile widths via its position: relative. */

.hm-vendor-directory__cities-wrap {
    position: relative;
}

.hm-vendor-directory__cities {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* Mobile toggle — hidden by default at desktop widths. The mobile
 * media query at the bottom of this file flips its display and
 * collapses the chip strip behind it. */
.hm-vendor-directory__cities-toggle {
    display: none;
}

.hm-vendor-directory__city {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    background: #f3f3f3;
    color: #555;
    border: 1px solid transparent;
    border-radius: 999px;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
    /* Reset CS-Cart .ty-btn defaults */
    box-shadow: none;
    text-shadow: none;
    letter-spacing: 0.01em;
}

.hm-vendor-directory__city:hover {
    background: #e8e8e8;
    color: #333;
}

.hm-vendor-directory__city--active {
    background: var(--hm-brand-green, #21703b);
    color: #fff;
    box-shadow: 0 1px 3px rgba(33, 112, 59, 0.25);
}

.hm-vendor-directory__city:focus-visible {
    outline: 2px solid var(--hm-brand-green, #21703b);
    outline-offset: 2px;
}

.hm-vendor-directory__city-count {
    display: inline-block;
    min-width: 18px;
    padding: 1px 5px;
    font-size: 11px;
    font-weight: 700;
    text-align: center;
    background: rgba(0, 0, 0, 0.06);
    color: inherit;
    border-radius: 999px;
    line-height: 1.4;
}

.hm-vendor-directory__city--active .hm-vendor-directory__city-count {
    background: rgba(255, 255, 255, 0.22);
}

/* --- List + rows --- */

.hm-vendor-directory__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 14px;

    /* scrollIntoView({block:'start'}) lands the user at the list's
     * top edge — but the sticky filter bar above (search + city
     * chips, ~114px tall, up to ~148px when chips wrap to 2 rows)
     * overlays that edge and cuts off the first row or two. 160px
     * leaves a small breathing buffer below the filter even in the
     * chip-wrap case. Mobile breakpoint overrides this to a tighter
     * value (filter bar is shorter when the city chips collapse
     * behind the dropdown toggle). Replaces the manual JS offset
     * pattern — scroll-margin-top is the native primitive for
     * "scroll-into-view with a gap." Used by both the Prev/Next
     * pagination handlers (Phase 7-1k) and the city-chip handler
     * from §37. */
    scroll-margin-top: 160px;
}

.hm-vendor-directory__row {
    background: #fff;
    border: 1px solid #eee;
    border-radius: 14px;
    overflow: hidden;
    transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease;
}

.hm-vendor-directory__row:hover {
    border-color: #d8e8df;
    box-shadow: 0 6px 20px rgba(33, 112, 59, 0.08);
    transform: translateY(-1px);
}

/* Hidden states — display:none keeps rows out of layout entirely
 * so hover targets and tab order match the visible list.
 *
 * Two-class model (post Phase 7-1k):
 *   --hidden:          filter doesn't match (search / city chip)
 *   --paginated-out:   filter matches but row is on a different page
 *
 * A row is visible iff neither class is applied. JS toggles each
 * class independently so a "no longer matches filter" row and a
 * "wrong page" row don't fight for the same state. */
.hm-vendor-directory__row--hidden,
.hm-vendor-directory__row--paginated-out {
    display: none;
}

.hm-vendor-directory__row-link {
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 18px;
    text-decoration: none;
    color: inherit;
}
.hm-vendor-directory__row-link:hover,
.hm-vendor-directory__row-link:focus {
    text-decoration: none;
    color: inherit;
}

/* Logo or initials placeholder */

.hm-vendor-directory__row-logo {
    flex-shrink: 0;
    width: 100px;
    height: 100px;
    border-radius: 12px;
    overflow: hidden;
    background: #f5f5f5;
    display: flex;
    align-items: center;
    justify-content: center;
}

.hm-vendor-directory__row-logo img {
    width: 100%;
    height: 100%;
    /* object-fit: contain matches the slug page's logo treatment
     * (NOTES §36.x). Vendor logos are mostly landscape brand marks;
     * cropping them destroys recognition. */
    object-fit: contain;
}

.hm-vendor-directory__row-logo-placeholder {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #eef3ee, #d8e8df);
    color: var(--hm-brand-green, #21703b);
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 36px;
    font-weight: 700;
    line-height: 1;
}

/* Row content */

.hm-vendor-directory__row-content {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.hm-vendor-directory__row-name {
    margin: 0;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 19px;
    font-weight: 700;
    line-height: 1.25;
    color: #222;
    /* Single-line truncation: long vendor names don't wrap the
     * row's vertical rhythm. Browsers without text-overflow on
     * flex children get the natural wrap behavior. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.hm-vendor-directory__row-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    align-items: center;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 13px;
    color: #777;
    font-weight: 500;
}

.hm-vendor-directory__row-location {
    display: inline-flex;
    align-items: center;
}

.hm-vendor-directory__row-count {
    display: inline-flex;
    align-items: center;
    color: var(--hm-brand-green, #21703b);
}

.hm-vendor-directory__row-count::before {
    content: "·";
    margin-right: 14px;
    color: #ccc;
    /* The neutral dot separator. Hidden if location is missing
     * (no preceding element to separate from) — the meta flex
     * gap handles spacing in that case. */
}

/* When the location element is absent, drop the leading separator
 * on the count. :first-child works because the meta children are
 * conditionally rendered server-side. */
.hm-vendor-directory__row-count:first-child::before {
    content: "";
    margin-right: 0;
}

.hm-vendor-directory__row-snippet {
    margin: 4px 0 0 0;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 14px;
    line-height: 1.5;
    color: #555;
    /* 2-line clamp keeps row heights uniform. Snippet was
     * truncated server-side to 150 chars; clamp guards against
     * narrow viewports where 150 chars wraps to 3+ lines. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* --- Empty states --- */

.hm-vendor-directory__empty,
.hm-vendor-directory__empty-results {
    padding: 64px 16px;
    text-align: center;
    color: #666;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 15px;
}

.hm-vendor-directory__empty p,
.hm-vendor-directory__empty-results p {
    margin: 0;
}

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

@media (prefers-reduced-motion: reduce) {
    .hm-vendor-directory__row,
    .hm-vendor-directory__city,
    .hm-vendor-directory__search {
        transition: none;
    }
    .hm-vendor-directory__row:hover {
        transform: none;
    }
}

/* --- Mobile --- */

@media (max-width: 575px) {
    .hm-vendor-directory__heading {
        font-size: 24px;
    }
    .hm-vendor-directory__intro {
        font-size: 14px;
    }
    /* Mobile filter bar is shorter (city chips collapse behind the
     * dropdown toggle), so the scroll buffer can be tighter. */
    .hm-vendor-directory__list {
        scroll-margin-top: 110px;
    }
    /* Mobile dropdown UX: toggle visible, chip panel collapsed by
     * default. The horizontal-scroll chip strip that lived here
     * previously was awful to use — Adam's feedback flagged it.
     * Replaced with a tap-to-open dropdown menu. */
    .hm-vendor-directory__cities-toggle {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        width: 100%;
        padding: 10px 14px;
        background: #f3f3f3;
        color: #333;
        border: 1px solid #e3e3e3;
        border-radius: 10px;
        font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
        font-size: 14px;
        font-weight: 600;
        text-align: left;
        cursor: pointer;
        /* Reset CS-Cart .ty-btn defaults. */
        box-shadow: none;
        text-shadow: none;
        letter-spacing: 0.01em;
        transition: background 120ms ease, border-color 120ms ease;
    }
    .hm-vendor-directory__cities-toggle:hover,
    .hm-vendor-directory__cities-toggle:focus-visible {
        background: #e8e8e8;
        border-color: #d8d8d8;
    }
    .hm-vendor-directory__cities-toggle:focus-visible {
        outline: 2px solid var(--hm-brand-green, #21703b);
        outline-offset: 2px;
    }

    .hm-vendor-directory__cities-toggle-label {
        color: #777;
        font-weight: 500;
    }
    .hm-vendor-directory__cities-toggle-sep {
        color: #ccc;
    }
    .hm-vendor-directory__cities-toggle-current {
        flex: 1;
        color: var(--hm-brand-green, #21703b);
        /* Single-line truncation in case the active city is long. */
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .hm-vendor-directory__cities-toggle-chevron {
        flex-shrink: 0;
        color: #777;
        font-size: 12px;
        transition: transform 180ms ease;
    }
    .hm-vendor-directory__cities-wrap--open
        .hm-vendor-directory__cities-toggle-chevron {
        transform: rotate(180deg);
    }
    .hm-vendor-directory__cities-wrap--open
        .hm-vendor-directory__cities-toggle {
        background: #fff;
        border-color: var(--hm-brand-green, #21703b);
        box-shadow: 0 1px 0 var(--hm-brand-green, #21703b);
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
    }

    /* Chip panel — collapsed by default at mobile widths, expanded
     * when .--open is set on the wrap. Absolutely positioned below
     * the toggle so the panel "drops down" from the toggle's
     * bottom edge.
     *
     * z-index just above the sticky filter bar's stacking context
     * but below the drawer overlay (1000+). */
    .hm-vendor-directory__cities {
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        display: none;
        flex-direction: column;
        gap: 0;
        margin-top: 0;
        padding: 8px;
        background: #fff;
        border: 1px solid var(--hm-brand-green, #21703b);
        border-top: none;
        border-radius: 0 0 10px 10px;
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
        z-index: 11;
        max-height: 60vh;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }
    .hm-vendor-directory__cities-wrap--open
        .hm-vendor-directory__cities {
        display: flex;
    }

    /* Chips inside the dropdown render as full-width tappable rows
     * rather than the desktop pill shape. Bigger tap targets, count
     * pushed to the right via flex justify, active chip gets the
     * brand-green background. */
    .hm-vendor-directory__city {
        display: flex;
        align-items: center;
        justify-content: space-between;
        width: 100%;
        padding: 11px 12px;
        border-radius: 8px;
        font-size: 14px;
        text-align: left;
        background: transparent;
        color: #333;
        /* Override the desktop chip's inline-flex + tighter padding. */
        gap: 12px;
    }
    .hm-vendor-directory__city:hover {
        background: #f3f3f3;
    }
    .hm-vendor-directory__city--active {
        background: var(--hm-brand-green, #21703b);
        color: #fff;
    }
    .hm-vendor-directory__city--active:hover {
        background: var(--hm-brand-green, #21703b);
    }
    .hm-vendor-directory__city-count {
        margin-left: auto;
        min-width: 24px;
        text-align: center;
    }
    .hm-vendor-directory__row-link {
        gap: 14px;
        padding: 14px;
    }
    .hm-vendor-directory__row-logo {
        width: 72px;
        height: 72px;
    }
    .hm-vendor-directory__row-name {
        font-size: 16px;
    }
    .hm-vendor-directory__row-meta {
        font-size: 12px;
        gap: 2px 10px;
    }
    .hm-vendor-directory__row-count::before {
        margin-right: 10px;
    }
    .hm-vendor-directory__row-snippet {
        font-size: 13px;
        -webkit-line-clamp: 3;
    }
}

/* === Shop filters (Phase 7n) ==============================================
 *
 * Anchored dropdown for product feature filtering on grid-mode shop
 * pages (subcategory + grid-fallback). Single-select UX across all
 * variants: radios share one name so picking one deselects the rest.
 *
 * Layering: the bar sits in normal flow under the page heading;
 * position:relative on the bar anchors the dropdown's position:absolute
 * directly below the trigger. The dropdown's z-index sits above grid
 * cards (z=2 for tags) and the qty badge but below the drawer overlay
 * (z=1000+). */

.hm-shop__filters-bar {
    position: relative;          /* anchor for .hm-shop-filters__dropdown */
    display: flex;
    justify-content: flex-end;
    align-items: center;
    max-width: 1200px;
    margin: 8px auto 16px auto;
    padding: 0 var(--hm-page-padding, 16px);
}

/* === Trigger button === */

/* Filter trigger — Montserrat accent via --hm-font-accent (Phase 7-1m
 * §60). Border tinted from stock-CS-Cart-feeling `#ddd` gray to the
 * leafy `rgba(45,62,16,0.12)` brand-restrained outline (one step
 * deeper than the card border `rgba(45,62,16,0.08)` so the trigger
 * reads as more clickable). Color shifted to green-darkest. Hover
 * lifts +1px and shifts background to cream tint instead of #f5f5f5
 * gray. Active (`aria-expanded="true"`) state moves to `--hm-color-
 * green-fresh` (matching the footer menu trigger pattern in §45.12f)
 * rather than the previous green-dark — the lime accent feels "live"
 * during interaction. */
.hm-shop-filters__trigger {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px 8px 16px;
    background: #fff;
    color: var(--hm-color-green-darkest);
    font-family: var(--hm-font-accent);
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border: 1px solid rgba(45, 62, 16, 0.12);
    border-radius: 999px;
    cursor: pointer;
    transition:
        background 120ms ease,
        border-color 120ms ease,
        color 120ms ease,
        transform 120ms ease;
}
.hm-shop-filters__trigger:hover {
    background: rgba(241, 235, 191, 0.4);
    border-color: rgba(45, 62, 16, 0.22);
    transform: translateY(-1px);
}
.hm-shop-filters__trigger:focus-visible {
    outline: 2px solid var(--hm-color-green-dark);
    outline-offset: 2px;
}
.hm-shop-filters__trigger[aria-expanded="true"] {
    background: var(--hm-color-green-fresh);
    color: #fff;
    border-color: var(--hm-color-green-fresh);
    transform: translateY(0);
}

/* Hidden by default; appears as a small orange dot when filters are
 * applied (data-has-filters="true"). Position-flexible — sits inline
 * between the label and the chevron. */
.hm-shop-filters__indicator {
    display: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #d96e2c;
    flex-shrink: 0;
}
.hm-shop-filters__trigger[data-has-filters="true"] .hm-shop-filters__indicator {
    display: inline-block;
}

.hm-shop-filters__chevron {
    font-size: 10px;
    line-height: 1;
    transition: transform 200ms ease;
}
.hm-shop-filters__trigger[aria-expanded="true"] .hm-shop-filters__chevron {
    transform: rotate(180deg);
}

/* === Dropdown panel === */

.hm-shop-filters__dropdown {
    position: absolute;
    top: 100%;
    right: var(--hm-page-padding, 16px);
    width: 320px;
    max-width: calc(100vw - 32px);
    max-height: 60vh;
    margin-top: 6px;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 160ms ease, transform 160ms ease, visibility 0s linear 160ms;
    z-index: 100;
    display: flex;
    flex-direction: column;
    overflow: hidden;            /* clip inner scroll */
}
.hm-shop-filters__dropdown[aria-hidden="false"] {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition: opacity 160ms ease, transform 160ms ease, visibility 0s linear 0s;
}

/* === Loading state === */

.hm-shop-filters__loading {
    padding: 24px 16px;
    text-align: center;
    color: #999;
    font-size: 14px;
}

/* === Sections — scrollable middle region between header and actions === */

.hm-shop-filters__sections {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 16px;
}

.hm-shop-filters__section {
    border: none;
    margin: 0 0 16px 0;
    padding: 0;
}
.hm-shop-filters__section:last-child {
    margin-bottom: 0;
}

/* Filter section heading — Montserrat accent via --hm-font-accent
 * (Phase 7-1m §60). Tracking bumped 0.04em → 0.05em for parity with
 * the rest of the addon's UPPERCASE accent treatments. Color shifted
 * to brand green-darkest. */
.hm-shop-filters__section-heading {
    font-family: var(--hm-font-accent);
    font-size: 12px;
    font-weight: 700;
    color: var(--hm-color-green-darkest);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin: 0 0 10px 0;
    padding: 0;
}

.hm-shop-filters__values {
    list-style: none;
    margin: 0;
    padding: 0;
}

.hm-shop-filters__value {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 0;
    cursor: pointer;
    font-size: 14px;
    color: #444;
    /* 44px touch target satisfied via padding + line height when
     * combined with the input's intrinsic size. */
}
.hm-shop-filters__value:hover {
    color: #111;
}

.hm-shop-filters__value input[type="radio"] {
    margin: 0;
    flex-shrink: 0;
    cursor: pointer;
    accent-color: var(--hm-brand-green, #21703b);
}

.hm-shop-filters__value-text {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.hm-shop-filters__value-count {
    color: #999;
    font-size: 12px;
    flex-shrink: 0;
}

.hm-shop-filters__empty-taxonomy {
    padding: 16px 0;
    text-align: center;
    color: #999;
    font-size: 13px;
}

/* === Actions bar === */

.hm-shop-filters__actions {
    display: flex;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid #eee;
    background: #fafafa;
}

/* Filter actions — Nunito display via --hm-font-display (Phase 7-1m
 * §60). Apply button matches the homepage `.hm-product__ask-submit`
 * and pagination button pattern: Nunito 700, green-dark base with
 * green-fresh hover. Clear button stays neutral but uses the same
 * type system. */
.hm-shop-filters__clear,
.hm-shop-filters__apply {
    flex: 1;
    padding: 10px 14px;
    font-family: var(--hm-font-display);
    font-size: 14px;
    font-weight: 700;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
}
.hm-shop-filters__clear {
    background: var(--hm-color-linen);
    color: var(--hm-color-green-darkest);
}
.hm-shop-filters__clear:hover {
    background: rgba(45, 62, 16, 0.12);
}
.hm-shop-filters__apply {
    background: var(--hm-color-green-dark);
    color: #fff;
}
.hm-shop-filters__apply:hover {
    background: var(--hm-color-green-fresh);
}
.hm-shop-filters__clear:focus-visible,
.hm-shop-filters__apply:focus-visible {
    outline: 2px solid var(--hm-brand-green, #21703b);
    outline-offset: 2px;
}

/* === Empty-state inside the grid (filtered → 0 results) === */

.hm-aisle-carousel__empty--filtered {
    grid-column: 1 / -1;          /* span the whole grid in grid mode */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    padding: 64px 16px;
    text-align: center;
    color: #666;
    font-size: 15px;
}

.hm-aisle-carousel__empty-msg {
    margin: 0;
}

.hm-aisle-carousel__empty-clear {
    padding: 10px 24px;
    background: var(--hm-brand-green, #21703b);
    color: #fff;
    font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 14px;
    font-weight: 600;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: background 120ms ease;
}
.hm-aisle-carousel__empty-clear:hover {
    background: #2a8748;
}
.hm-aisle-carousel__empty-clear:focus-visible {
    outline: 2px solid #fff;
    outline-offset: -3px;
    box-shadow: 0 0 0 5px var(--hm-brand-green, #21703b);
}

/* === Reduced motion === */

@media (prefers-reduced-motion: reduce) {
    .hm-shop-filters__trigger,
    .hm-shop-filters__dropdown,
    .hm-shop-filters__chevron,
    .hm-shop-filters__clear,
    .hm-shop-filters__apply,
    .hm-aisle-carousel__empty-clear {
        transition: none;
    }
}

/* === Mobile === */

@media (max-width: 575px) {
    .hm-shop-filters__dropdown {
        /* §64 (7-1q): same viewport-overflow fix as .hm-help__dropdown.
         * The `.hm-shop-filters` container's right padding edge sits a
         * constant ~32px from the viewport right edge; the prior
         * `right: 8px; width: calc(100vw - 24px)` overshot and the panel's
         * LEFT edge landed at ~-8px, clipping "FILTER" → "ILTER" (measured
         * at 500px). Anchor for symmetric 12px gutters:
         *   right edge = container_right + 20 = (100vw - 32) + 20 = 100vw - 12
         *   width      = 100vw - 24  →  left edge = 12 */
        right: -20px;
        width: calc(100vw - 24px);
        max-height: 70vh;
    }
}

/* ============================================================================
 * === Zone-checker topographic backdrop (Phase 7p2) ==========================
 * ============================================================================
 *
 * The zone-checker block now renders one of two visual modes depending
 * on auth state (see blocks/zone_checker.tpl):
 *
 *   .hm-zone-topo.hm-zone-topo--full
 *     Logged-out. The full zone-checker form sits on top of the topo
 *     SVG backdrop. Height grows to the form's content.
 *
 *   .hm-zone-topo.hm-zone-topo--strip
 *     Logged-in. No form — just a slim ~72px strip carrying the topo
 *     vector. Serves as a horizontal page break between everything
 *     above-the-fold and the carousels below.
 *
 * Layer model:
 *   .hm-zone-topo (relative, gradient backdrop, animated drift)
 *     ├── .hm-zone-topo__svg  (absolute <object>, slow zoom inside SVG)
 *     └── .hm-zone-topo__content  (the actual zone-checker mount, z:1)
 *
 * The SVG's own internal CSS handles per-line brand greens and the
 * slow zoom-in/zoom-out animation on the contour group. This rule
 * set handles only the WRAPPER: gradient plate, sizing per variant,
 * and stacking.
 * ========================================================================= */

.hm-zone-topo {
    position: relative;
    /* z-index lifts the whole wrapper (including the autocomplete
     * dropdown that spills below) above sibling page blocks. The
     * carousel-area children top out at z-index 3 within their own
     * stacking contexts; 5 here clears them with margin. */
    z-index: 5;
    width: auto;
    max-width: 1200px;
    margin: 20px auto;
    /* overflow stays VISIBLE on the wrapper — the zone-checker's
     * autocomplete dropdown is position:absolute below the input and
     * needs to spill past the wrapper's bottom edge. The SVG layer
     * underneath has its own overflow:hidden so corners still clip
     * cleanly to the border-radius. */
    border-radius: 14px;
    background:
        radial-gradient(ellipse at 30% 20%, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 55%),
        linear-gradient(135deg, #f7f1de 0%, #e9f1e2 40%, #d2e6c4 75%, #b9d8a8 100%);
    background-size: 200% 200%, 220% 220%;
    background-position: 0% 0%, 0% 0%;
    animation: hm-zone-topo-bg-drift 28s ease-in-out infinite;
    box-shadow:
        inset 0 0 24px rgba(33, 112, 59, 0.06),
        0 2px 12px rgba(0, 0, 0, 0.06);
}

/* SVG-only clipping layer — sits behind content, clips the topo
 * paths to the wrapper's rounded corners. Separate from
 * .hm-zone-topo so the wrapper can let descendant overlays
 * (autocomplete dropdown, future popovers) escape. */
.hm-zone-topo__bg {
    position: absolute;
    inset: 0;
    overflow: hidden;
    border-radius: inherit;
    z-index: 0;
    pointer-events: none;
}

/* Full variant: block layout (not flex) so the inner .hm-zone card
 * can use its existing `max-width + margin: 0 auto` to size + center
 * horizontally. Flex centering was collapsing the card to its
 * content-width (a flex item is content-sized by default) instead
 * of letting it grow to its max-width. Wrapper padding gives
 * symmetric breathing room above/below the card; the card's auto
 * margins handle horizontal centering. */
.hm-zone-topo--full {
    padding: clamp(20px, 2.5vw, 32px) clamp(16px, 2vw, 24px);
}

/* Slim strip variant: page-break separator. Height is small so the
 * topo design reads as ambient texture, not a focal element. */
.hm-zone-topo--strip {
    height: clamp(56px, 8vw, 84px);
}

.hm-zone-topo__svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    border: 0;
    display: block;
    z-index: 0;
}

/* The zone-checker JS island mounts inside this container — lift it
 * above the topo backdrop so the form is interactive and the topo
 * stays as ambient background. */
.hm-zone-topo__content {
    position: relative;
    z-index: 1;
}

/* Slow drift of the gradient plate (independent of the SVG's own
 * zoom). Counter-phase between the radial highlight and the linear
 * gradient so the plate breathes diagonally. */
@keyframes hm-zone-topo-bg-drift {
    0%, 100% {
        background-position: 0% 0%, 100% 100%;
    }
    50% {
        background-position: 100% 100%, 0% 0%;
    }
}

@media (prefers-reduced-motion: reduce) {
    .hm-zone-topo {
        animation: none;
    }
}

@media (max-width: 575px) {
    .hm-zone-topo {
        border-radius: 10px;
        margin-top: 14px;
        margin-bottom: 14px;
        /* Pull the topo in from the viewport edges on small screens so
         * it visually aligns with the page-padding used by other blocks. */
        margin-left: var(--hm-page-padding, 16px);
        margin-right: var(--hm-page-padding, 16px);
    }
    .hm-zone-topo--strip {
        height: clamp(44px, 12vw, 64px);
    }
}

/* === Zone checker map (Phase 7u) ========================================= */

/* When show_map='Y' the topo wrapper switches to side-by-side layout:
 * form on the left at a fixed comfortable width, map fills the rest.
 * Stacks vertically below the mobile breakpoint.
 *
 * The topo backdrop (.hm-zone-topo__bg with the SVG <object>) keeps its
 * absolute-inset positioning under both form and map — the gradient
 * texture reads continuously across the whole wrapper, including the
 * map area's outer chrome (the map canvas itself has its own opaque
 * background and obscures the topo underneath, which is the right
 * visual). */

.hm-zone-topo--with-map {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

@media (min-width: 768px) {
    .hm-zone-topo--with-map {
        flex-direction: row;
        align-items: stretch;
    }

    .hm-zone-topo--with-map .hm-zone-topo__content {
        flex: 0 0 360px;
        max-width: 360px;
    }
}

/* The map area sibling. position:relative so the canvas can use the
 * full container width; z-index:1 lifts it above the topo backdrop
 * layer same as .hm-zone-topo__content. min-width:0 lets it shrink
 * inside the flex row if needed. */
.hm-zone-map {
    position: relative;
    z-index: 1;
    flex: 1;
    min-width: 0;
}

.hm-zone-map__canvas {
    width: 100%;
    height: 320px;
    background: #eef3ee;
    border-radius: 12px;
    overflow: hidden;
    /* Box-shadow gives the map a subtle floating-card feel against
     * the topo backdrop — same visual weight as the form's input
     * group. */
    box-shadow: 0 2px 8px rgba(33, 112, 59, 0.08);
}

@media (max-width: 767px) {
    .hm-zone-map__canvas {
        /* Shorter on mobile so the form remains the primary action
         * above the fold; map is secondary at this width. */
        height: 240px;
    }
}

.hm-zone-map__fallback {
    padding: 16px;
    text-align: center;
    color: #777;
    font-size: 14px;
}

/* === Product page (Phase 7s) ============================================== */

.hm-product {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 var(--hm-page-padding, 16px);
}

/* Breadcrumb reuses .hm-shop__breadcrumb* — no new rules needed. */

/* === Above-the-fold layout === */

.hm-product__above-fold {
    display: grid;
    grid-template-columns: 1fr;
    gap: 24px;
    padding: 16px 0 32px 0;
}

@media (min-width: 768px) {
    .hm-product__above-fold {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: 40px;
        padding: 24px 0 48px 0;
        align-items: start;
    }
}

/* === Gallery === */

.hm-product__gallery {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

.hm-product__hero-image {
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: 16px;
    overflow: hidden;
    background: #f5f5f5;
    position: relative;
}

.hm-product__hero-image--empty {
    background: linear-gradient(135deg, #eef3ee 0%, #d8e5dc 100%);
}

.hm-product__hero-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.hm-product__thumbnails {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 4px;
    scrollbar-width: thin;
}

.hm-product__thumbnail {
    flex-shrink: 0;
    width: 72px;
    height: 72px;
    border-radius: 10px;
    overflow: hidden;
    background: #f5f5f5;
    border: 2px solid transparent;
    cursor: pointer;
    padding: 0;
    transition: border-color 120ms ease, transform 120ms ease;
}

.hm-product__thumbnail:hover {
    transform: translateY(-1px);
}

.hm-product__thumbnail--active {
    border-color: #21703b;
}

.hm-product__thumbnail-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* === Info column === */

.hm-product__info {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

/* Product name — Nunito via --hm-font-display (Phase 7-1m §60). Replaces
 * the prior `var(--hm-display-font, "Montserrat", ...)` which referenced
 * a token that was never declared (the literal Montserrat fallback ran
 * always). The product page is the leaf of the shopping funnel — the
 * Nunito vocabulary brings the homepage hero/intro voice into the
 * product page, while the page-defining H1 role on shop frame pages
 * stays on Poppins (--hm-font-headline). Color shifted to brand
 * green-darkest. */
.hm-product__name {
    margin: 0;
    font-family: var(--hm-font-display);
    font-size: 26px;
    font-weight: 700;
    color: var(--hm-color-green-darkest);
    line-height: 1.2;
    letter-spacing: -0.015em;
}

@media (min-width: 768px) {
    .hm-product__name {
        font-size: 32px;
    }
}

@media (max-width: 480px) {
    .hm-product__name {
        font-size: 22px;
    }
}

.hm-product__vendor {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    text-decoration: none;
    color: inherit;
    padding: 6px 14px 6px 6px;
    background: #f7f8f6;
    border-radius: 999px;
    align-self: flex-start;
    transition: background 120ms ease;
}

.hm-product__vendor:hover {
    background: #eaeee7;
}

.hm-product__vendor-logo {
    display: inline-flex;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    overflow: hidden;
    background: #fff;
    flex-shrink: 0;
}

.hm-product__vendor-logo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.hm-product__vendor-text {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
}

.hm-product__vendor-eyebrow {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #888;
}

.hm-product__vendor-name {
    font-size: 14px;
    font-weight: 600;
    color: #21703b;
}

.hm-product__price {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

.hm-product__price-current {
    font-size: 26px;
    font-weight: 700;
    color: #21703b;
    line-height: 1;
}

.hm-product__price-list {
    font-size: 16px;
    color: #999;
    text-decoration: line-through;
    line-height: 1;
}

.hm-product__price-current .ty-price,
.hm-product__price-current .ty-price-num {
    font-size: inherit;
    color: inherit;
    font-weight: inherit;
}

.hm-product__price-list .ty-price,
.hm-product__price-list .ty-price-num {
    font-size: inherit;
    color: inherit;
    text-decoration: line-through;
}

.hm-product__rating {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    color: inherit;
    font-size: 14px;
    align-self: flex-start;
}

.hm-product__rating:hover .hm-product__rating-text {
    text-decoration: underline;
}

.hm-product__rating-stars {
    color: #f0a020;
    font-size: 18px;
    letter-spacing: 1px;
    line-height: 1;
}

.hm-product__rating-text {
    color: #555;
    font-weight: 500;
}

.hm-product__rating-sep {
    color: #ccc;
    margin: 0 2px;
}

/* === Variant picker === */

.hm-product__variants {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 16px 0;
    border-top: 1px solid #eee;
}

.hm-product__variant-axis {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.hm-product__variant-label {
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #777;
    font-weight: 600;
}

.hm-product__variant-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.hm-product__variant-chip {
    display: inline-flex;
    align-items: center;
    padding: 7px 14px;
    border: 1.5px solid #d8d8d8;
    border-radius: 999px;
    background: #fff;
    color: #333;
    font-size: 14px;
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease, color 120ms ease;
    line-height: 1.2;
}

.hm-product__variant-chip:hover {
    border-color: #21703b;
    color: #21703b;
}

.hm-product__variant-chip--active {
    background: #21703b;
    color: #fff;
    border-color: #21703b;
    font-weight: 600;
}

.hm-product__variant-chip--active:hover {
    color: #fff;
    background: #2a8748;
    border-color: #2a8748;
}

.hm-product__variant-chip--disabled {
    color: #b0b0b0;
    border-color: #e8e8e8;
    background: #fafafa;
    cursor: not-allowed;
    text-decoration: line-through;
}

.hm-product__variant-chip--disabled:hover {
    border-color: #e8e8e8;
    color: #b0b0b0;
}

/* === Add-to-cart row === */

.hm-product__cart-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 8px;
    flex-wrap: wrap;
}

.hm-product__qty {
    display: inline-flex;
    align-items: stretch;
    border: 1.5px solid #d8d8d8;
    border-radius: 12px;
    overflow: hidden;
    background: #fff;
}

.hm-product__qty-btn {
    width: 40px;
    height: 48px;
    background: #fff;
    border: none;
    font-size: 20px;
    color: #333;
    cursor: pointer;
    line-height: 1;
    transition: background 120ms ease;
    padding: 0;
}

.hm-product__qty-btn:hover:not(:disabled) {
    background: #f0f3ee;
}

.hm-product__qty-btn:disabled {
    color: #ccc;
    cursor: not-allowed;
}

.hm-product__qty-value {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 48px;
    height: 48px;
    padding: 0 12px;
    border-left: 1.5px solid #d8d8d8;
    border-right: 1.5px solid #d8d8d8;
    text-align: center;
    font-size: 16px;
    font-weight: 600;
    color: #222;
    background: #fff;
    line-height: 1;
    user-select: none;
    font-variant-numeric: tabular-nums;
}

.hm-product__add-to-cart {
    flex: 1 1 200px;
    min-width: 0;
    height: 48px;
    padding: 0 24px;
    background: #21703b;
    color: #fff;
    font-size: 16px;
    font-weight: 700;
    border: none;
    border-radius: 12px;
    cursor: pointer;
    transition: background 120ms ease, transform 80ms ease;
}

.hm-product__add-to-cart:hover {
    background: #2a8748;
}

.hm-product__add-to-cart:active {
    transform: translateY(1px);
}

.hm-product__add-to-cart--busy {
    opacity: 0.7;
    cursor: progress;
}

.hm-product__qty-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 32px;
    height: 32px;
    padding: 0 10px;
    border-radius: 999px;
    background: #21703b;
    color: #fff;
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
}

.hm-product__qty-badge:empty {
    display: none;
}

/* === Section headings === */

/* Section headings within the product page — Nunito via --hm-font-display
 * (Phase 7-1m §60). Same token-typo fix as .hm-product__name. Reads as
 * "section within the product page" relative to the product name's
 * page-voice role. */
.hm-product__section-heading {
    margin: 32px 0 16px 0;
    font-family: var(--hm-font-display);
    font-size: 20px;
    font-weight: 700;
    color: var(--hm-color-green-darkest);
    letter-spacing: -0.01em;
}

/* === Description === */

.hm-product__description {
    padding: 16px 0 8px 0;
    border-top: 1px solid #eee;
}

.hm-product__description-content {
    font-size: 15px;
    line-height: 1.6;
    color: #444;
    max-width: 800px;
}

.hm-product__description-content > *:first-child {
    margin-top: 0;
}

.hm-product__description-content img {
    max-width: 100%;
    height: auto;
    border-radius: 12px;
    margin: 16px 0;
    display: block;
}

.hm-product__description-content p {
    margin: 0 0 12px 0;
}

.hm-product__description-content a {
    color: #21703b;
    text-decoration: underline;
}

.hm-product__description-content ul,
.hm-product__description-content ol {
    margin: 12px 0;
    padding-left: 24px;
}

.hm-product__description-content li {
    margin-bottom: 4px;
}

/* === Reviews === */

.hm-product__reviews {
    padding: 16px 0 24px 0;
    border-top: 1px solid #eee;
    scroll-margin-top: 80px;
}

.hm-product__reviews-summary {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 16px;
    padding: 12px 16px;
    background: #fafaf6;
    border-radius: 12px;
}

.hm-product__reviews-rating-stars {
    color: #f0a020;
    font-size: 18px;
    letter-spacing: 1px;
    line-height: 1;
}

.hm-product__reviews-rating-value {
    font-size: 20px;
    font-weight: 700;
    color: #222;
    line-height: 1;
}

.hm-product__reviews-rating-count {
    font-size: 14px;
    color: #777;
}

.hm-product__reviews-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.hm-product__review {
    padding: 14px 16px;
    background: #fafafa;
    border-radius: 12px;
    border: 1px solid #eee;
}

.hm-product__review-header {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 8px;
    font-size: 13px;
}

.hm-product__review-author {
    font-weight: 600;
    color: #333;
}

.hm-product__review-buyer-badge {
    font-size: 11px;
    color: #21703b;
    background: #e6f4eb;
    padding: 2px 8px;
    border-radius: 999px;
    font-weight: 500;
}

.hm-product__review-rating {
    color: #f0a020;
    letter-spacing: 1px;
}

.hm-product__review-date {
    color: #999;
    margin-left: auto;
    font-size: 12px;
}

.hm-product__review-text {
    margin: 0 0 6px 0;
    font-size: 14px;
    line-height: 1.5;
    color: #444;
}

.hm-product__review-pros,
.hm-product__review-cons {
    margin: 4px 0;
    font-size: 13px;
    color: #555;
}

.hm-product__review-pros strong {
    color: #21703b;
}

.hm-product__review-cons strong {
    color: #b3592a;
}

.hm-product__review-reply {
    margin-top: 10px;
    padding: 10px 12px;
    background: #fff;
    border-left: 3px solid #21703b;
    border-radius: 6px;
    font-size: 13px;
    color: #444;
}

.hm-product__show-all-reviews {
    margin-top: 14px;
    padding: 8px 0;
    background: none;
    border: none;
    color: #21703b;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    align-self: flex-start;
}

.hm-product__show-all-reviews:hover {
    text-decoration: underline;
}

/* §63 (Phase 7-1p): was an <a>, now a <button> that toggles the
 * inline write-review panel. Button-reset properties added so the
 * visual stays text-link-like (no chrome borders/bg/padding). */
.hm-product__write-review-link {
    display: inline-block;
    margin-top: 8px;
    margin-left: 16px;
    padding: 0;
    background: none;
    border: 0;
    color: #21703b;
    text-decoration: none;
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    box-shadow: none;
    text-shadow: none;
}

.hm-product__write-review-link:hover {
    text-decoration: underline;
}

.hm-product__write-review-link:focus-visible {
    outline: 2px solid var(--hm-color-green-dark);
    outline-offset: 3px;
    border-radius: 2px;
}

.hm-product__reviews-empty {
    padding: 20px 24px;
    background: #fafafa;
    border-radius: 12px;
    text-align: center;
    color: #666;
}

.hm-product__reviews-empty-msg {
    margin: 0 0 12px 0;
    font-size: 14px;
}

/* §63 (Phase 7-1p): was an <a>, now a <button> that toggles the
 * inline write-review panel. Visual stays identical (brand-green
 * pill); font-family: inherit ensures the page's Open Sans wins
 * over the browser's default button system-font. */
.hm-product__write-review-cta {
    display: inline-block;
    padding: 10px 22px;
    background: #21703b;
    color: #fff;
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    border: none;
    border-radius: 10px;
    text-decoration: none;
    cursor: pointer;
    box-shadow: none;
    text-shadow: none;
    transition: background 120ms ease;
}

.hm-product__write-review-cta:hover {
    background: #2a8748;
    color: #fff;
}

.hm-product__write-review-cta:focus-visible {
    outline: 2px solid var(--hm-color-green-darkest);
    outline-offset: 3px;
}

/* === Write-review inline panel (§63 / Phase 7-1p) ========================
 *
 * Hidden by default via the [hidden] attribute (which has higher
 * specificity than any class rule but lower than inline display:none).
 * write-review.js toggles the attribute on the trigger button click;
 * smooth-scrolls the panel into view on reveal.
 *
 * Visual borrows from the §57 Ask-a-Question form: cream-tinted panel
 * matching the Q&A card, brand-green submit button, rating stars
 * using the reverse-DOM-order + sibling-selector trick (lower-rated
 * stars come AFTER the hovered/checked one in DOM order, so the CSS
 * `:checked ~` selector can paint them while leaving higher stars
 * alone).
 *
 * Logged-out variant renders a sign-in CTA in place of the form,
 * mirroring §57's pattern.
 * ========================================================================= */

.hm-product__write-review-panel {
    margin-top: 20px;
    padding: 24px 28px;
    background: var(--hm-color-cream);
    border-radius: 16px;
    /* §63: scroll-margin-top so JS smooth-scroll-into-view doesn't
     * land the panel right under the site header. ~80px gives clearance
     * for the addon's discount banner + announcement strip. */
    scroll-margin-top: 80px;
}

.hm-product__write-review-panel[hidden] {
    /* Explicit display:none with !important to defeat any
     * `[hidden]` reset that downstream rules might apply. CS-Cart's
     * theme doesn't override [hidden] currently but the explicit
     * rule is defensive. */
    display: none !important;
}

.hm-product__write-review-heading {
    margin: 0 0 8px 0;
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: 18px;
    color: var(--hm-color-green-darkest);
    letter-spacing: -0.01em;
    line-height: 1.2;
}

.hm-product__write-review-intro {
    font-family: var(--hm-font-body);
    color: var(--hm-color-green-darkest);
    opacity: 0.85;
    margin: 0 0 18px 0;
    font-size: 14px;
    line-height: 1.5;
}

.hm-product__write-review-form {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.hm-product__write-review-row {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.hm-product__write-review-label {
    font-family: var(--hm-font-body);
    font-weight: 600;
    font-size: 13px;
    color: var(--hm-color-green-darkest);
}

/* Rating stars — radio inputs visually hidden, labels styled as
 * clickable stars. Reverse DOM order (5 → 1) so the CSS sibling
 * selector `~` can paint lower-rated stars after the checked/hovered
 * one without targeting the higher ones. */
.hm-product__write-review-stars {
    display: inline-flex;
    flex-direction: row-reverse;
    /* row-reverse so the visual order is 1→5 left-to-right even
     * though DOM order is 5→1 (for sibling-selector painting). */
    justify-content: flex-end;
    gap: 2px;
    padding: 0;
    margin: 0;
    border: 0;
}

.hm-product__write-review-star-input {
    /* Hide the radio input from sight but keep it focusable so
     * keyboard users can tab through + select with arrow keys. */
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    border: 0;
    padding: 0;
    margin: -1px;
    white-space: nowrap;
}

.hm-product__write-review-star-label {
    display: inline-block;
    cursor: pointer;
    font-size: 28px;
    line-height: 1;
    color: rgba(45, 62, 16, 0.18);
    transition: color 120ms ease, transform 80ms ease;
}

/* Hover-painting: in row-reverse DOM (5,4,3,2,1), when the user
 * hovers a label, all `~` siblings AFTER it (lower-rated stars) get
 * the gold-with-glow treatment too. The hovered star itself is
 * targeted by its own :hover rule. */
.hm-product__write-review-stars:hover .hm-product__write-review-star-label {
    color: rgba(45, 62, 16, 0.18);
}

.hm-product__write-review-stars .hm-product__write-review-star-label:hover,
.hm-product__write-review-stars .hm-product__write-review-star-label:hover ~ .hm-product__write-review-star-label,
.hm-product__write-review-star-input:checked ~ .hm-product__write-review-star-label,
.hm-product__write-review-star-input:checked + .hm-product__write-review-star-label {
    color: #F0A020;
    filter: drop-shadow(0 0 4px rgba(240, 160, 32, 0.45));
}

.hm-product__write-review-star-label:active {
    transform: scale(0.92);
}

.hm-product__write-review-star-input:focus-visible + .hm-product__write-review-star-label {
    outline: 2px solid var(--hm-color-green-dark);
    outline-offset: 3px;
    border-radius: 4px;
}

.hm-product__write-review-textarea {
    font-family: var(--hm-font-body);
    font-size: 15px;
    padding: 12px 14px;
    border: 1px solid rgba(45, 62, 16, 0.15);
    border-radius: 10px;
    background: #fff;
    color: var(--hm-color-green-darkest);
    transition: border-color 120ms ease, box-shadow 120ms ease;
    resize: vertical;
    min-height: 110px;
    line-height: 1.5;
}

.hm-product__write-review-textarea:focus {
    outline: none;
    border-color: var(--hm-color-green-fresh);
    box-shadow: 0 0 0 3px rgba(86, 164, 48, 0.18);
}

.hm-product__write-review-submit,
.hm-product__write-review-signin {
    align-self: flex-start;
    display: inline-block;
    padding: 12px 28px;
    background: var(--hm-color-green-dark);
    color: #fff;
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: 15px;
    border: none;
    border-radius: 12px;
    cursor: pointer;
    box-shadow: none;
    text-shadow: none;
    transition: background 120ms ease, transform 80ms ease;
    text-decoration: none;
    line-height: 1.2;
}

.hm-product__write-review-submit:hover,
.hm-product__write-review-signin:hover {
    background: var(--hm-color-green-fresh);
    color: #fff;
    text-decoration: none;
}

.hm-product__write-review-submit:active,
.hm-product__write-review-signin:active {
    transform: translateY(1px);
}

.hm-product__write-review-submit:focus-visible,
.hm-product__write-review-signin:focus-visible {
    outline: 2px solid var(--hm-color-green-darkest);
    outline-offset: 3px;
}

@media (max-width: 600px) {
    .hm-product__write-review-panel {
        padding: 20px 18px;
        margin-top: 16px;
    }
    .hm-product__write-review-star-label {
        font-size: 32px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .hm-product__write-review-star-label,
    .hm-product__write-review-textarea,
    .hm-product__write-review-submit,
    .hm-product__write-review-signin {
        transition: none;
    }
    .hm-product__write-review-star-label:active,
    .hm-product__write-review-submit:active,
    .hm-product__write-review-signin:active {
        transform: none;
    }
}

.hm-product__reviews-full {
    padding: 16px 0;
    border-top: 1px solid #eee;
    scroll-margin-top: 80px;
}

.hm-product .hm-aisle--also-bought {
    padding: 16px 0;
    border-top: 1px solid #eee;
}

@media (prefers-reduced-motion: reduce) {
    .hm-product__thumbnail,
    .hm-product__add-to-cart,
    .hm-product__qty-btn,
    .hm-product__variant-chip,
    .hm-product__vendor,
    .hm-product__write-review-cta {
        transition: none;
    }
    .hm-product__add-to-cart:active {
        transform: none;
    }
}

@media (max-width: 576px) {
    .hm-product__price-current {
        font-size: 22px;
    }
    .hm-product__price-list {
        font-size: 14px;
    }
    .hm-product__add-to-cart {
        font-size: 15px;
        padding: 0 18px;
    }
    .hm-product__qty-btn {
        width: 38px;
    }
    .hm-product__qty-value {
        min-width: 44px;
        padding: 0 10px;
    }
    .hm-product__write-review-link {
        display: block;
        margin-left: 0;
    }
}

/* ============================================================================
 * === Footer block (Phase 7x) ===============================================
 * ============================================================================
 *
 * Replaces the 11-block inherited footer with one cohesive branded block.
 * Renders inside CS-Cart's stock `footer_general.tpl` block wrapper which
 * adds a collapsible h2 + .ty-footer shell — both neutralized below via
 * the `[data-hm-footer-shell]` sibling marker.
 *
 * Visual structure:
 *   .hm-footer (the outer floating card — dark green, rounded, breathing
 *               room from page edges, contains everything)
 *     .hm-footer__inner (flex row: nav | donate | social)
 *       .hm-footer__nav (3 menu groups with hover/tap panels)
 *       .hm-footer__social (prominent FB + IG icons)
 *       .hm-footer__donate-wrap (Stripe Buy Button — corner-sized)
 *     .hm-footer__legal (EIN + Terms + Privacy)
 *
 * Menu interaction: hover (desktop) + tap (mobile) — see
 * footer-menus.js. Panels open UPWARD from triggers (footer-appropriate)
 * since downward would extend past the page edge.
 *
 * The "contained block" floating-style choice (vs. viewport-pinned) was
 * settled in Phase 7x scoping — matches the addon's homepage aesthetic
 * (hero + intro + carousels all read as "cards on cream wash").
 * ========================================================================= */

/* Neutralize the inherited .ty-footer block wrapper. The wrapper's
 * collapsible h2 + sectional shell aren't appropriate for our cohesive
 * footer card. Targets the wrapper that contains our shell-marker
 * sibling — the [data-hm-footer-shell] sentinel exists ONLY to give
 * this selector something to hook into without us editing the core
 * wrapper template. */
.ty-footer:has([data-hm-footer-shell]),
.ty-footer:has(.hm-footer) {
    background: transparent;
    border: none;
    padding: 0;
    margin: 0;
}
.ty-footer:has([data-hm-footer-shell]) .ty-footer-general__header,
.ty-footer:has(.hm-footer) .ty-footer-general__header {
    display: none;
}
.ty-footer:has([data-hm-footer-shell]) .ty-footer-general__body,
.ty-footer:has(.hm-footer) .ty-footer-general__body {
    padding: 0;
    margin: 0;
    background: transparent;
}
/* Hide the empty sibling sentinel itself */
[data-hm-footer-shell] {
    display: none;
}

/* Neutralize the responsive theme's OUTER footer-area styling. The
 * full parent chain above our `.hm-footer` is six elements deep, and
 * the stock theme paints / sizes / floats every layer differently —
 * each needs explicit override.
 *
 * Layer-by-layer breakdown (top-down):
 *
 * 1. `.tygh-footer > div` = `.container-fluid.ty-footer-grid`
 *    Stock theme applies `#gradient .vertical(@footer_bg, ...)` LESS
 *    mixin → a green gradient background, AND
 *    `width: auto; max-width: none` to stretch edge-to-edge. The
 *    gradient was bleeding past our card's edges and visually
 *    "uncentering" it.
 *
 * 2. `.span16` direct child of .container-fluid
 *    Bootstrap 2.3 grid class with FIXED PIXEL WIDTH (940px base,
 *    1175px desktop) AND `float: left`. The floated 1175px box sits
 *    anchored at the LEFT of the un-capped container-fluid, with
 *    no margin auto, regardless of viewport width. Our footer
 *    inside .span16 inherits this left-anchoring — no amount of
 *    `margin: auto` on our footer can escape because its parent's
 *    bounds are already left-anchored.
 *
 * Override both: strip backgrounds + break .span16 out of its
 * Bootstrap fixed-width float so it fills the un-capped container
 * normally. Then our `.hm-footer { width: calc(100% - 32px);
 * max-width: 1280px; margin: 12px auto 24px auto }` works as
 * designed.
 *
 * `!important` on .span16 because Bootstrap's fixed width is set
 * inside a media query (`.span16 { width: 1175px }`) which has the
 * same specificity as our rule, and media-query order in the cascade
 * is undefined relative to our addon CSS — !important guarantees
 * we win regardless of order. */
.tygh-footer:has(.hm-footer),
.tygh-footer:has(.hm-footer) > div {
    background: transparent !important;
    background-image: none !important;
}
.tygh-footer:has(.hm-footer) .span16 {
    float: none !important;
    /* Cream mat sized 1320px max — 40px wider than the cards inside
     * (which cap at 1280px each) so a visible ~20px brand-cream halo
     * shows on each side. `width: calc(100% - 16px)` keeps a small
     * gutter from the viewport edge at narrow widths (where the
     * 1320px max-width doesn't kick in). `margin: auto` centers the
     * mat. The bumped specificity + !important set this against
     * Bootstrap's media-query-set fixed widths. */
    width: calc(100% - 16px) !important;
    /* §64 (7-1q): 1320 → 1240. The homepage content column (product +
     * vendor carousels, testimonials) RENDERS at 1200px — the prior
     * 1320 mat (and 1280 card below) flowed ~60px past it on each side,
     * reading as "wider than the page." 1240 = the 1200 card (below) plus
     * the intended ~20px §45 cream halo per side, so the halo survives
     * but the footer system no longer overhangs the content column. */
    max-width: 1240px !important;
    margin-left: auto !important;
    margin-right: auto !important;
    background: var(--hm-color-cream);
    border-radius: 20px;
    /* Vertical breathing room at the top/bottom of the mat. The
     * cards inside already carry their own top/bottom margins
     * (SMS banner 32 top, footer 24 bottom) which now read as
     * "cream halo INSIDE the mat" rather than gutters above the
     * tygh-footer area. Adding a small extra padding here gives
     * the visible cream a hair more presence around the rounded
     * corners. */
    padding: 4px 0;
}

/* === Outer footer card ===
 * Background: --hm-color-green-dark (#21703B) — the main brand green
 * used throughout the addon's CTAs, banners, and the SMS banner above.
 * Originally green-darkest (#2D3E10) which read as near-black against
 * the cream wash — felt off-brand. Switching to green-dark unifies the
 * footer with the brand voice and pairs visually with the SMS banner
 * (same color, small gap between them = clearly two cards but
 * unmistakably one footer system).
 *
 * Top margin tightened 64 → 12 (2026-05-22 feedback). Combined with
 * the SMS banner's bottom margin (now 0), the gap between the SMS
 * banner and the footer card drops from 72px to 12px — they read as
 * paired elements rather than two independent sections.
 *
 * Width capped at 1280px (matches .hm-pc / .hm-vc carousels — the
 * most visually prominent homepage content; the responsive theme's
 * own .container-fluid is 1200px max, slightly tighter). Without the
 * cap, the previous `margin: 12px 16px 24px 16px` extended the card
 * nearly viewport-wide, which read as visually disconnected from the
 * centered content above at wide viewports. The `width: calc(100% -
 * 32px)` + `margin: auto` pattern gives a fixed 16px gutter at narrow
 * widths and auto-centers when the 1280 cap is reached. */
.hm-footer {
    width: calc(100% - 32px);
    /* §64 (7-1q): 1280 → 1200 to match the rendered content-column width
     * (the .hm-pc / .hm-vc carousels + testimonials all lay out at 1200px;
     * the old 1280 was the carousels' max-width, not their rendered width).
     * Pairs with the mat above dropping 1320 → 1240. */
    max-width: 1200px;
    margin: 12px auto 24px auto;
    background: var(--hm-color-green-dark);
    color: #fff;
    border-radius: 16px;
    /* Both axes visible so the upward-opening menu panels (which extend
     * ~210px above the card's top edge) are never clipped on ANY viewport.
     *
     * Phase 7-2h (§81.9): this was `overflow-x: clip` — a safety net for
     * the Stripe Buy Button's `transform: scale()` layout box (§45), which
     * is visual-only and doesn't shrink the reserved width. But that net is
     * now dormant: desktop shrinks the box with `zoom` and mobile's
     * single-column grid absorbs it, so the donate cell measures 32–276px
     * INSIDE the card's right edge at every width (360→1366) — nothing to
     * clip. Meanwhile, on iPad Safari `overflow-x: clip` + `overflow-y:
     * visible` clips the VERTICAL panels too (Safari diverges from the spec
     * here; Chromium honors it, which is why local Edge testing never
     * caught it). That clipped the footer menu links on a landscape iPad
     * (1024px) — above the §75 mobile override's 992px ceiling, so 7-2b
     * only fixed portrait. If a future change reintroduces real horizontal
     * overflow here, contain it on a dedicated wrapper that does NOT
     * enclose the menu panels — never back on .hm-footer. */
    overflow-x: visible;
    overflow-y: visible;
    position: relative;
    box-shadow: 0 8px 32px rgba(45, 62, 16, 0.16);
}

.hm-footer__inner {
    /* No max-width — the outer card is already capped at 1280px (above),
     * so the inner content fills the card's full width minus its own
     * padding. Previously had max-width: 1200px which created an extra
     * 40px-each-side gap inside the (then-uncapped) card. */
    padding: 28px 32px;
    display: grid;
    /* Single-row 3-column grid: nav (left) | social (center) | donate (right).
     * `1fr auto 1fr` makes the side columns absorb available space equally
     * while the center column (socials) sizes to its content — and stays
     * truly centered in the inner container regardless of nav/donate widths.
     *
     * Source order in the template is nav → donate → social, but each item
     * has explicit grid-row/column placement below so source order doesn't
     * dictate visual order. The mobile rule (below the 768px breakpoint)
     * re-flows them into a single column with grid-row 1/2/3 = nav/social/donate. */
    grid-template-columns: 1fr auto 1fr;
    column-gap: clamp(16px, 2.5vw, 32px);
    align-items: center;
}

.hm-footer__nav {
    grid-row: 1;
    grid-column: 1;
    justify-self: start;
    display: flex;
    gap: 4px;
    align-items: center;
}
.hm-footer__social {
    grid-row: 1;
    grid-column: 2;
    justify-self: center;
    display: flex;
    gap: 16px;
    /* Phase 7-3a — defensive: lets the FB/IG pills wrap rather than force
     * overflow if labels ever grow. Not the h-scroll root cause (that was
     * the donate-wrap layout box; see the .hm-footer__donate-wrap mobile
     * rule). Harmless on wider screens (never wraps). */
    flex-wrap: wrap;
    justify-content: center;
}
.hm-footer__donate-wrap {
    grid-row: 1;
    grid-column: 3;
    justify-self: end;
    /* Shrink the Stripe Buy Button to corner proportions via `zoom`.
     * `transform: scale()` is visual-only — the layout box stays at
     * the unscaled size, which previously caused the button's reserved
     * grid cell to push past the card's right edge at narrow widths
     * (NOTES §45.12c). `zoom` scales both the visual rendering AND the
     * layout box together, so the grid sees a proportionally-sized cell.
     * Stripe's web-component renders in shadow DOM and would otherwise
     * resist resizing; zoom on the wrapper scales the entire subtree
     * (shadow DOM included) cleanly.
     *
     * Value-tuning history: started at 0.5 (NOTES §45.12d) — too small
     * to read. Bumped to 0.6 (§45.12e) — readable + still corner-sized.
     *
     * Cream outline + rounded frame (NOTES §45.12g): Stripe's default
     * button color happens to match the new footer bg (green-dark),
     * so the button was visually disappearing into the card. Cream
     * border + padding gives it a distinct frame.
     *
     * IMPORTANT — values are pre-zoom: `zoom: 0.6` scales border /
     * padding / border-radius proportionally just like the button
     * itself. Targets are ~2.4px visible border, ~3.6px visible gap,
     * ~7.2px visible corner radius. Pre-zoom values are 1.67× those. */
    border: 4px solid var(--hm-color-cream);
    border-radius: 12px;
    padding: 6px;
    zoom: 0.6;
}

/* === Nav menu triggers === */
.hm-footer__menu-trigger {
    background: transparent;
    border: none;
    color: var(--hm-color-cream);
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: 15px;
    padding: 10px 16px;
    border-radius: 10px;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.hm-footer__menu-trigger:hover,
.hm-footer__menu-trigger:focus-visible,
.hm-footer__menu-trigger[aria-expanded="true"] {
    /* Hover uses --hm-color-green-fresh (lime, #56A430) — the brand's
     * "fresh" accent. Was green-dark, but the footer card itself is
     * now green-dark too (2026-05-22 brand-color pass), which would
     * have made the hover state invisible. Same green-dark → green-fresh
     * vocabulary used by the carousel chevron arrows (.hm-pc__nav). */
    background: var(--hm-color-green-fresh);
    color: #fff;
    outline: none;
}
.hm-footer__menu-trigger:focus-visible {
    box-shadow: 0 0 0 2px var(--hm-color-cream);
}
.hm-footer__menu-chevron {
    font-size: 10px;
    transition: transform 180ms ease;
    margin-top: 1px;
}
.hm-footer__menu-trigger[aria-expanded="true"] .hm-footer__menu-chevron {
    transform: rotate(180deg);
}

/* === Menu panels (open upward from trigger) === */
.hm-footer__menu-group {
    position: relative;
}
.hm-footer__menu-panel {
    position: absolute;
    bottom: calc(100% + 8px); /* anchors above the trigger */
    left: 0;
    min-width: 220px;
    padding: 12px;
    background: #fff;
    color: var(--hm-color-green-darkest);
    border-radius: 12px;
    box-shadow: 0 8px 32px rgba(45, 62, 16, 0.22);
    z-index: 1000;
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 180ms ease, transform 180ms ease;
}
.hm-footer__menu-panel[hidden] {
    display: none;
}
.hm-footer__menu-panel:not([hidden]) {
    opacity: 1;
    transform: translateY(0);
}
/* Caret tail pointing down at the trigger */
.hm-footer__menu-panel::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 24px;
    width: 0;
    height: 0;
    border-left: 8px solid transparent;
    border-right: 8px solid transparent;
    border-top: 8px solid #fff;
}

.hm-footer__menu-links {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.hm-footer__menu-links a {
    display: block;
    padding: 8px 12px;
    font-family: var(--hm-font-body);
    font-size: 14px;
    font-weight: 500;
    color: var(--hm-color-green-darkest);
    text-decoration: none;
    border-radius: 8px;
    transition: background 120ms ease, color 120ms ease;
}
.hm-footer__menu-links a:hover,
.hm-footer__menu-links a:focus-visible {
    background: var(--hm-color-cream);
    color: var(--hm-color-green-darkest);
    outline: none;
}

/* Phase 7-2h (§81) — iPad footer tap fix (belt-and-suspenders to the
 * footer-menus.js hover-detection fix). touch-action:manipulation drops
 * the legacy ~300ms tap delay and double-tap-to-zoom gesture handling so
 * taps on the triggers and panel links register on first contact. No
 * horizontal-scroll container exists in this footer (the original bug
 * report's hypothesis was wrong — measured scrollWidth===clientWidth in
 * both iPad orientations; see NOTES §81). */
.hm-footer__menu-trigger,
.hm-footer__menu-links a,
.hm-footer__contact-content a,
.hm-footer__social-link,
.hm-footer__legal-link {
    touch-action: manipulation;
}

/* === Contact panel (paragraphs not link list) === */
.hm-footer__menu-panel--contact {
    min-width: 260px;
}
.hm-footer__contact-content {
    font-family: var(--hm-font-body);
    font-size: 14px;
    color: var(--hm-color-green-darkest);
    line-height: 1.5;
}
.hm-footer__contact-content p,
.hm-footer__contact-content address {
    margin: 0 0 8px 0;
}
.hm-footer__contact-content > *:last-child {
    margin-bottom: 0;
}
.hm-footer__contact-address {
    font-style: normal;
}
.hm-footer__contact-content a {
    color: var(--hm-color-green-dark);
    text-decoration: none;
    font-weight: 600;
}
.hm-footer__contact-content a:hover {
    color: var(--hm-color-green-fresh);
    text-decoration: underline;
}

/* === Prominent socials === */
.hm-footer__social-link {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    background: rgba(241, 235, 191, 0.12); /* cream at 12% — subtle pill */
    color: var(--hm-color-cream);
    text-decoration: none;
    border-radius: 999px;
    font-family: var(--hm-font-accent);
    font-weight: 600;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    transition: background 160ms ease, color 160ms ease, transform 160ms ease;
}
.hm-footer__social-link:hover,
.hm-footer__social-link:focus-visible {
    background: var(--hm-color-cream);
    color: var(--hm-color-green-darkest);
    transform: translateY(-1px);
    outline: none;
    text-decoration: none;
}
.hm-footer__social-icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}
.hm-footer__social-label {
    line-height: 1;
}

/* === Donate (Stripe Buy Button wrapper) ===
 * Stripe's web-component renders its own button + checkout modal in
 * shadow DOM. We can scale the wrapper but can't restyle internals.
 * The buy-button-id matches the inherited block 460 verbatim — same
 * donation flow. */
.hm-footer__donate-wrap stripe-buy-button {
    display: block;
}

/* === Legal strip === */
.hm-footer__legal {
    padding: 14px 24px;
    border-top: 1px solid rgba(241, 235, 191, 0.18);
    text-align: center;
}
.hm-footer__legal-line {
    margin: 0;
    font-family: var(--hm-font-body);
    font-size: 12px;
    color: var(--hm-color-cream);
    opacity: 0.85;
    display: inline-flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    align-items: center;
}
.hm-footer__ein {
    font-weight: 500;
}
.hm-footer__legal-sep {
    opacity: 0.5;
}
.hm-footer__legal-link {
    color: var(--hm-color-cream);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: color 120ms ease, border-color 120ms ease;
}
.hm-footer__legal-link:hover,
.hm-footer__legal-link:focus-visible {
    color: #fff;
    border-color: rgba(255, 255, 255, 0.5);
    outline: none;
}

/* === Narrow-and-mobile layout (stack everything centered) ===
 * Single-column grid; explicit grid-row reordering so the stack reads
 * nav → social → donate (the visually-centered order Adam wants for
 * thinner viewports). All three items get justify-self: center
 * overrides — the desktop start/center/end values would otherwise
 * shove nav to the left edge of the column.
 *
 * Breakpoint raised 768 → 992 (2026-05-21 feedback pass). The
 * single-row desktop layout needs ~900px of content room to fit
 * nav (~280px) + socials (~240px) + Stripe donate button (~280px,
 * scale() doesn't shrink the layout box) + gaps + padding. At
 * widths between 768-992px the donate button's reserved box was
 * extending past the card's right edge for a visible moment before
 * snapping into the stack. 992px gives a safety margin. */
@media (max-width: 992px) {
    .hm-footer {
        /* Tighter 8px-each-side gutter on mobile (was 16px). Width
         * pattern stays consistent with desktop — calc + auto margins
         * — so the centering logic doesn't need a different shape. */
        width: calc(100% - 16px);
        margin: 32px auto 16px auto;
    }
    .hm-sms-banner {
        /* Mirror the footer's mobile gutter. */
        width: calc(100% - 16px);
    }
    .hm-footer__inner {
        grid-template-columns: 1fr;
        grid-template-rows: auto auto auto;
        padding: 24px 16px;
        row-gap: 16px;
    }
    .hm-footer__nav {
        grid-row: 1;
        grid-column: 1;
        justify-self: center;
        justify-content: center;
        flex-wrap: wrap;
    }
    .hm-footer__social {
        grid-row: 2;
        grid-column: 1;
        justify-self: center;
    }
    .hm-footer__donate-wrap {
        grid-row: 3;
        grid-column: 1;
        justify-self: center;
        /* Phase 7-2b — `zoom: 0.6` (the desktop sizing knob, see the
         * .hm-footer__donate-wrap rule near §45) is a non-standard CSS
         * property that doesn't apply in Firefox <126 (released 2024)
         * or various pre-2022 mobile browsers. Where zoom is a no-op,
         * the Stripe Buy Button renders at full native size (~280×50px)
         * inside our 4px cream border + 6px padding → reads as a "large
         * cream-bordered block" on small phones rather than the intended
         * corner-button presence.
         *
         * Switch to transform: scale on mobile — universally supported.
         * The trade-off is that transform leaves the layout box at
         * unscaled width, but mobile's single-column grid (grid-row 3,
         * justify-self: center) absorbs that without overflow. Desktop
         * (>992px) keeps zoom because its 3-column grid does need the
         * cell to shrink — switching there would re-introduce §45.12c's
         * "donate cell overflows card edge" symptom. */
        zoom: 1;
        transform: scale(0.6);
        transform-origin: center;
        /* Phase 7-3a — THE mobile horizontal-scroll root cause. `transform:
         * scale()` is visual-only: the wrapper's *layout* box stays at the
         * Stripe button's native ~288px (measured: visual 185px, layout
         * 308px incl. border/padding). In the single-column footer grid that
         * 308px layout box inflated the `1fr` track wider than the viewport,
         * pushing the whole footer ~6px past the right edge at <=324px — the
         * page-level overflow the §75.6 clip net was only masking (and masks
         * unreliably on real iOS Safari). §75.2 assumed the single-column
         * grid "absorbs" the unscaled box; it does not at narrow widths.
         * `overflow: hidden` gives the grid item an automatic min-width of 0
         * so it no longer inflates the track; max-width caps it to the cell.
         * The visually-scaled button is well inside the clip box, so nothing
         * user-visible is cut (verified 320–430px). */
        overflow: hidden;
        min-width: 0;
        max-width: 100%;
    }
    /* Phase 7-2b set `.hm-footer { overflow-x: visible }` here to stop the
     * upward menu panels being clipped on mobile. As of 7-2h (§81.9) the
     * base .hm-footer rule is `overflow-x: visible` at all widths, so this
     * mobile-only override is no longer needed — folded into the base rule
     * (which also fixes landscape iPad, >992px, that this override missed). */
}

@media (max-width: 480px) {
    .hm-footer__menu-trigger {
        font-size: 14px;
        padding: 8px 12px;
    }
    .hm-footer__social-link {
        font-size: 12px;
        padding: 8px 14px;
    }
    .hm-footer__legal-line {
        font-size: 11px;
    }
}

/* === Reduced motion === */
@media (prefers-reduced-motion: reduce) {
    .hm-footer__menu-trigger,
    .hm-footer__menu-panel,
    .hm-footer__menu-chevron,
    .hm-footer__menu-links a,
    .hm-footer__social-link,
    .hm-footer__legal-link {
        transition: none;
    }
    .hm-footer__social-link:hover,
    .hm-footer__social-link:focus-visible {
        transform: none;
    }
}

/* ============================================================================
 * === SMS reminder banner (Phase 7x) ========================================
 * ============================================================================
 *
 * Standalone dismissable strip above the branded footer. Reuses the §8
 * discount banner UX pattern (sessionStorage key, JS-revealed to avoid
 * FOUC) but renders inline rather than via the islands DOM-construction
 * pattern — simpler since there's no admin-tunable content to plumb.
 *
 * sessionStorage key `hm:sms-banner:dismissed` mirrors the §8 key
 * `hm:discount-banner:dismissed`. JS reveals the banner only if NOT
 * already dismissed; close button writes the key + hides.
 * ========================================================================= */

/* Same .ty-footer wrapper neutralization applies (the SMS banner is
 * also a FOOTER-container block). The selectors above already cover
 * any block containing .hm-sms-banner via the :has() approach — but
 * we add an explicit fallback in case :has() isn't supported. */
.ty-footer:has(.hm-sms-banner) {
    background: transparent;
    border: none;
    padding: 0;
    margin: 0;
}
.ty-footer:has(.hm-sms-banner) .ty-footer-general__header {
    display: none;
}
.ty-footer:has(.hm-sms-banner) .ty-footer-general__body {
    padding: 0;
    margin: 0;
    background: transparent;
}

.hm-sms-banner {
    /* Bottom margin 8 → 0 (2026-05-22). The footer card's top margin
     * (12px) now provides the entire gap between the SMS banner and
     * the footer below — paired-element spacing rather than the
     * previous 72px breathing room.
     *
     * Width capped at 1280px to match the footer card below (NOTES
     * §45.12h). Same `calc(100% - 32px)` + `margin: auto` pattern as
     * the footer for consistent gutters + auto-centering at wide
     * viewports. */
    width: calc(100% - 32px);
    max-width: 1280px;
    margin: 32px auto 0 auto;
    background: var(--hm-color-green-dark);
    color: #fff;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 4px 16px rgba(45, 62, 16, 0.14);
}
.hm-sms-banner[hidden] {
    display: none;
}

.hm-sms-banner__inner {
    /* No max-width — outer banner is now capped at 1280px (above). */
    padding: 12px 56px 12px 24px; /* extra right padding for close button */
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    position: relative;
}

.hm-sms-banner__message {
    margin: 0;
    font-family: var(--hm-font-body);
    font-size: 14px;
    font-weight: 500;
    text-align: center;
    line-height: 1.4;
}

.hm-sms-banner__cta {
    color: var(--hm-color-cream);
    font-family: var(--hm-font-accent);
    font-weight: 700;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    text-decoration: underline;
    text-underline-offset: 3px;
    margin-left: 4px;
    white-space: nowrap;
}
.hm-sms-banner__cta:hover,
.hm-sms-banner__cta:focus-visible {
    color: #fff;
    outline: none;
}

.hm-sms-banner__close {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    background: transparent;
    border: none;
    color: #fff;
    font-size: 22px;
    line-height: 1;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 120ms ease;
}
.hm-sms-banner__close:hover,
.hm-sms-banner__close:focus-visible {
    background: rgba(255, 255, 255, 0.18);
    outline: none;
}

@media (max-width: 576px) {
    .hm-sms-banner__inner {
        padding: 10px 48px 10px 16px;
        flex-direction: column;
        gap: 4px;
    }
    .hm-sms-banner__message {
        font-size: 13px;
    }
    .hm-sms-banner__cta {
        font-size: 12px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .hm-sms-banner__close,
    .hm-sms-banner__cta {
        transition: none;
    }
}

/* ============================================================================
 * === Testimonials marquee (Phase 7z) =======================================
 * ============================================================================
 *
 * Single-row right-to-left CSS marquee of testimonial pull-quote
 * cards. Data sourced from the discussion addon's E-thread via
 * fn_harvestly_modern_get_testimonials (NOTES §47). Template
 * renders the list TWICE inside the track (.hm-testimonials__group
 * + .hm-testimonials__group--clone); the animation translates 0
 * → -50% so the loop is seamless.
 *
 * Pause-on-hover (desktop): CSS via :hover .track { play-state: paused }.
 * Pause-on-touch-and-hold (mobile): JS adds .is-paused on touchstart.
 * Both routes drive the same animation-play-state.
 *
 * Gold-with-glow treatment on the opening quote glyph and the 5
 * stars is decorative; #F0A020 is the marquee-only accent (not
 * promoted to a brand token since it isn't used elsewhere).
 * filter: drop-shadow gives a subtle warm halo around both.
 *
 * Reduced motion: track becomes flex-wrap, animation off, the
 * clone group is hidden entirely (so only the first N
 * testimonials show in a static grid). Mask-image edges also
 * disabled — without the animation the fade serves no purpose.
 * ========================================================================= */

.hm-testimonials {
    padding: 64px 0 48px 0;
    overflow: hidden;
    position: relative;
    /* Phase 7-2b — defensive paint containment for the §47 marquee. The
     * inner .hm-testimonials__track is ~6800px wide (3 testimonial groups
     * × 2 clones) and transformed via the marquee keyframes. On some
     * mobile browsers the combination of mask-image (on the marquee
     * wrapper below) + transform on the track has been observed to leak
     * past the wrapper's overflow:hidden — `contain: paint` is the
     * stronger spec guarantee that paint operations cannot extend past
     * the border edge. Safe on all modern browsers; no-op on older ones. */
    contain: paint;
}

.hm-testimonials__inner {
    max-width: 1200px;
    margin: 0 auto 32px auto;
    padding: 0 24px;
    text-align: center;
}

.hm-testimonials__heading {
    margin: 0;
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: clamp(24px, 3vw, 32px);
    color: var(--hm-color-green-darkest);
    letter-spacing: -0.01em;
}

/* === Marquee viewport + track === */

.hm-testimonials__marquee {
    width: 100%;
    overflow: hidden;
    position: relative;
    /* Subtle edge fades so cards appear/disappear smoothly rather
     * than slamming into the viewport edges. Webkit prefix kept
     * for older Safari; spec-mask works in modern browsers. */
    -webkit-mask-image: linear-gradient(to right,
        transparent 0%,
        #000 5%,
        #000 95%,
        transparent 100%);
            mask-image: linear-gradient(to right,
        transparent 0%,
        #000 5%,
        #000 95%,
        transparent 100%);
}

.hm-testimonials__track {
    display: flex;
    width: max-content;
    /* Phase 7-2e — bumped 50s → 65s (~30% slower) per Adam's feedback
     * that the marquee was "whizzing by" on prod. Live testimonial
     * cards have more content than the local samples used to tune the
     * §47 original, so the visual feel was faster than intended. */
    animation: hm-testimonials-marquee 65s linear infinite;
    will-change: transform;
}

/* Each group is the full set of cards. Two groups laid end-to-end
 * give us the translateX(0) → translateX(-50%) loop point. */
.hm-testimonials__group {
    display: flex;
    gap: 20px;
    /* Right-edge spacer so the gap between the last card of group A
     * and the first card of group B matches the inter-card gap. */
    padding-right: 20px;
    flex-shrink: 0;
}

@keyframes hm-testimonials-marquee {
    0%   { transform: translateX(0); }
    100% { transform: translateX(-50%); }
}

/* Pause on hover — desktop only. The hover query keeps touch devices
 * out of this branch (their "tap = brief hover" would pause-resume
 * jitterily). Touch pause is wired by testimonials-marquee.js. */
@media (hover: hover) {
    .hm-testimonials__marquee:hover .hm-testimonials__track {
        animation-play-state: paused;
    }
}

/* JS-applied class on touchstart (mobile pause-on-touch-and-hold). */
.hm-testimonials__marquee.is-paused .hm-testimonials__track {
    animation-play-state: paused;
}

/* === Cards === */

.hm-testimonials__card {
    flex-shrink: 0;
    width: 340px;
    background: #fff;
    border: 1px solid rgba(45, 62, 16, 0.08);
    border-radius: 16px;
    padding: 32px 28px 24px 28px;
    box-shadow: 0 2px 12px rgba(45, 62, 16, 0.04);
    position: relative;
    display: flex;
    flex-direction: column;
}

@media (max-width: 576px) {
    .hm-testimonials__card {
        width: 280px;
        padding: 24px 20px 20px 20px;
    }
}

.hm-testimonials__quote-glyph {
    position: absolute;
    top: 8px;
    left: 20px;
    font-family: var(--hm-font-script);
    font-weight: 700;
    font-size: 64px;
    line-height: 1;
    color: #F0A020;
    filter: drop-shadow(0 0 8px rgba(240, 160, 32, 0.4));
    pointer-events: none;
    user-select: none;
}

.hm-testimonials__quote {
    margin: 16px 0 24px 0;
    font-family: var(--hm-font-body);
    font-size: 15px;
    font-weight: 400;
    line-height: 1.55;
    color: var(--hm-color-green-darkest);
    /* Cap visible quote at ~6 lines so very long testimonials
     * don't blow out the card height. Average Harvestly post
     * fits within this; outliers get a clean ellipsis. */
    display: -webkit-box;
    -webkit-line-clamp: 6;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.hm-testimonials__meta {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: auto;
}

.hm-testimonials__author {
    margin: 0;
    font-family: var(--hm-font-accent);
    font-weight: 600;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--hm-color-green-dark);
}

.hm-testimonials__stars {
    font-size: 16px;
    color: #F0A020;
    letter-spacing: 2px;
    filter: drop-shadow(0 0 4px rgba(240, 160, 32, 0.5));
}

/* === Reduced motion — static grid fallback ===
 *
 * Turn the marquee off entirely: track becomes a wrapping flex
 * grid, animation removed, edge-fade mask off. Hide the clone
 * group so only the source-of-truth testimonials render. */
@media (prefers-reduced-motion: reduce) {
    .hm-testimonials__marquee {
        -webkit-mask-image: none;
                mask-image: none;
    }
    .hm-testimonials__track {
        animation: none;
        width: 100%;
        flex-wrap: wrap;
        justify-content: center;
        gap: 20px;
        padding: 0 24px;
    }
    .hm-testimonials__group {
        flex-wrap: wrap;
        justify-content: center;
        padding-right: 0;
    }
    .hm-testimonials__group--clone {
        display: none;
    }
}

/* === Product page Q&A + inventory (Phase 7-1j) ============================ */

/* --- Inventory chip ---
 *
 * Subtle text-with-icon-spacing chip that sits between the price and
 * the rating eyebrow on the product page. Three visual states keyed
 * off modifier classes:
 *
 *   default state — neutral darkest-green text, lowered opacity. Tells
 *     the customer there's plenty without shouting.
 *   --low — warm warning red, full opacity, bold weight + 🔥 note chip
 *     pulsing gently to draw attention. Triggers at amount 1-3 (threshold
 *     chosen per inventory distribution: 128 products live in this band).
 *   --out — muted grey, no animation. Add-to-cart button is already
 *     handled by stock CS-Cart's inventory gating; this just labels why.
 *
 * The pulse uses opacity oscillation (not transform) so it doesn't
 * impact layout or composite cost. 2.5s cycle = visible but not jittery.
 * @media reduced-motion kills it. */

.hm-product__inventory {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: -4px;
    font-family: var(--hm-font-body);
    font-size: 14px;
}

.hm-product__inventory-text {
    color: var(--hm-color-green-darkest);
    opacity: 0.7;
}

.hm-product__inventory--low .hm-product__inventory-text {
    color: #C8553D;
    font-weight: 600;
    opacity: 1;
}

.hm-product__inventory--out .hm-product__inventory-text {
    color: #999;
    font-weight: 500;
    opacity: 1;
}

.hm-product__inventory-note {
    font-family: var(--hm-font-accent);
    font-weight: 700;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #C8553D;
    padding: 3px 8px;
    border-radius: 999px;
    background: rgba(200, 85, 61, 0.08);
}

/* Animated flame mount inside the "Going fast" chip. inventory-fire.js
 * swaps the 🔥 emoji fallback for a looping fire Lottie; this just sizes
 * the mount slot. Slightly taller than the 11px chip caps so the flame
 * reads cleanly, and the 500:690 source aspect is preserved. If JS / the
 * Lottie CDN fails, the inline-block keeps the emoji centred the same way. */
.hm-product__inventory-fire {
    display: inline-block;
    width: 13px;
    height: 18px;
    vertical-align: -4px;
    line-height: 1;
}
.hm-product__inventory-fire svg { display: block; width: 100%; height: 100%; }

.hm-product__inventory--low {
    animation: hm-inventory-pulse 2.5s ease-in-out infinite;
}

@keyframes hm-inventory-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.78; }
}

/* --- Ask-a-Question section ---
 *
 * Sits at the bottom of the product page, after reviews + also-bought.
 * Cream background to visually distinguish from white-bg sections above
 * and signal "different kind of action: contact, not browse."
 *
 * Single textarea form for logged-in users; sign-in CTA for guests.
 * Submit button is the standard brand-green pill we use across the site
 * (matches .hm-product__add-to-cart styling for visual consistency).
 *
 * No JS — standard CS-Cart form POST + page reload + notification
 * surfaced by fn_set_notification (handled by vendor_communication's
 * create_thread controller). */

.hm-product__ask {
    margin-top: 48px;
    padding: 32px;
    background: var(--hm-color-cream);
    border-radius: 16px;
}

.hm-product__ask .hm-product__section-heading {
    margin-top: 0;
}

.hm-product__ask-intro {
    font-family: var(--hm-font-body);
    color: var(--hm-color-green-darkest);
    opacity: 0.85;
    margin: 0 0 20px 0;
    font-size: 15px;
    line-height: 1.5;
}

.hm-product__ask-form {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.hm-product__ask-row {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.hm-product__ask-label {
    font-family: var(--hm-font-body);
    font-weight: 600;
    font-size: 13px;
    color: var(--hm-color-green-darkest);
}

.hm-product__ask-textarea {
    font-family: var(--hm-font-body);
    font-size: 15px;
    padding: 12px 14px;
    border: 1px solid rgba(45, 62, 16, 0.15);
    border-radius: 10px;
    background: #fff;
    color: var(--hm-color-green-darkest);
    transition: border-color 120ms ease, box-shadow 120ms ease;
    resize: vertical;
    min-height: 110px;
    line-height: 1.5;
}

.hm-product__ask-textarea:focus {
    outline: none;
    border-color: var(--hm-color-green-fresh);
    box-shadow: 0 0 0 3px rgba(86, 164, 48, 0.18);
}

.hm-product__ask-submit,
.hm-product__ask-signin {
    align-self: flex-start;
    display: inline-block;
    padding: 12px 28px;
    background: var(--hm-color-green-dark);
    color: #fff;
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: 15px;
    border: none;
    border-radius: 12px;
    cursor: pointer;
    transition: background 120ms ease, transform 80ms ease;
    text-decoration: none;
    line-height: 1.2;
}

.hm-product__ask-submit:hover,
.hm-product__ask-signin:hover {
    background: var(--hm-color-green-fresh);
    color: #fff;
    text-decoration: none;
}

.hm-product__ask-submit:active,
.hm-product__ask-signin:active {
    transform: translateY(1px);
}

@media (max-width: 600px) {
    .hm-product__ask {
        padding: 24px 20px;
        margin-top: 32px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .hm-product__inventory--low {
        animation: none;
    }
    .hm-product__ask-textarea,
    .hm-product__ask-submit,
    .hm-product__ask-signin {
        transition: none;
    }
    .hm-product__ask-submit:active,
    .hm-product__ask-signin:active {
        transform: none;
    }
}

/* === Vendor directory pagination (Phase 7-1k) ============================ */

/* Centered control bar below the vendor list. The whole nav hides
 * via [hidden] when there's only one page of results — JS sets the
 * attribute; CSS just respects display:none semantics.
 *
 * Buttons use the brand-green pill style consistent with the rest
 * of the addon's primary CTAs (matches §44's "Browse all" pattern
 * and §39's Add-to-cart button). The Montserrat-uppercase indicator
 * matches the eyebrow treatment we use across the site for meta
 * text. */

.hm-vendor-directory__pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    margin: 40px auto 24px auto;
    padding: 16px 24px;
    max-width: 480px;
}

.hm-vendor-directory__pagination[hidden] {
    display: none;
}

.hm-vendor-directory__pagination-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 10px 18px;
    background: var(--hm-color-green-dark);
    color: #fff;
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: 14px;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    transition: background 120ms ease, transform 120ms ease;
}

.hm-vendor-directory__pagination-btn:hover {
    background: var(--hm-color-green-fresh);
    transform: translateY(-1px);
}

.hm-vendor-directory__pagination-btn:active {
    transform: translateY(0);
}

.hm-vendor-directory__pagination-btn[hidden] {
    display: none;
}

/* min-width keeps the indicator from shifting Prev/Next as the
 * page number text changes width ("Page 1 of 5" vs "Page 10 of 5").
 * Calibrated for "Page N of M" with single-digit totals; the rare
 * "Page 10..." case will widen the indicator by ~10px — acceptable
 * since pagination changes are intentional user actions. */
.hm-vendor-directory__pagination-indicator {
    font-family: var(--hm-font-accent);
    font-weight: 600;
    font-size: 13px;
    color: var(--hm-color-green-darkest);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    opacity: 0.85;
    min-width: 100px;
    text-align: center;
}

@media (prefers-reduced-motion: reduce) {
    .hm-vendor-directory__pagination-btn {
        transition: none;
    }
    .hm-vendor-directory__pagination-btn:hover,
    .hm-vendor-directory__pagination-btn:active {
        transform: none;
    }
}

@media (max-width: 576px) {
    .hm-vendor-directory__pagination {
        gap: 12px;
        margin: 32px 16px 16px 16px;
        padding: 12px 16px;
    }
    .hm-vendor-directory__pagination-btn {
        padding: 8px 14px;
        font-size: 13px;
    }
    .hm-vendor-directory__pagination-indicator {
        font-size: 12px;
        min-width: 80px;
    }
}

/* ============================================================
 * === Nonprofit partners (Phase 7-1l) ========================
 * ============================================================
 *
 * Replaces inherited block 318 (banner-carousel of partner logos).
 *
 *   DESKTOP (>= 769px): a 3-col staggered logo grid (3 top, 2
 *   bottom shifted half a column for honeycomb visual rhythm).
 *   Hover any logo → tile lifts, warm-gold drop-shadow glow, and a
 *   tooltip card appears below with the partner's name + mission
 *   blurb + Harvestly leaf mark in the corner.
 *
 *   MOBILE (<= 768px): a horizontal CSS marquee scrolls the logos
 *   continuously. Below it, a single text-display element shows
 *   the centered partner's name + description. partners.js syncs
 *   the text via rAF center-detection.
 *
 *   Warm-gold tone: rgba(240, 160, 32, X) — same as §46/§48/§52
 *   (testimonials marquee accent, bag-icon glow, cart-drawer title
 *   glow). Hardcoded RGBA (no token) because the existing pattern
 *   uses it that way; if a token gets added later, this section
 *   migrates with the rest.
 *
 *   reduced-motion: marquee animation off, hover transforms off,
 *   tile-lift off. The marquee track wraps into a static centered
 *   row in reduced-motion mode (logos no longer scroll). The text-
 *   display still shows the first partner's blurb statically — JS
 *   never updates it because the rAF loop bails when prefers-
 *   reduced-motion is set.
 *
 *   Click-through: every logo is an <a target="_blank"> on both
 *   desktop and mobile, with rel="noopener". The tooltip is a
 *   sibling (not a child of the link) so its text isn't part of
 *   the link's accessible name. */

.hm-partners {
    background: var(--hm-color-cream);
    padding: 56px 24px 48px;
    position: relative;
    /* No `overflow: hidden` here — row-2 tooltips render below
     * the bottom tile (top: calc(100% + 14px)) and that anchor
     * point already sits near the section's padding-bottom. With
     * overflow clipping enabled, those tooltips get sliced off.
     *
     * Fixed-width grid columns + justify-content:center already
     * contain the stagger horizontally, so the safety net isn't
     * needed. The tooltip has z-index:50 so it layers cleanly
     * over the testimonials section below. */
}

.hm-partners__header {
    max-width: 560px;
    margin: 0 auto 36px;
    text-align: center;
}

.hm-partners__heading {
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: clamp(24px, 3vw, 32px);
    color: var(--hm-color-green-darkest);
    letter-spacing: -0.01em;
    margin: 0 0 12px;
}

.hm-partners__intro {
    font-family: var(--hm-font-body);
    font-size: 15px;
    line-height: 1.55;
    color: var(--hm-color-green-darkest);
    opacity: 0.85;
    margin: 0;
}

/* === Desktop: 3-col staggered grid ============================ */

.hm-partners__grid {
    /* Fixed 155px column widths (NOT 1fr) so each tile is
     * exactly 155px square regardless of the surrounding block
     * wrapper's width. The 1fr-based layout would stretch tiles
     * to fill the parent container when the parent exceeds the
     * grid's intended size, which made tiles inflate to 260+px
     * on this site's content column.
     *
     * `justify-content: center` is what actually centers the
     * grid items inside the section — the grid track itself is
     * only as wide as its 3 fixed columns + gaps (491px total),
     * and centering pins it inside the cream section regardless
     * of how wide the parent renders. */
    display: grid;
    grid-template-columns: repeat(3, 155px);
    justify-content: center;
    gap: 32px 52px;
    margin: 0 auto;
}

/* §66 (7-1s): the staggered desktop grid is hidden at ALL widths — the
 * marquee is now the universal display (Adam's "marquee everywhere"
 * pivot). The grid markup is RETAINED in the template (rendered
 * display:none) solely as the partner name/blurb data source that
 * partners.js scrapes for the marquee text-sync. Do not delete the grid
 * markup without first moving that data to data-props. (Unconditional —
 * placed after the base `display:grid` rule so it wins everywhere.) */
.hm-partners__grid {
    display: none;
}

.hm-partners__tile {
    position: relative;
    aspect-ratio: 1 / 1;
}

/* Stagger row 2 (the 4th and 5th tiles in a 3-col grid). The
 * shift is half a tile width + half a gap = `calc(50% + 26px)`
 * where 50% = half the 155px tile = 77.5px and 26px = half the
 * 52px horizontal gap. Combined: 103.5px shift, which centers
 * row-2 tiles in the gaps between row-1 tiles.
 *
 * Keep `calc(50% + Npx)` where N = (horizontal-gap / 2) in
 * sync with the `gap` value above. */
.hm-partners__grid > .hm-partners__tile:nth-child(3n+4),
.hm-partners__grid > .hm-partners__tile:nth-child(3n+5) {
    transform: translateX(calc(50% + 26px));
}

.hm-partners__logo-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    /* 20px padding inside a 155px tile leaves the logo a 115px
     * canvas — enough that detailed marks (TMHA's flower, City
     * Farm's seal) read clearly, but small enough that simpler
     * marks (PSHH icon) don't blob out the full tile. */
    padding: 20px;
    background: #fff;
    border: 1px solid rgba(45, 62, 16, 0.08);
    border-radius: 14px;
    box-shadow: 0 4px 12px rgba(45, 62, 16, 0.05);
    cursor: pointer;
    text-decoration: none;
    transition: transform 200ms ease, box-shadow 240ms ease,
                border-color 240ms ease;
}

.hm-partners__logo-link:hover,
.hm-partners__logo-link:focus-visible {
    transform: translateY(-4px);
    border-color: rgba(240, 160, 32, 0.5);
    box-shadow: 0 12px 28px rgba(45, 62, 16, 0.12),
                0 0 0 1px rgba(240, 160, 32, 0.35);
    outline: none;
    text-decoration: none;
}

.hm-partners__logo {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
    transition: filter 240ms ease;
}

.hm-partners__logo-link:hover .hm-partners__logo,
.hm-partners__logo-link:focus-visible .hm-partners__logo,
.hm-partners__tile.is-tooltip-open .hm-partners__logo {
    filter: drop-shadow(0 0 6px rgba(240, 160, 32, 0.55))
            drop-shadow(0 0 14px rgba(240, 160, 32, 0.25));
}

/* === Desktop tooltip ========================================== */

.hm-partners__tooltip {
    position: absolute;
    top: calc(100% + 14px);
    left: 50%;
    /* Two transforms: the centering translate AND the entry slide.
     * Both are merged on the same `transform` so we don't break
     * the centering when toggling visibility. */
    transform: translateX(-50%) translateY(8px);
    z-index: 50;

    width: max-content;
    max-width: 320px;
    padding: 20px 22px 28px;

    background: #fff;
    border-radius: 14px;
    box-shadow: 0 14px 36px rgba(45, 62, 16, 0.18),
                0 0 0 1px rgba(240, 160, 32, 0.18);

    opacity: 0;
    pointer-events: none;
    transition: opacity 180ms ease, transform 180ms ease;
}

.hm-partners__tooltip:not([hidden]) {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
    pointer-events: auto;
}

/* Tooltip arrow — small diamond rotated 45°, half hidden behind
 * the tooltip body so only the top point shows as a triangle.
 * Border on the top + left side picks up the warm-gold ring tone. */
.hm-partners__tooltip::before {
    content: '';
    position: absolute;
    top: -7px;
    left: 50%;
    transform: translateX(-50%) rotate(45deg);
    width: 12px;
    height: 12px;
    background: #fff;
    border-top: 1px solid rgba(240, 160, 32, 0.18);
    border-left: 1px solid rgba(240, 160, 32, 0.18);
}

/* When row 2's tooltip would overflow the right edge, this
 * helper class (set by partners.js if needed) shifts it left.
 * Default centered placement works for most viewport widths. */
.hm-partners__tooltip.is-shift-left {
    left: auto;
    right: 0;
    transform: translateY(8px);
}
.hm-partners__tooltip.is-shift-left:not([hidden]) {
    transform: translateY(0);
}
.hm-partners__tooltip.is-shift-left::before {
    left: auto;
    right: 18px;
    transform: rotate(45deg);
}
.hm-partners__tooltip.is-shift-right {
    left: 0;
    transform: translateY(8px);
}
.hm-partners__tooltip.is-shift-right:not([hidden]) {
    transform: translateY(0);
}
.hm-partners__tooltip.is-shift-right::before {
    left: 18px;
    transform: rotate(45deg);
}

/* === Row-2 tooltips flip UPWARD ==================================
 * The bottom-row tiles (4th + 5th in a 3-col grid) sit close to
 * the section's bottom edge — opening their tooltips downward
 * pushes them into the testimonials section below, where they
 * clip behind that block's z-index/stacking. Same `:nth-child`
 * selectors that drive the stagger here flip the tooltip
 * positioning: bottom-anchored instead of top-anchored, arrow
 * inverted to point down at the logo, slide-in transform
 * inverted on the Y axis. */

.hm-partners__grid > .hm-partners__tile:nth-child(3n+4) .hm-partners__tooltip,
.hm-partners__grid > .hm-partners__tile:nth-child(3n+5) .hm-partners__tooltip {
    top: auto;
    bottom: calc(100% + 14px);
    transform: translateX(-50%) translateY(-8px);
}

.hm-partners__grid > .hm-partners__tile:nth-child(3n+4) .hm-partners__tooltip:not([hidden]),
.hm-partners__grid > .hm-partners__tile:nth-child(3n+5) .hm-partners__tooltip:not([hidden]) {
    transform: translateX(-50%) translateY(0);
}

/* Arrow flipped: now anchored to the tooltip's BOTTOM edge,
 * with bottom + right borders (the visible faces of the diamond
 * point downward when rotated 45°). */
.hm-partners__grid > .hm-partners__tile:nth-child(3n+4) .hm-partners__tooltip::before,
.hm-partners__grid > .hm-partners__tile:nth-child(3n+5) .hm-partners__tooltip::before {
    top: auto;
    bottom: -7px;
    border-top: none;
    border-left: none;
    border-bottom: 1px solid rgba(240, 160, 32, 0.18);
    border-right: 1px solid rgba(240, 160, 32, 0.18);
}

/* Horizontal-shift variants set by partners.js when the tooltip
 * would overflow the viewport edge also need the Y inversion. */
.hm-partners__grid > .hm-partners__tile:nth-child(3n+4) .hm-partners__tooltip.is-shift-left,
.hm-partners__grid > .hm-partners__tile:nth-child(3n+5) .hm-partners__tooltip.is-shift-left,
.hm-partners__grid > .hm-partners__tile:nth-child(3n+4) .hm-partners__tooltip.is-shift-right,
.hm-partners__grid > .hm-partners__tile:nth-child(3n+5) .hm-partners__tooltip.is-shift-right {
    transform: translateY(-8px);
}

.hm-partners__grid > .hm-partners__tile:nth-child(3n+4) .hm-partners__tooltip.is-shift-left:not([hidden]),
.hm-partners__grid > .hm-partners__tile:nth-child(3n+5) .hm-partners__tooltip.is-shift-left:not([hidden]),
.hm-partners__grid > .hm-partners__tile:nth-child(3n+4) .hm-partners__tooltip.is-shift-right:not([hidden]),
.hm-partners__grid > .hm-partners__tile:nth-child(3n+5) .hm-partners__tooltip.is-shift-right:not([hidden]) {
    transform: translateY(0);
}

.hm-partners__tooltip-name {
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: 16px;
    color: var(--hm-color-green-darkest);
    margin: 0 0 8px;
    letter-spacing: -0.005em;
}

.hm-partners__tooltip-description {
    font-family: var(--hm-font-body);
    font-size: 14px;
    line-height: 1.55;
    color: var(--hm-color-green-darkest);
    opacity: 0.88;
    margin: 0;
}

.hm-partners__tooltip-mark {
    position: absolute;
    right: 12px;
    bottom: 10px;
    width: 26px;
    height: 22px;
    color: var(--hm-color-green-fresh);
    opacity: 0.85;
    filter: drop-shadow(0 0 4px rgba(240, 160, 32, 0.45))
            drop-shadow(0 0 10px rgba(240, 160, 32, 0.2));
    pointer-events: none;
}

.hm-partners__tooltip-mark svg {
    width: 100%;
    height: 100%;
    display: block;
}

/* === Marquee + synced text (all viewports, §66 / 7-1s) ========
 * Was mobile-only (<=768px); now the universal partners display
 * after the "marquee everywhere" pivot. */

.hm-partners__mobile {
    display: block;
}

.hm-partners__marquee {
    width: 100%;
    /* overflow:hidden clips the scrolling track horizontally (the marquee
     * illusion). §66 (7-1s): vertical padding gives the featured tile's
     * gold drop-shadow glow room to render INSIDE the clip box — without
     * it the glow was sliced off at the marquee's top/bottom edges. We
     * can't use overflow-y:visible (one-axis-visible computes to auto and
     * still clips), so padding is the right lever. The horizontal mask
     * fade is unaffected (it runs left↔right across the padded box).
     *
     * Phase 7-2b — `contain: paint` is the stronger guarantee that the
     * inner ~1380px wide track (with its transform animation + the
     * mask-image fade) cannot leak past this box on browsers where
     * overflow:hidden + transform interact buggy. Defensive depth
     * against the prod-only L/R-scroll symptom; no-op on browsers
     * that already honor overflow:hidden correctly. */
    overflow: hidden;
    contain: paint;
    padding: 16px 0;
    position: relative;
    margin-bottom: 16px;

    /* Soft fade at the marquee edges so logos don't pop on/off
     * the screen abruptly. mask-image lets the cream background
     * show through. */
    -webkit-mask-image: linear-gradient(to right,
        transparent 0%, #000 10%, #000 90%, transparent 100%);
            mask-image: linear-gradient(to right,
        transparent 0%, #000 10%, #000 90%, transparent 100%);
}

.hm-partners__track {
    display: flex;
    width: max-content;
    animation: hm-partners-marquee 40s linear infinite;
    will-change: transform;
}

.hm-partners__marquee-group {
    display: flex;
    flex-shrink: 0;
    gap: 16px;
    padding-right: 16px;
}

@keyframes hm-partners-marquee {
    0%   { transform: translate3d(0, 0, 0); }
    100% { transform: translate3d(-50%, 0, 0); }
}

.hm-partners__marquee-tile {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 110px;
    height: 110px;
    padding: 14px;
    background: #fff;
    border: 1px solid rgba(45, 62, 16, 0.08);
    border-radius: 14px;
    box-shadow: 0 4px 10px rgba(45, 62, 16, 0.05);
    text-decoration: none;
    transition: filter 200ms ease, transform 200ms ease;
}

.hm-partners__marquee-tile img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

/* JS adds .is-centered to the marquee tile under the viewport
 * center while the marquee scrolls. Visual confirmation that the
 * text-display below corresponds to which logo is shown. */
.hm-partners__marquee-tile.is-centered {
    /* §66 (7-1s): brand-green halo (the #56a430 logo leaf green) instead
     * of the gold accent — a bright green reads as a luminous glow and is
     * more striking against the cream section than the warmer gold (Adam).
     * Slightly larger blur + alpha so the green halo carries. */
    filter: drop-shadow(0 0 11px rgba(86, 164, 48, 0.65));
}

.hm-partners__text-display {
    text-align: center;
    padding: 8px 24px 0;
    /* Reserve space so the description's longest line doesn't
     * cause layout shift when text swaps. Two-line min for
     * 14px/1.55 line-height ≈ 43px; plus name (~22px) and
     * margins ≈ 90px total. */
    min-height: 110px;
    /* §66 (7-1s): cap + center so the blurb reads as a tidy column
     * on wide desktops instead of stretching the full section width.
     * Below ~620px the cap is a no-op and it stays full-width. */
    max-width: 620px;
    margin-left: auto;
    margin-right: auto;
}

.hm-partners__text-name {
    font-family: var(--hm-font-display);
    font-weight: 700;
    font-size: 17px;
    color: var(--hm-color-green-darkest);
    margin: 0 0 10px;
    transition: opacity 220ms ease;
}

.hm-partners__text-description {
    font-family: var(--hm-font-body);
    font-size: 14px;
    line-height: 1.55;
    color: var(--hm-color-green-darkest);
    opacity: 0.88;
    margin: 0;
    transition: opacity 220ms ease;
}

/* JS toggles this class on the text-display wrapper during a
 * partner swap. CSS fades name + description down → JS swaps text
 * → CSS removes the class → fade back up. 220ms half-cycle each
 * way ≈ 440ms total — slow enough to read as a transition, fast
 * enough that the marquee's next logo is still centered when the
 * fade-in completes. */
.hm-partners__text-display.is-transitioning .hm-partners__text-name,
.hm-partners__text-display.is-transitioning .hm-partners__text-description {
    opacity: 0;
}

/* === Desktop sizing for the universal marquee (§66 / 7-1s) =====
 * The marquee tile + type were sized for mobile (110px tiles). On
 * wider viewports give the logos + featured text a bit more presence. */
@media (min-width: 769px) {
    .hm-partners__marquee-tile {
        width: 140px;
        height: 140px;
        padding: 18px;
    }
    .hm-partners__marquee-group {
        gap: 24px;
        padding-right: 24px;
    }
    .hm-partners__text-name {
        font-size: 19px;
    }
    .hm-partners__text-description {
        font-size: 15px;
    }
}

/* === Reduced motion =========================================== */

@media (prefers-reduced-motion: reduce) {
    .hm-partners__logo-link,
    .hm-partners__logo,
    .hm-partners__tooltip,
    .hm-partners__text-name,
    .hm-partners__text-description,
    .hm-partners__marquee-tile {
        transition: none;
    }

    .hm-partners__logo-link:hover,
    .hm-partners__logo-link:focus-visible {
        transform: none;
    }

    /* Marquee → static centered row. The track stops animating
     * and wraps. Mask-image off so the logos aren't clipped at
     * the edges. The duplicate group is hidden so we don't show
     * each logo twice. */
    .hm-partners__marquee {
        overflow: visible;
        -webkit-mask-image: none;
                mask-image: none;
    }

    .hm-partners__track {
        animation: none;
        flex-wrap: wrap;
        justify-content: center;
        width: 100%;
    }

    .hm-partners__marquee-group--clone {
        display: none;
    }
}

/* === Narrow phones (≤ 480px) ================================== */

@media (max-width: 480px) {
    .hm-partners {
        padding: 48px 16px 40px;
    }
    .hm-partners__header {
        margin-bottom: 28px;
    }
    .hm-partners__marquee-tile {
        width: 96px;
        height: 96px;
        padding: 12px;
    }
    .hm-partners__text-display {
        padding-left: 16px;
        padding-right: 16px;
    }
}

/* ============================================================================
 * === Product faves heart (Faves UI) =========================================
 * Heart toggle on aisle cards (bottom row, left, opposite the price) and the
 * PDP (top-right of the hero image). State + toggle handled by
 * product-faves.js; favorited fill is driven by [aria-pressed="true"] /
 * .hm-fave--active. Reuses the vendor-favorite heart path for visual parity.
 * ========================================================================= */

.hm-fave {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* anchor for the .hm-fave__lottie overlay (style polish) */
    position: relative;
    border: 0;
    background: transparent;
    /* padding expands the tap target around the ~22px icon; negative
     * margin keeps the layout box tight so it doesn't push the price. */
    padding: 6px;
    margin: -6px;
    cursor: pointer;
    color: rgba(45, 62, 16, 0.5);   /* inactive: muted leafy outline */
    line-height: 0;
    -webkit-tap-highlight-color: transparent;
}

.hm-fave__icon {
    width: 22px;
    height: 22px;
    display: block;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    /* round joins/caps so the outline (inactive) heart has a clean,
     * classic silhouette — no sharp notch at the top cleft / bottom tip. */
    stroke-linejoin: round;
    stroke-linecap: round;
    pointer-events: none;
    transition: fill 160ms ease, stroke 160ms ease, color 160ms ease,
                transform 160ms ease, filter 160ms ease;
}

.hm-fave:hover,
.hm-fave:focus-visible {
    color: #e6446b;   /* warm rose preview of the favorited color on hover */
    outline: none;
}
.hm-fave:hover .hm-fave__icon,
.hm-fave:focus-visible .hm-fave__icon {
    transform: scale(1.12);
    /* gold spotlight — the addon's established "you're interacting with
     * this surface" cue (§46/§48.5). */
    filter: drop-shadow(0 0 6px rgba(240, 160, 32, 0.45));
}
.hm-fave:active .hm-fave__icon {
    transform: scale(0.9);
}

/* Favorited — filled warm rose, legible against the green/cream surface. */
.hm-fave[aria-pressed="true"],
.hm-fave--active {
    color: #e6446b;
}
.hm-fave[aria-pressed="true"] .hm-fave__icon,
.hm-fave--active .hm-fave__icon {
    fill: currentColor;
    stroke: currentColor;
}

/* Keyboard ring drawn on the icon so it traces the heart's box. */
.hm-fave:focus-visible .hm-fave__icon {
    outline: 2px solid var(--hm-color-green-dark, #21703b);
    outline-offset: 3px;
    border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .hm-fave__icon { transition: fill 160ms ease, color 160ms ease; }
    .hm-fave:hover .hm-fave__icon,
    .hm-fave:focus-visible .hm-fave__icon,
    .hm-fave:active .hm-fave__icon { transform: none; filter: none; }
}

/* --- Heart Lottie overlay (style polish) -------------------------------
 * product-faves.js mounts the recoloured `Heart Like` Lottie here when a
 * heart is favorited (plays 0→55, holds; plays 55→111 on un-favorite, then
 * is torn down). The `--lottie` class — added by JS only AFTER the Lottie
 * mounts — hides the outline SVG and reveals the overlay. If Lottie can't
 * load (CDN/reduced-motion), `--lottie` is never added and the filled-SVG
 * `--active` state stays as the favorited look (graceful degradation).
 *
 * The overlay is a touch larger than the 22px icon and centred on it so the
 * "like" burst has room to breathe; the heart inside still reads at icon
 * scale. pointer-events:none — the button itself takes the click. */
.hm-fave__lottie {
    display: none;
    position: absolute;
    top: 50%;
    left: 50%;
    /* ~2× the 22px icon so the filled heart reads at full weight and the
     * "like" burst has room (it overflows the button — pointer-events:none
     * keeps the click on the button). Centred on the icon. */
    width: 60px;
    height: 60px;
    transform: translate(-50%, -50%);
    pointer-events: none;
}
.hm-fave__lottie svg { display: block; }

.hm-fave--lottie .hm-fave__icon {
    /* keep the layout box (button sizing/centering) but hide the outline
     * while the Lottie is the visible heart */
    visibility: hidden;
}
.hm-fave--lottie .hm-fave__lottie {
    display: block;
}

/* --- Add-to-bag firework (style polish) --------------------------------
 * celebrate.js appends one of these to <body> per add, sized ~2.2× the bag
 * button, its canvas centred on the button's (0,0) corner (inline
 * left/top/size set by JS off the button's viewport rect). position:fixed
 * (NOT absolute-in-card) so the burst overflows freely — the card and
 * carousel track both clip overflow. Self-destructs on the Lottie's
 * 'complete'; never intercepts a tap. */
.hm-firework {
    position: fixed;
    z-index: 9998;
    pointer-events: none;
}
.hm-firework svg { display: block; }

/* --- Aisle card: footer row (heart left, price right) ------------------- */
.hm-aisle-card__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: auto;   /* pin to the bottom of the flex-column body */
}
.hm-aisle-card__fave {
    flex-shrink: 0;
}
/* Price stays left (its long-standing position); the heart sits at the
 * right of the footer row, opposite the price. `space-between` on the
 * footer pushes them apart. */

/* --- PDP: top-right of the hero image ---------------------------------- */
.hm-product__hero-image {
    position: relative;   /* positioning context for the heart */
}
.hm-product__fave {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 2;
    /* circular cream chip so the heart reads over any product image,
     * matching the bag-button treatment on cards (§48). */
    width: 44px;
    height: 44px;
    padding: 0;
    margin: 0;
    border-radius: 50%;
    background: rgba(241, 235, 191, 0.85);
    box-shadow: 0 2px 8px rgba(45, 62, 16, 0.18);
}
.hm-product__fave:hover,
.hm-product__fave:focus-visible {
    background: rgba(241, 235, 191, 1);
}
.hm-product__fave .hm-fave__icon {
    width: 24px;
    height: 24px;
}

