/** Shopify CDN: Minification failed

Line 15:20 Unexpected "*"

**/
/* ===== Bean & Bean DS v1.0 — Wave 2 surface-adoption layer (dsv2.css) =====
   Loaded after theme.css and dsv1.css. dsv1.css is the FROZEN primitives
   contract (.bb-*) as shipped 2026-06-12 — never edit it; this file applies
   those patterns to theme trunk surfaces.

   Conventions:
   - Each W2 block below is delimited, self-contained and order-independent.
     Packages append blocks; never modify another package's block.
   - Tokens only (--bb-*, --fg-*, --bg-*, --font-*, --shadow-*, --space-*,
     --dur-*/--ease-*, --weight-*, --tracking-*). No raw color values.
   - High-traffic surfaces launched gated behind per-surface body classes
     (.ds-w2-*/.ds-w3-*) — retired to base by D-1 (2026-07); Wave 4/5
     surfaces (.ds-w4-*/.ds-w5-*) remain gated until their own soak ends
     (same DS-05 pattern: flags default OFF, flips in the theme editor).
     Ungated blocks are marked UNGATED and take effect on merge.
   - The minified theme.css / theme.js are never edited. JS workarounds
     live in assets/custom.js (none required by Wave 2 so far). */

/* === W2-01 — UNGATED brand-correctness fixes ============================
   (a) Theme .btn focus ring → .bb-btn purple halo (Wave 1 carry-over).
   (b) Hero headline weight rule (decision memo 1): hero headlines are
       always Sofia Pro Black, regardless of the richtext heading level
       used to size them. */

/* (a) Replace the browser-default blue focus ring on theme buttons with
   the dsv1 .bb-btn halo. The 1px purple-500 ring carries the >= 3:1
   non-text contrast on the light token backgrounds (#FBFBFE / #F1F2F8
   via --bb-paper / --bb-cream); the purple-100 glow provides the brand
   read on dark and brand-purple surfaces. Matches dsv1 .bb-btn exactly. */
.btn:focus-visible,
.btn--quick:focus-visible,
.shopify-payment-button__button--unbranded:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--bb-purple-100), 0 0 0 1px var(--bb-purple-500);
}

/* box-shadow is stripped in Windows High Contrast / forced-colors mode;
   a transparent outline is re-drawn by the system there, so keyboard
   users never lose the indicator. */
@media (forced-colors: active) {
  .btn:focus-visible,
  .btn--quick:focus-visible,
  .shopify-payment-button__button--unbranded:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* Product-card keyboard focus (ungated a11y). The card's tab stop is the small
   title/price link (the image link is tabindex=-1 + aria-hidden), so a halo on
   the title alone is easy to miss against the prominent card art. Ring the whole
   card image box instead, using :has(:focus-visible) so it only shows on keyboard
   focus of any card link. The 4px white gap separates the ring from the card
   art's own purple border so the focus state reads as a distinct frame, not a
   thicker border. The title/price halo (theme.css) stays as a secondary cue. */
.product-grid-item:has(:focus-visible) .product-grid-item__image {
  box-shadow: 0 0 0 4px var(--bb-white, #fff), 0 0 0 8px var(--bb-purple-500);
}
@media (forced-colors: active) {
  .product-grid-item:has(:focus-visible) .product-grid-item__image {
    outline: 3px solid Highlight;
    outline-offset: 4px;
  }
}

/* (b) Hero headline weight — decision memo 1 (force Black 900).
   The slideshow title is a richtext field; picking "Heading 2/3" to get
   a smaller size also changes the wrapper tag, so dsv1's h2/h3 -> 700
   rule wins over the intended 900. Weight must not be a side effect of
   the size control. The wrapper always carries .slide__heading whatever
   tag the richtext produces; descendant selectors cover the stray-tag
   DOM variant (mixed tags unify under a <div> wrapper). */
.slide__heading,
.slide__heading h1, .slide__heading h2, .slide__heading h3,
.slide__heading h4, .slide__heading h5, .slide__heading h6 {
  font-weight: var(--weight-black);
  letter-spacing: var(--tracking-tight);
}

/* === W2-02 — newsletter + theme forms adopt .bb-field (BASE since D-1 batch 1)
   Surfaces: newsletter signup (footer block, newsletter section, popup),
   contact form, header search field. Visual contract mirrors dsv1
   .bb-field: paper fill, hairline border, pill radius, purple focus halo.
   Font-size is deliberately NOT set (theme keeps its 16px+ input sizing,
   which prevents iOS focus zoom). Colors work on dark footers because the
   field surface is always paper. */
.input-group__field,
.search-form__input,
.input--full {
  color: var(--fg-1);
  background: var(--bb-paper);
  border: 1px solid var(--bb-line-2);
  border-radius: var(--bb-radius-pill);
  transition: border-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}
.input-group__field::placeholder,
.search-form__input::placeholder,
.input--full::placeholder {
  color: var(--fg-3);
}
.input-group__field:focus,
.search-form__input:focus,
.input--full:focus {
  outline: none;
  border-color: var(--bb-purple-500);
  box-shadow: 0 0 0 3px var(--bb-purple-100);
}

/* Multi-line fields keep a soft rectangle — pill radius distorts
   textareas (contact form message field). */
textarea.input--full {
  border-radius: var(--bb-radius-md);
}

/* Newsletter inline arrow submit (icon-only variant): subtle nudge on
   hover + the dsv1 halo on keyboard focus. */
.input-group__btn--icon {
  transition: transform var(--dur-base) var(--ease-out);
}
.input-group__btn--icon:hover {
  transform: translateX(2px);
}
.input-group__btn:focus-visible {
  outline: none;
  border-radius: var(--bb-radius-pill);
  box-shadow: 0 0 0 3px var(--bb-purple-100), 0 0 0 1px var(--bb-purple-500);
}

@media (prefers-reduced-motion: reduce) {
  .input-group__btn--icon:hover {
    transform: none;
  }
}
@media (forced-colors: active) {
  .input-group__field:focus,
  .search-form__input:focus,
  .input--full:focus,
  .input-group__btn:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* === W2-03 — footer (BASE since D-1 batch 1) ================================
   Footer background / text / border colors stay admin-driven (section
   settings; currently legacy purple #664fff + white — recommended flip to
   brand purple is an admin action, see package notes). This block
   restyles type and interaction only, with background-agnostic
   treatments (currentColor, underlines, opacity) so it is correct both
   before and after the color flip. */

/* Column titles become the DS eyebrow voice (Inter caps), replacing the
   display-face h5 — column labels are wayfinding, not display copy. */
.footer-title,
.footer-title__button {
  font-family: var(--font-body);
  font-size: var(--bb-text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
}

/* Links: underline-on-hover reads on any footer background; the theme's
   alpha-fade hover stays as a secondary cue. */
.footer-link:hover,
.footer-copyright a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Keyboard focus: currentColor ring is visible on any admin-chosen
   background (white-on-purple today, ink-on-cream if flipped later). */
.footer-link:focus-visible,
.footer-title__button:focus-visible,
.footer-copyright a:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  border-radius: var(--bb-radius-xs);
}

/* Copyright line in the mono accent voice (approved subheading accent). */
.footer-copyright {
  font-family: var(--font-mono);
  font-size: var(--bb-text-2xs);
  letter-spacing: 0.02em;
}

/* === W2-04 — product cards + collection grid (BASE since D-1 batch 2) =======
   The media wrapper becomes a .bb-card surface (paper, hairline,
   radius-lg, shadow lift on hover); info typography aligns to the DS
   card spec. Grid layout, AOS attributes, quick-buy DOM and swatch
   behavior are untouched. */
.product-grid-item__image {
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-lg);
  box-shadow: var(--shadow-xs);
  overflow: hidden;
  transition: box-shadow var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}
.product-grid-item:hover .product-grid-item__image,
.product-grid-item:focus-within .product-grid-item__image {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}
/* When the cards flag is ON, the elevation rule above (specificity 0,4,0) would
   otherwise beat the ungated card focus ring (0,3,0) and replace it with the
   hover shadow on keyboard focus. Re-assert the ring at matching specificity,
   keyboard-only, so the focus indicator survives the Wave 2 cards flag. */
.product-grid-item:has(:focus-visible) .product-grid-item__image {
  box-shadow: 0 0 0 4px var(--bb-white, #fff), 0 0 0 8px var(--bb-purple-500);
}

/* Card title: DS UI voice (Inter semibold). NOTE: this is the one
   intentionally visible type change in Wave 2 — card titles currently
   inherit the Sofia Pro heading stack. Single rule; delete if Jiyoon
   prefers display-face titles on cards (open question in the morning
   report). */
.product-grid-item__title {
  font-family: var(--font-body);
  font-weight: var(--weight-semibold);
  color: var(--fg-1);
}

.product-grid-item__price {
  color: var(--fg-3);
  font-variant-numeric: tabular-nums;
}

/* Tagline / cutline: supporting size (color is set inline by the theme
   from the cutline_color setting, so it is intentionally not overridden
   here). */
.product-cutline {
  font-size: var(--bb-text-sm);
}

/* Badges become DS pills; badge colors stay admin-driven. */
.product__badge__item {
  font-family: var(--font-body);
  font-size: var(--bb-text-2xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  border-radius: var(--bb-radius-pill);
  padding: 4px 10px;
}

@media (prefers-reduced-motion: reduce) {
  .product-grid-item:hover .product-grid-item__image,
  .product-grid-item:focus-within .product-grid-item__image {
    transform: none;
  }
}

/* === W2-05 — cart drawer (BASE since D-1 batch 2) =============================
   Drawer surface goes paper with the DS elevation; free-shipping meter,
   line items, note fields and the checkout CTA adopt token styling.
   Drawer open/close mechanics, focus trap (Phase 1) and cart JS are
   untouched. (.cart-drawer__close already has the Wave 1 purple outline
   in theme.css — not re-declared here.) */
.cart-drawer {
  background-color: var(--bb-paper);
  box-shadow: var(--shadow-lg);
}

/* Item count beside the title: mono accent voice. */
.cart-drawer__item-qty {
  font-family: var(--font-mono);
  font-size: var(--bb-text-xs);
  letter-spacing: 0.02em;
  color: var(--fg-3);
}

/* Free-shipping progress meter: brand fill on soft-purple track.
   `color` styles the bar in Firefox fallback; vendor pseudo-elements
   cover WebKit and Gecko explicitly. */
.cart__message__progress {
  color: var(--bb-purple-500);
  background-color: var(--bb-purple-50);
  border-radius: var(--bb-radius-pill);
}
.cart__message__progress::-webkit-progress-bar {
  background-color: var(--bb-purple-50);
  border-radius: var(--bb-radius-pill);
}
.cart__message__progress::-webkit-progress-value {
  background-color: var(--bb-purple-500);
  border-radius: var(--bb-radius-pill);
}
.cart__message__progress::-moz-progress-bar {
  background-color: var(--bb-purple-500);
  border-radius: var(--bb-radius-pill);
}
.cart__message.is-success {
  color: var(--bb-success);
}

/* Line items */
.cart__item__title a {
  color: var(--fg-1);
}
.cart__item__price {
  font-variant-numeric: tabular-nums;
}
.cart__item__selected-options,
.cart__item__selling-plan {
  color: var(--fg-3);
}
.cart__item__remove:hover {
  color: var(--bb-danger);
}
.cart__item__quantity-minus:focus-visible,
.cart__item__quantity-plus:focus-visible,
.cart__item__quantity-field:focus-visible,
.cart__item__remove:focus-visible {
  outline: 2px solid var(--bb-purple-500);
  outline-offset: 2px;
  border-radius: var(--bb-radius-xs);
}

/* Cart note / gift note fields: .bb-field treatment, soft rectangle. */
.cart__field {
  color: var(--fg-1);
  background: var(--bb-paper);
  border: 1px solid var(--bb-line-2);
  border-radius: var(--bb-radius-md);
  transition: border-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}
.cart__field:focus {
  outline: none;
  border-color: var(--bb-purple-500);
  box-shadow: 0 0 0 3px var(--bb-purple-100);
}

/* Checkout CTA: dsv1 .bb-btn--primary hover behavior (lift + brand
   shadow). Color/size stay admin-driven (already brand purple). */
.cart__checkout {
  transition: transform var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out),
    background var(--dur-base) var(--ease-out);
}
.cart__checkout:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-brand);
}

@media (prefers-reduced-motion: reduce) {
  .cart__checkout:hover {
    transform: none;
  }
}
@media (forced-colors: active) {
  .cart__field:focus {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* === W2-06 — header & navigation (BASE since D-1 batch 1) ===================
   Live header is solid (non-transparent, non-sticky, dark text on token
   background, "line" hover effect). This block brands the hover/active
   line and cart indicator, and gives keyboard focus a background-proof
   double ring (white core + purple) so nothing regresses if transparent
   headers over imagery are ever re-enabled. The "Sale" highlight item
   keeps its own --highlight color (theme rule is !important). */

/* Hover / active line under nav links → brand purple, slightly thicker,
   pill-capped. Covers top-level links, icon links and mega-menu child
   links (the three theme variants of the line effect). */
.navlink--toplevel > .navtext::before,
.navlink--toplevel > .navtext::after,
.navlink--icon .navtext::before,
.navlink--icon .navtext::after,
.grandparent .navlink--child > .navtext::before,
.grandparent .navlink--child > .navtext::after {
  background: var(--bb-purple-500);
  height: 2px;
  border-radius: var(--bb-radius-pill);
}

/* Keyboard focus: double ring reads on light header, dark imagery and
   brand surfaces alike. */
.navlink:focus-visible,
.mobile-menu__button:focus-visible,
.logo__image-link:focus-visible,
.logo__text-link:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bb-white), 0 0 0 4px var(--bb-purple-500);
  border-radius: var(--bb-radius-xs);
}

/* Cart "has items" indicator: pinned to the brand token (was --accent,
   same value post-DS-05 — pinning prevents drift if the admin accent
   ever changes). */
.cart-indicator {
  fill: var(--bb-purple-500);
}

/* Sticky/scrolled state (dormant while sticky_header is off): soft DS
   elevation instead of a hard edge. Both selectors cover the scroll
   class living on <html> or <body>. */
.has-scrolled .site-header,
.has-scrolled .site-header {
  box-shadow: var(--shadow-sm);
}

@media (forced-colors: active) {
  .navlink:focus-visible,
  .mobile-menu__button:focus-visible,
  .logo__image-link:focus-visible,
  .logo__text-link:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* === W2-07 — PDP buy box (BASE since D-1 batch 2) ===========================
   Price, variant pickers (radio boxes + popout selects), quantity and
   the add-to-cart CTA adopt token styling. Cart/variant JS, form markup
   and the Recharge subscription widget (app block, themed in its own
   admin settings) are untouched. */

/* Price: tabular numerals; sale treatments in the DS berry accent.
   --bb-berry on --bb-paper is 5.6:1 (AA). */
.product__price {
  font-variant-numeric: tabular-nums;
}
.product__price--sale {
  color: var(--bb-berry);
}
.product__price--compare {
  color: var(--fg-4);
}
.product__price--off {
  font-family: var(--font-mono);
  font-size: var(--bb-text-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--bb-berry);
}

/* Variant boxes (radio-style pickers): paper pills, purple selected
   state. Selected text purple-700 on purple-50 is 8.3:1 (AA). */
.radio__fieldset .radio__label {
  background-color: var(--bb-paper);
  border-color: var(--bb-line-2);
  border-radius: var(--bb-radius-pill);
  transition: color var(--dur-base) var(--ease-out),
    background var(--dur-base) var(--ease-out),
    border var(--dur-base) var(--ease-out);
}
.radio__fieldset .radio__label:hover {
  border-color: var(--bb-purple-300);
}
.radio__fieldset .radio__input:checked ~ .radio__label {
  background-color: var(--bb-purple-50);
  border-color: var(--bb-purple-500);
  color: var(--bb-purple-700);
}
/* Image-option tiles and the stacked "coffee-radio" rows keep a soft
   rectangle — pill radius clips imagery / reads wrong on full-width rows. */
.radio__fieldset--variant-option-image .radio__label,
.coffee-radio .radio__label {
  border-radius: var(--bb-radius-md);
}

/* Dropdown selectors (popout) */
.select-popout__toggle {
  color: var(--fg-1);
  background: var(--bb-paper);
  border: 1px solid var(--bb-line-2);
  border-radius: var(--bb-radius-pill);
  transition: border-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}
.select-popout__toggle:focus-visible,
.select-popout__toggle[aria-expanded="true"] {
  outline: none;
  border-color: var(--bb-purple-500);
  box-shadow: 0 0 0 3px var(--bb-purple-100);
}
.select-popout__list {
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-md);
  box-shadow: var(--shadow-md);
}
.select-popout__option:hover,
.select-popout__option:focus-visible {
  background: var(--bb-purple-50);
}
.select-popout__item--current .select-popout__option {
  color: var(--bb-purple-700);
}

/* Quantity stepper */
.quantity__btn:focus-visible,
.quantity__input:focus-visible {
  outline: 2px solid var(--bb-purple-500);
  outline-offset: -2px;
}

/* Add to cart: dsv1 .bb-btn--primary hover (lift + brand shadow) and
   disabled (cream-2 / ink-4) states. Base color/size stay admin-driven. */
.product__submit__add {
  transition: transform var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out),
    background var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out);
}
.product__submit__add:hover:not([disabled]) {
  transform: translateY(-1px);
  box-shadow: var(--shadow-brand);
}
.product__submit__add[disabled] {
  background: var(--bb-cream-2);
  color: var(--bb-ink-4);
  box-shadow: none;
  transform: none;
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  .product__submit__add:hover:not([disabled]) {
    transform: none;
  }
}
@media (forced-colors: active) {
  .select-popout__toggle:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* === W2-07b — PDP price-off badge surface (BASE since D-1 batch 2) ==========
   The "Subscription • Save 10%" / "Sale • Save X" pill next to the PDP
   price. The theme fills it from JS (updateSaleTextSubscription) whenever
   a selling plan is active — the new Recharge widget selects a plan via
   the form's selling_plan input, so this badge now appears on every
   subscribe-selected PDP view.

   Its surface comes from --saving-bg / --saving-text → the admin
   "Saving badge" colors (settings_data: legacy #d02e2e red). W2-07 set
   the badge TEXT to berry but left that red background, which would ship
   berry-on-red once the flag flips. Resolve the surface here: brand
   purple pill, matching the Recharge widget's own "Save up to 10%"
   badge. White on purple-500 is 5.6:1 (AA at the badge's 12px size,
   same pairing as the ATC button). Cascade note: this block is later in
   the file than W2-07 at equal specificity, so its color declaration
   intentionally wins over W2-07's berry. */
.product__price--off {
  background-color: var(--bg-brand);
  color: var(--fg-on-brand);
}
/* The bullet separator inherits a 0.5 opacity from theme.css — too dim
   on the solid purple surface. */
.product__price--off em {
  opacity: 0.7;
}

/* === W3-01 — nav drawer + megamenu interior (BASE since D-1 batch 3) ===========
   W2-06 branded the header bar (hover lines, focus rings, cart dot) but
   deliberately stopped at the two navigation interiors. This block does
   them: the megamenu panel (.header__dropdown, desktop) and the mobile
   nav drawer (.drawer--nav). Focus conventions follow W2-06/#62: rings
   that survive any surface, System Colors under forced-colors. Both
   surfaces scroll or clip (drawer has overflow hidden), so in-flow
   focus indicators here are inside-drawn outlines, not outer rings. */

/* -- Megamenu panel (desktop) -------------------------------------------- */
/* Panel surface: paper instead of the theme --bg, closed with a hairline
   and soft DS elevation so it reads as a card over the page, matching
   the cart drawer (W2-05). */
.header__dropdown {
  background: var(--bb-paper);
  border-bottom: 1px solid var(--bb-line);
  box-shadow: var(--shadow-md);
}

/* Link hierarchy: child columns keep full ink; grandchild (leaf) links
   step back to fg-2 and answer hover/keyboard with brand purple. The
   admin "Sale" highlight item keeps its own --highlight (theme rule is
   !important, same note as W2-06). */
.header__dropdown .navlink--grandchild {
  color: var(--fg-2);
  transition: color var(--dur-fast) var(--ease-out);
}
.header__dropdown .navlink--grandchild:hover,
.header__dropdown .navlink--grandchild:focus-visible,
.header__dropdown .navlink--active {
  color: var(--bb-purple-600);
}

/* -- Mobile nav drawer ---------------------------------------------------- */
/* Drawer surface: paper + ink, header sealed with a hairline. The theme
   paints the drawer from the header's admin colors (--bg/--text); the
   drawer is a DS surface now, so pin it to tokens. */
.drawer--nav {
  background-color: var(--bb-paper);
  color: var(--fg-1);
}
.drawer--nav .drawer__header {
  border-bottom: 1px solid var(--bb-line);
}

/* Level hierarchy mirrors the megamenu: top level in full ink, deeper
   levels step back, active answers in purple. Highlight item untouched. */
.drawer--nav .mobile-navlink--level-2,
.drawer--nav .mobile-navlink--level-3,
.drawer--nav .mobile-navlink--small {
  color: var(--fg-2);
  transition: color var(--dur-fast) var(--ease-out);
}
.drawer--nav .mobile-navlink:hover {
  color: var(--bb-purple-600);
}
.drawer--nav .mobile-menu__item--active > .mobile-navlink {
  color: var(--bb-purple-600);
}

/* Sub-level containers: a brand-tinted left rule makes depth legible
   without the theme's font-size shrink doing all the work. */
.drawer--nav .mobile-nav__sublist,
.drawer--nav .mobile-nav__sub-sublist {
  border-left: 2px solid var(--bb-purple-100);
}

/* Keyboard focus: links and +/- triggers span the drawer's full width
   against its overflow-hidden edge, so an outer box-shadow ring would
   clip. Inside-drawn outline instead — visible on paper, no clipping,
   and it renders under forced-colors without a fallback block. */
.drawer--nav .mobile-navlink:focus-visible,
.drawer--nav .mobile-nav__trigger:focus-visible {
  outline: 2px solid var(--bb-purple-500);
  outline-offset: -2px;
  border-radius: var(--bb-radius-xs);
}

/* Close button: same double ring as the W2-06 header icons (it floats
   clear of the drawer edge, so the outer ring fits). */
.drawer--nav .drawer__close-button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bb-white), 0 0 0 4px var(--bb-purple-500);
  border-radius: var(--bb-radius-xs);
}

@media (forced-colors: active) {
  .drawer--nav .drawer__close-button:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* === W3-02 — search + collection filters (BASE since D-1 batch 3) ===========
   Predictive search popdown, popular searches, and the collection
   filters sidebar/pills. The search FIELD itself is already W2-02
   territory (.search-form__input adopts .bb-field there) — this block
   never touches it, only the chrome around it. Focus conventions per
   W3-01: inside-drawn outlines inside scroll containers, double ring on
   floating controls, System Colors under forced-colors. */

/* -- Predictive popdown + popular searches -------------------------------- */
/* Group headings ("Products", "Suggestions", popular searches) read as
   DS eyebrows over a token hairline instead of label-size body text. */
.predictive-search__heading {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--bb-text-xs);
  color: var(--fg-2);
  border-bottom-color: var(--bb-line);
}

/* Result rows and popular-search links answer hover/keyboard in brand
   purple; the popdown scrolls, so keyboard focus is an inside-drawn
   outline (W3-01 rationale). */
.predictive-search__link,
.popular-searches a {
  transition: color var(--dur-fast) var(--ease-out);
}
.predictive-search__link:hover,
.predictive-search__link:focus-visible,
.popular-searches a:hover,
.popular-searches a:focus-visible {
  color: var(--bb-purple-600);
}
.predictive-search__link:focus-visible,
.popular-searches a:focus-visible {
  outline: 2px solid var(--bb-purple-500);
  outline-offset: -2px;
  border-radius: var(--bb-radius-xs);
}

/* Submit (magnifier) and close: floating controls clear of any clipping
   edge, so they take the W2-02 pill halo / W2-06 double ring. */
.search-form__submit:focus-visible {
  outline: none;
  border-radius: var(--bb-radius-pill);
  box-shadow: 0 0 0 3px var(--bb-purple-100), 0 0 0 1px var(--bb-purple-500);
}
.search-popdown__close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bb-white), 0 0 0 4px var(--bb-purple-500);
  border-radius: var(--bb-radius-xs);
}

/* -- Collection filters ---------------------------------------------------- */
/* Checkbox tags: unchecked boxes soften from full ink to fg-3; checked
   fills brand purple (theme paints the check glyph via --icon-check,
   which stays white). Disabled state keeps the theme's grey. */
.collection__filter-tag--default .filter__button:before {
  border-color: var(--fg-3);
  transition: border-color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}
.collection__filter-tag--default .filter__button:hover:before {
  border-color: var(--bb-purple-500);
}
.collection__filter-tag--default .filter__input:checked ~ .filter__button:before,
.collection__filter-tag--default.is-active .filter__button:before {
  background-color: var(--bb-purple-500);
  border-color: var(--bb-purple-500);
}

/* Keyboard focus on a tag: the input is visually hidden and the label
   carries the state; sidebar can render as a drawer, so inside-drawn. */
.filter__input:focus-visible ~ .filter__button {
  outline: 2px solid var(--bb-purple-500);
  outline-offset: -2px;
  border-radius: var(--bb-radius-xs);
}

/* Reset / clear-all actions become brand links (underline kept — color
   is not the only affordance). */
.collection__filters__reset,
.collection__filters__clear {
  color: var(--bb-purple-600);
}

@media (forced-colors: active) {
  .search-form__submit:focus-visible,
  .search-popdown__close:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* === W3-05 — slider / flickity chrome ====================================
   PART A is UNGATED (a11y, same reasoning as W2-01's button halo): the
   theme ships no visible :focus-visible style on Flickity's controls.
   PART B (BASE since D-1 batch 3) brands the arrows + page dots. Flickity
   class names are vendor-stable (bundled in vendor.js); theme.css already
   restyles .flickity-button (translucent white) and .flickity-page-dots
   .dot (bordered, transparent) — Part B overrides both at higher
   specificity. Slider JS/options untouched. */

/* --- Part A — UNGATED: focus ring on carousel controls --- */
.flickity-button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bb-white), 0 0 0 4px var(--bb-purple-500);
}
@media (forced-colors: active) {
  .flickity-button:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* --- Part B — GATED: arrow + dot branding --- */
.flickity-button {
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  color: var(--fg-1);
  box-shadow: var(--shadow-sm);
  transition: background var(--dur-base) var(--ease-out);
}
.flickity-button:hover { background: var(--bb-purple-50); }
.flickity-button:disabled { opacity: 0.35; }
.flickity-button-icon { fill: currentColor; }

/* theme.css draws dots as bordered transparent pills — flatten to filled
   token dots (border-color must be cleared along with background). */
.flickity-page-dots .dot {
  background: var(--bb-line-2);
  border-color: transparent;
  opacity: 1;
}
.flickity-page-dots .dot.is-selected {
  background: var(--bb-purple-500);
}
@media (prefers-reduced-motion: reduce) {
  .flickity-button { transition: none; }
}

/* === W3-07 — customer account pages (BASE since D-1 batch 3) ===============
   login / register / account / addresses / order detail / activate / reset.
   All templates share the .page-customer wrapper (this theme has no
   .customer/.account-box — real hooks are .account__heading, .table-wrap
   table.full, .address, .account__table__item). Fields inherit .bb-field
   (W2-02), buttons inherit W2-01's ungated halo; this block does headings,
   the orders tables, the card surfaces, and link color. No account JS. */

/* Page headings → display face */
.page-customer .section__heading,
.page-customer .account__heading {
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  color: var(--fg-1);
}

/* Orders tables (account list + order detail are <table class="full">
   inside .table-wrap) → eyebrow header row, hairline body rows, tabular
   figures so totals and dates column-align */
.page-customer .table-wrap table.full th {
  font-family: var(--font-body);
  font-size: var(--bb-text-2xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--fg-3);
  border-bottom: 1px solid var(--bb-line);
}
.page-customer .table-wrap table.full td {
  border-bottom: 1px solid var(--bb-line);
  font-variant-numeric: tabular-nums;
  color: var(--fg-2);
}

/* Address cards + the two account boxes (orders / account details) →
   paper surface */
.page-customer .address,
.page-customer .account__table__item {
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-lg);
  box-shadow: var(--shadow-xs);
  padding: var(--space-5);
}

/* Text links → brand purple. Buttons keep their own treatment; .light-gray
   links stay muted (deliberate secondary voice in these templates). */
.page-customer a:not(.btn):not(.light-gray) {
  color: var(--bb-purple-600);
}

/* Keyboard focus: bg-proof double ring on template links */
.page-customer a:not(.btn):focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bb-white), 0 0 0 4px var(--bb-purple-500);
  border-radius: var(--bb-radius-xs);
}
@media (forced-colors: active) {
  .page-customer a:not(.btn):focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* === W3-08 — cart page / non-drawer /cart (BASE since D-1 batch 3) =========
   The page renders the SAME snippets/cart-line-items.liquid as the drawer,
   and W2-05's line-item / meter / field / checkout rules are unscoped, so
   with ds-w2-cart on, /cart already inherits all of them — the only
   drawer-scoped W2-05 rule is the .cart-drawer surface itself (verified).
   This block adds only page-only chrome. This theme has no .cart-page /
   .cart-totals / .cart__footer; real hooks are .cart__title, .cart__aside,
   .cart__total. Cart AJAX untouched. */

/* Page heading → display face (the item-count span inside keeps W2-05's
   mono treatment via .cart-drawer__item-qty, which the page reuses) */
.cart__title {
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  color: var(--fg-1);
}

/* Aside column (totals + checkout) → paper card, tabular money */
.cart__aside {
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5);
}
.cart__total {
  font-variant-numeric: tabular-nums;
  color: var(--fg-2);
}
.cart__total__money {
  color: var(--fg-1);
  font-weight: var(--weight-bold);
}

/* Empty-cart state uses .section__heading instead of .cart__title */
.cart .section__heading {
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  color: var(--fg-1);
}

/* === W3-06 — editorial + content sections (BASE since D-1 batch 3) =========
   Blog cards / article body / list-collections / testimonials / press.
   This theme's real classes are .blog__post-*, .article__content.rte,
   .list-collections__item*, .testimonial__*, .press__logo* (the spec's
   .blog-post-grid-item / .article__body / .logo-list names don't exist —
   verified). Section layout + AOS untouched. */

/* Article body rhythm: DS prose */
.article__content.rte {
  color: var(--fg-1);
  line-height: var(--lh-loose);
}
.article__content.rte h2,
.article__content.rte h3 {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
}
.article__content.rte a {
  color: var(--bb-purple-600);
  text-underline-offset: 2px;
}
.article__content.rte blockquote {
  border-left: 3px solid var(--bb-purple-300);
  color: var(--fg-2);
  font-style: italic;
}

/* Post meta (article page + blog-grid cards) → mono eyebrow voice.
   Color only on the article page (paper background); blog-grid cards
   overlay their content on the post image behind .blog__post-overlay,
   so card meta/titles inherit the theme's on-image color. */
.article__meta,
.blog__post-meta {
  font-family: var(--font-mono);
  font-size: var(--bb-text-2xs);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.article__meta { color: var(--fg-3); }

/* Post + collection titles → display face ONLY. Both card types overlay
   imagery; forcing ink broke contrast on photos (QA 2026-07-04). */
.blog__post-title-h2 {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
}
.list-collections__item__title {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
}

/* Testimonials → paper card, display quote, mono attribution */
.testimonial__item__inner {
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-lg);
  box-shadow: var(--shadow-sm);
}
.testimonial__heading,
.testimonial__text {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  color: var(--fg-1);
}
.testimonial__user__name,
.testimonial__subheading {
  font-family: var(--font-mono);
  font-size: var(--bb-text-2xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--fg-3);
}

/* Press logos: neutral wash so the row reads as one unit; full color on
   hover of the slide */
.press__logo-image {
  filter: grayscale(1);
  opacity: 0.7;
  transition: opacity var(--dur-base) var(--ease-out),
              filter var(--dur-base) var(--ease-out);
}
.press__logos-slide:hover .press__logo-image {
  filter: none;
  opacity: 1;
}

/* Post-link / article-link focus rings (bg-proof double ring) */
.blog__post-title-link:focus-visible,
.blog__post-excerpt-link:focus-visible,
.article__content.rte a:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bb-white), 0 0 0 4px var(--bb-purple-500);
  border-radius: var(--bb-radius-xs);
}
@media (prefers-reduced-motion: reduce) {
  .press__logo-image { transition: none; }
}
@media (forced-colors: active) {
  .blog__post-title-link:focus-visible,
  .blog__post-excerpt-link:focus-visible,
  .article__content.rte a:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* === W3-03 — PDP below the fold (BASE since D-1 batch 3) =======================
   Accordions + related/recently tabs + carousel scrollbar + Judge.me.
   Accordion/tabs/scrollbar selectors confirmed in-repo (section-accordions,
   product.liquid, related-products). Judge.me markup is injected at runtime
   by app blocks (templates/product.json), so the .jdgm-* rules target the
   app's stable public classes — if the app's own CSS outranks one, set that
   value in Judge.me admin (Settings → Customize) instead of escalating
   specificity here. Collapsible/tabs JS untouched. */

/* Accordion trigger: DS type + token rules + visible focus */
.accordion__item { border-bottom: 1px solid var(--bb-line); }
.accordion__item__title {
  font-family: var(--font-body);
  font-weight: var(--weight-semibold);
  color: var(--fg-1);
}
.accordion__item__title:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bb-white), 0 0 0 4px var(--bb-purple-500);
  border-radius: var(--bb-radius-xs);
}
.accordion__item__text.rte { color: var(--fg-2); }

/* Tabs nav (related / recently viewed): purple active underline */
.tabs__link {
  font-family: var(--font-body);
  font-weight: var(--weight-medium);
  color: var(--fg-3);
}
.tabs__link.current {
  color: var(--fg-1);
  box-shadow: inset 0 -2px 0 var(--bb-purple-500);
}
.tabs__link:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bb-white), 0 0 0 4px var(--bb-purple-500);
  border-radius: var(--bb-radius-xs);
}

/* Related-products heading rule + carousel scrollbar → tokens (scoped to
   .related-products so the same classes elsewhere stay untouched) */
.related-products .section__heading-line {
  background: var(--bb-line);
  border: 0;
}
.related-products .custom-scrollbar__thumb,
.custom-scrollbar__thumb {
  background: var(--bb-purple-300);
}

/* Judge.me reviews — honey stars (approved 2026-07-04: warm accent reads
   as rating convention; one-line swap to purple if reversed) */
.jdgm-star { color: var(--bb-honey); }
.jdgm-rev-widg__title,
.jdgm-histogram__title {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  color: var(--fg-1);
}
.jdgm-write-rev-link { color: var(--bb-purple-600); }
.jdgm-paginate__page.jdgm-curr-page {
  color: var(--bb-purple-700);
  font-weight: var(--weight-bold);
}

@media (forced-colors: active) {
  .accordion__item__title:focus-visible,
  .tabs__link:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
  .tabs__link.current { text-decoration: underline; }
}

/* === W3-04 — quick-view modal chrome (BASE since D-1 batch 3) ==================
   The quick-view renders the same snippets/product.liquid buy-box as the
   PDP (sections/api-quickview.liquid), so its interior inherits W2-07 +
   W2-04 once those flags are on — nothing duplicated here. The modal is
   PhotoSwipe-based; this theme has no .modal--quickview/.quick-view. Real
   hooks: panel .popup-quick-view__inner, close .popup-quick-view__close,
   scrim .pswp__bg (scoped below to the quick-view popup so the plain
   image-zoom lightbox keeps its default background — verify the scoping
   holds on the preview; theme.js applies the popup-quick-view class family
   when opening quick-views). Open/close + focus-trap JS untouched. */

/* Scrim: ink wash — only when the pswp root carries the quick-view class */
.pswp.popup-quick-view .pswp__bg,
.popup-quick-view .pswp__bg {
  background: color-mix(in srgb, var(--bb-ink) 55%, transparent);
}

/* Dialog panel → paper surface */
.popup-quick-view__inner {
  background: var(--bb-paper);
  border-radius: var(--bb-radius-lg);
  box-shadow: var(--shadow-lg);
}

/* Close button: token color, purple hover, bg-proof focus ring */
.popup-quick-view__close {
  color: var(--fg-2);
  border-radius: var(--bb-radius-pill);
}
.popup-quick-view__close:hover { color: var(--bb-purple-600); }
.popup-quick-view__close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bb-white), 0 0 0 4px var(--bb-purple-500);
}
@media (forced-colors: active) {
  .popup-quick-view__close:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* === W3-09 — 404 + password page (BASE since D-1 batch 3) ==================
   Approved Beanie surfaces (packaging / About / 404 / subscriber-drop —
   standing rule: never homepage hero or category headers). 404 markup
   confirmed (sections/404.liquid). The password page renders through
   layout/password.liquid, which this package also teaches to load
   dsv1/dsv2 and emit the flag body class (it previously loaded theme.css
   only). Type + surface only; no routing or password-gate logic. */

/* 404 — display headline, hairline rule, roomier stack */
#four-oh-four .wrapper { padding-block: var(--space-9); }
#four-oh-four h1 {
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: var(--bb-text-4xl);
  color: var(--fg-1);
}
#four-oh-four .section__heading-line {
  background: var(--bb-line);
  border: 0;
}
#four-oh-four p { color: var(--fg-2); }

/* Beanie slot — no-op until the Section C asset lands. Activate later by
   setting --beanie-404 to the beanie-404.svg asset url in a one-line
   theme.liquid style hook; while the var is unset this renders nothing
   (no broken-image icon), so the art can land without another CSS PR. */
#four-oh-four .wrapper::before {
  content: "";
  display: block;
  width: 180px;
  height: 180px;
  margin: 0 auto var(--space-5);
  background: var(--beanie-404, none) center / contain no-repeat;
}

/* Password / coming-soon page — brand surface + paper form cards.
   (.password-page/.password__heading don't exist in this theme; real
   wrappers are .password-page__wrapper/__inner, headings are an unclassed
   h1/h3, and the two toggled forms live in #AdminLogin / #CustomerSignup.) */
.password-page__wrapper {
  background: var(--bb-cream);
  color: var(--fg-1);
}
.password-page__inner h1,
.password-page__inner h3 {
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  color: var(--fg-1);
}
.password-page__inner #AdminLogin,
.password-page__inner #CustomerSignup {
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6);
}
/* Password + signup inputs → .bb-field voice (W2-02 gates on ds-w2-forms,
   whose body class the password layout doesn't emit — so restated here
   under this package's own gate) */
.password-input,
.signup-input {
  background: var(--bb-white);
  border: 1px solid var(--bb-line-2);
  border-radius: var(--bb-radius-pill);
  color: var(--fg-1);
}

/* === FIX — footer cross-border selector (BASE since D-1 batch 1) ============
   W2-07's .select-popout rules used to gate on the ds-w2-buybox BODY class
   (base since D-1 batch 2), and the paper pill skin leaked into the
   footer's country/language selector,
   where a white pill fights the brand-purple footer. Footer treatments
   are background-agnostic by W2-03's convention (currentColor over the
   admin-driven colors), so the toggle blends with any footer background;
   the floating option list stays paper for readability. Post-D-1 both rules
   are unprefixed: this one is (0,2,0) vs the buy-box (0,1,0) — wins on
   specificity outright. */
.site-footer .select-popout__toggle {
  background: transparent;
  color: inherit;
  border: 1px solid color-mix(in srgb, currentColor 45%, transparent);
  border-radius: var(--bb-radius-pill);
}
.site-footer .select-popout__toggle:hover {
  border-color: color-mix(in srgb, currentColor 75%, transparent);
}
.site-footer .select-popout__toggle:focus-visible,
.site-footer .select-popout__toggle[aria-expanded="true"] {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  border-color: color-mix(in srgb, currentColor 75%, transparent);
  box-shadow: none;
}

/* === FIX 2 — footer cross-border option list (BASE since D-1 batch 1) =======
   Follow-up to the footer popout fix: the option list is a paper panel
   (W2-07 skin), but option text inherited the footer's admin-driven white
   → white-on-paper, readable only under the hover wash. Pin the panel's
   text to ink tokens. Also clip children to the panel's rounded corners —
   scrolling is owned by the inner .select-popout__list__scroll (theme.css
   overflow-y:auto), so overflow:hidden here is layout-safe. */
.site-footer .select-popout__list {
  color: var(--fg-1);
  overflow: hidden;
}
.site-footer .select-popout__option {
  color: var(--fg-1);
}
.site-footer .select-popout__item--current .select-popout__option {
  color: var(--bb-purple-700);
}

/* === FIX 3 — footer cross-border toggle breathing room =====================
   (BASE since D-1 batch 1) The pill's contents sat flush against its rounded
   ends. Match the .bb-field pill's horizontal rhythm and give the flag,
   label and chevron space to breathe. */
.site-footer .select-popout__toggle {
  padding-inline: var(--space-4);
  gap: var(--space-2);
}

/* === W4-05 — tasting-note row in the buy-box (BASE since D-1 batch 3) ===========
   C-2 build: snippets/tasting-notes-row.liquid renders under the PDP title —
   one paper pill per unique tasting SVG, the metafield entries as the label
   (map: docs/c2-tasting-note-map-proposal-2026-07-04.md). The render itself
   renders nothing when the product has no tasting metafield (no DOM,
   not unstyled DOM; the ds_w3_pdp Liquid gate retired with D-1 batch 3). Pill skin matches the W2-07 variant pills (paper fill,
   hairline border, pill radius); label --fg-2 on --bb-paper is AA. */
.tasting-notes {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-2) 0 var(--space-1);
  padding: 0;
}
.tasting-notes__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-1) var(--space-3) var(--space-1) var(--space-2);
  background: var(--bb-paper);
  border: 1px solid var(--bb-line-2);
  border-radius: var(--bb-radius-pill);
}
.tasting-notes__icon {
  width: 24px;
  height: 24px;
  flex: none;
}
.tasting-notes__label {
  font-size: var(--bb-text-sm);
  line-height: 1.2;
  color: var(--fg-2);
}

/* === W4-06 — bloom product tiles (GATED: ds-w4-tiles) =====================
   The canonical DS storefront product visual (spec: DS project
   ProductTile.jsx + product_tiles PDF comps). Coffee cards in collection
   grids drop bag photography for an illustrated cover (tasting-note SVGs
   on a per-product pastel tint) + a redesigned footer (display name,
   mono roast-dot row, mono notes row). Markup: snippets/bloom-tile.liquid,
   rendered by product-grid-item.liquid only when the ds_w4_tiles setting
   is on AND the product qualifies — so everything here is additionally
   scoped to .product-grid-item--bloom and never touches photo cards.

   Cover tint tokens — the 8 --bb-tile-tint-* pastels are defined in the
   DS token block of snippets/css-variables.liquid (canonical home; this
   file stays raw-hex-free per its header). Every tasting-icon bucket
   maps to one of the 8; the bucket→tint table lives in bloom-tile.liquid
   (tint = first note's bucket):
     choc   chocolate / nut / caramel      floral soft pink
     honey  honey / sugar / vanilla        citrus citrus / herbal / tea
     berry  grape / wine / dark fruit      stone  stone fruit / tropical
     red    strawberry / red fruit         cream  default / roast / spice */

/* Card chrome. The theme card is two stacked boxes (aspect-ratio image
   box + info box); the tile reads as ONE white card: image box carries
   the top radius and side/top border, the footer carries the bottom
   radius, side/bottom border and the 1px top divider. */
.ds-w4-tiles .product-grid-item--bloom .product-grid-item__image {
  margin-bottom: 0;
  background: var(--bb-white);
  border: 1px solid var(--bb-line);
  border-bottom: 0;
  border-radius: 14px 14px 0 0;
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}
.ds-w4-tiles .product-grid-item--bloom .product-grid-item__info {
  background: var(--bb-white);
  border: 1px solid var(--bb-line);
  border-radius: 0 0 14px 14px;
  box-shadow: var(--shadow-sm);
  text-align: left;
  gap: 0;
}
/* Keep the W2-04 hover lift feel on tiles (this block is appended after
   W2-04, so without these the base rules above would win on hover). */
.ds-w4-tiles .product-grid-item--bloom:hover .product-grid-item__image,
.ds-w4-tiles .product-grid-item--bloom:focus-within .product-grid-item__image {
  box-shadow: var(--shadow-md);
}
/* Keyboard focus ring (ungated W2-01 rule is earlier in the file at equal
   specificity — re-assert it above the tile chrome, W2-04 pattern). */
.ds-w4-tiles .product-grid-item--bloom:has(:focus-visible) .product-grid-item__image {
  box-shadow: 0 0 0 4px var(--bb-white, #fff), 0 0 0 8px var(--bb-purple-500);
}

/* Height standardization (owner QA round 2). The collection grid is
   display:grid, so the <li> already stretches to the tallest card in
   its row — but the white tile inside ended wherever its footer ended,
   so rows LOOKED ragged. Make the bloom card a column flex and let the
   footer box grow to fill the stretched row height; combined with the
   2-line name clamp + the footer min-height below, cards in a row
   equalize whether the name is one word or the Bolivia auction lot. */
.ds-w4-tiles .product-grid-item--bloom {
  display: flex;
  flex-direction: column;
}
.ds-w4-tiles .product-grid-item--bloom .product-grid-item__image {
  flex: 0 0 auto;
}
.ds-w4-tiles .product-grid-item--bloom .product-grid-item__info {
  flex: 1 0 auto;
}
/* Reserved footer structure: covers a 2-line name + roast + notes rows,
   so a single-line no-roast card can't collapse shorter than its
   neighbors even where the container doesn't stretch (carousels). */
.ds-w4-tiles .product-grid-item--bloom .bloom-foot {
  min-height: 92px;
}

/* Contain-mode ("Fit" image size) insurance: the theme's --portrait /
   --landscape container variants shrink the contain box to the ORIGINAL
   photo's aspect ratio, which would letterbox the cover. The Liquid side
   already skips those classes for bloom tiles; this override guarantees
   a full-bleed cover regardless. */
.ds-w4-tiles .product-grid-item--bloom .product__media__container {
  width: 100%;
  height: 100%;
  padding: 0;
}

/* Cover: fills the card's aspect-ratio image area (the media holder is
   already absolute inset-0 z-1; quick-buy overlays at z-2 above it). */
.ds-w4-tiles .bloom-cover {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--bb-tile-tint-cream);
}
.ds-w4-tiles .bloom-cover--tint-choc   { background: var(--bb-tile-tint-choc); }
.ds-w4-tiles .bloom-cover--tint-floral { background: var(--bb-tile-tint-floral); }
.ds-w4-tiles .bloom-cover--tint-honey  { background: var(--bb-tile-tint-honey); }
.ds-w4-tiles .bloom-cover--tint-citrus { background: var(--bb-tile-tint-citrus); }
.ds-w4-tiles .bloom-cover--tint-berry  { background: var(--bb-tile-tint-berry); }
.ds-w4-tiles .bloom-cover--tint-stone  { background: var(--bb-tile-tint-stone); }
.ds-w4-tiles .bloom-cover--tint-red    { background: var(--bb-tile-tint-red); }
.ds-w4-tiles .bloom-cover--tint-cream  { background: var(--bb-tile-tint-cream); }

/* Icon composition (W4-06d slot system). The tasting/gear SVGs have
   wildly different intrinsic aspect ratios (lemongrass wide, peanut
   tall), so every slot is a fixed bounding BOX — width AND height —
   with object-fit: contain, keeping visual mass consistent regardless
   of asset proportions. ONE scale system for all layout variants:
     a = large  54% × 58%
     b = medium 34% × 38%
     c = small  21% × 24% (optional third)
   Six deterministic variants v0–v5 (picked by the seeded modulo in
   bloom-tile.liquid) differ in PLACEMENT + rotation only, never in
   scale; rotations stay within the ±8–12° band. */
.ds-w4-tiles .bloom-cover__icon {
  position: absolute;
  object-fit: contain;
  pointer-events: none;
}
.ds-w4-tiles .bloom-cover__icon--a { width: 54%; height: 58%; }
.ds-w4-tiles .bloom-cover__icon--b { width: 34%; height: 38%; }
.ds-w4-tiles .bloom-cover__icon--c { width: 21%; height: 24%; }

/* v0 — large lower-left, medium upper-right, small mid-right */
.ds-w4-tiles .bloom-cover--v0 .bloom-cover__icon--a { left: 5%; bottom: 4%; transform: rotate(-9deg); }
.ds-w4-tiles .bloom-cover--v0 .bloom-cover__icon--b { right: 7%; top: 10%; transform: rotate(10deg); }
.ds-w4-tiles .bloom-cover--v0 .bloom-cover__icon--c { right: 6%; bottom: 30%; transform: rotate(-8deg); }
/* v1 — large lower-right, medium upper-left, small top-center */
.ds-w4-tiles .bloom-cover--v1 .bloom-cover__icon--a { right: 5%; bottom: 5%; transform: rotate(8deg); }
.ds-w4-tiles .bloom-cover--v1 .bloom-cover__icon--b { left: 7%; top: 12%; transform: rotate(-10deg); }
.ds-w4-tiles .bloom-cover--v1 .bloom-cover__icon--c { left: 44%; top: 6%; transform: rotate(12deg); }
/* v2 — large center-low, medium upper-right, small upper-left */
.ds-w4-tiles .bloom-cover--v2 .bloom-cover__icon--a { left: 23%; bottom: 3%; transform: rotate(-8deg); }
.ds-w4-tiles .bloom-cover--v2 .bloom-cover__icon--b { right: 5%; top: 8%; transform: rotate(11deg); }
.ds-w4-tiles .bloom-cover--v2 .bloom-cover__icon--c { left: 6%; top: 12%; transform: rotate(-12deg); }
/* v3 — large upper-left, medium lower-right, small lower-left */
.ds-w4-tiles .bloom-cover--v3 .bloom-cover__icon--a { left: 6%; top: 6%; transform: rotate(9deg); }
.ds-w4-tiles .bloom-cover--v3 .bloom-cover__icon--b { right: 6%; bottom: 10%; transform: rotate(-11deg); }
.ds-w4-tiles .bloom-cover--v3 .bloom-cover__icon--c { left: 10%; bottom: 8%; transform: rotate(8deg); }
/* v4 (new W4-06d) — large upper-right, medium lower-left, small mid-left */
.ds-w4-tiles .bloom-cover--v4 .bloom-cover__icon--a { right: 5%; top: 5%; transform: rotate(-10deg); }
.ds-w4-tiles .bloom-cover--v4 .bloom-cover__icon--b { left: 6%; bottom: 6%; transform: rotate(9deg); }
.ds-w4-tiles .bloom-cover--v4 .bloom-cover__icon--c { left: 8%; top: 34%; transform: rotate(-8deg); }
/* v5 (new W4-06d) — large center-high, medium lower-left, small lower-right */
.ds-w4-tiles .bloom-cover--v5 .bloom-cover__icon--a { left: 24%; top: 4%; transform: rotate(10deg); }
.ds-w4-tiles .bloom-cover--v5 .bloom-cover__icon--b { left: 5%; bottom: 5%; transform: rotate(-9deg); }
.ds-w4-tiles .bloom-cover--v5 .bloom-cover__icon--c { right: 7%; bottom: 12%; transform: rotate(12deg); }

/* Gift ASSORTMENT composition (owner QA round 2): bundles must read as
   "several things in a box", clearly different from a single-bag coffee
   tile — 4 uniformly small icons in a loose 2×2 grid instead of the
   large/medium/small hierarchy. One fixed arrangement (no v0–v5 on the
   gift cover); per-bundle variety comes from the seeded icon pick and
   the berry/cream tint alternation. Rotations stay in the ±8–12° band.
   These rules out-rank the base slot boxes (0,3,0 vs 0,2,0). */
.ds-w4-tiles .bloom-cover--gift .bloom-cover__icon {
  width: 27%;
  height: 30%;
}
.ds-w4-tiles .bloom-cover--gift .bloom-cover__icon--a { left: 11%; top: 12%; transform: rotate(-8deg); }
.ds-w4-tiles .bloom-cover--gift .bloom-cover__icon--b { right: 13%; top: 17%; transform: rotate(10deg); }
.ds-w4-tiles .bloom-cover--gift .bloom-cover__icon--c { left: 15%; bottom: 16%; transform: rotate(9deg); }
.ds-w4-tiles .bloom-cover--gift .bloom-cover__icon--d { right: 11%; bottom: 11%; transform: rotate(-11deg); }

/* Subscription covers (owner QA round 3 — replaces the rejected flat
   purple-500 Beanie cover; beanie-take-it-slow.svg stays in assets/).
   W4-06g (owner-final): pre-paid tiles compose beans in STRICT ROWS OF
   THREE — 1 row / 2 rows / 4 rows for 3/6/12 months. The cover is a
   centered column of .bloom-cover__bean-row flex rows; the bean box is
   sized off the tightest (4-row) case and shared by all durations, so
   fewer months read as more air, not bigger beans. Rotation jitter
   arrives as a seeded inline --bean-rot from bloom-tile.liquid. Odd
   rows are bean-1, even rows bean-2 (shade variation — flattening back
   to bean-1-only is a one-line revert in the snippet). NO badge on any
   sub cover (LMTD removed, owner QA round 4). */
.ds-w4-tiles .bloom-cover--sub {
  background: var(--bb-tile-tint-sub);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  row-gap: 4.5%;
  padding: 10%;
}
.ds-w4-tiles .bloom-cover__bean-row {
  display: flex;
  justify-content: center;
  column-gap: 8%;
  width: 100%;
}
.ds-w4-tiles .bloom-cover__bean {
  width: 17%;
  aspect-ratio: 1;
  object-fit: contain;
  transform: rotate(var(--bean-rot, 0deg));
  pointer-events: none;
  flex: none;
}
/* Monthly Surprise — "Sunny burst" (owner-picked): honey-cream tint,
   one large aroma-swirl "mystery flavor" mark left-of-center and a
   loose strawberry/lemon/peach ring on the right. Deliberately the
   brightest tile in the subscription row. */
.ds-w4-tiles .bloom-cover--sub-surprise {
  background: var(--bb-tile-tint-sub-surprise);
  display: block;
  padding: 0;
}
.ds-w4-tiles .bloom-cover__mystery {
  position: absolute;
  width: 46%;
  height: 56%;
  object-fit: contain;
  left: 8%;
  top: 22%;
  transform: rotate(-8deg);
  pointer-events: none;
}
.ds-w4-tiles .bloom-cover__fruit {
  position: absolute;
  width: 19%;
  height: 22%;
  object-fit: contain;
  pointer-events: none;
}
.ds-w4-tiles .bloom-cover__fruit--a { right: 26%; top: 10%; transform: rotate(8deg); }
.ds-w4-tiles .bloom-cover__fruit--b { right: 8%; top: 38%; transform: rotate(-7deg); }
.ds-w4-tiles .bloom-cover__fruit--c { right: 22%; bottom: 10%; transform: rotate(5deg); }
/* W4-06h — Sunny-burst twinkle (owner: "make it sparkle… boost
   conversion"). CSS-only: three honey-gold sparkles twinkle on a
   staggered loop and the aroma mark breathes; no JS, ~0.4 KB. Both
   animations stop entirely under prefers-reduced-motion. */
.ds-w4-tiles .bloom-cover__spark {
  position: absolute;
  z-index: 1;
  font-size: 22px;
  line-height: 1;
  color: var(--bb-honey);
  opacity: 0.35;
  animation: bloom-twinkle 2.6s var(--ease-out) infinite;
  pointer-events: none;
  user-select: none;
}
.ds-w4-tiles .bloom-cover__spark--1 { left: 10%; top: 12%; }
.ds-w4-tiles .bloom-cover__spark--2 { left: 46%; top: 56%; font-size: 15px; animation-delay: 0.9s; }
.ds-w4-tiles .bloom-cover__spark--3 { right: 10%; bottom: 26%; font-size: 18px; animation-delay: 1.7s; }
.ds-w4-tiles .bloom-cover--sub-surprise .bloom-cover__mystery {
  animation: bloom-breathe 5.2s ease-in-out infinite;
}
@keyframes bloom-twinkle {
  0%, 100% { opacity: 0.25; transform: scale(0.75) rotate(-8deg); }
  45%      { opacity: 1;    transform: scale(1.12) rotate(6deg); }
}
@keyframes bloom-breathe {
  0%, 100% { transform: rotate(-8deg) translateY(0); }
  50%      { transform: rotate(-8deg) translateY(-2.5%); }
}
@media (prefers-reduced-motion: reduce) {
  .ds-w4-tiles .bloom-cover__spark,
  .ds-w4-tiles .bloom-cover--sub-surprise .bloom-cover__mystery {
    animation: none;
  }
  .ds-w4-tiles .bloom-cover__spark { opacity: 0.7; }
}

/* Footer (round 3): FULL-WIDTH name block over the mono rows — the
   price left the name row so the coffee name gets the whole width
   (owner: "almost full space horizontally to show the coffee name").
   The visible price right-aligns on the roast row (or the notes row,
   or its own quiet line — placement decided in bloom-tile.liquid) and
   is aria-hidden; the canonical AT price is the hidden full wording
   inside the name link. */
.ds-w4-tiles .bloom-foot {
  padding: 12px 14px 14px;
  display: grid;
  gap: 6px;
}
.ds-w4-tiles .bloom-foot__name-heading {
  margin: 0;
}
.ds-w4-tiles .bloom-foot__name {
  display: block;
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: 15px;
  line-height: var(--lh-tight);
  letter-spacing: -0.01em;
  color: var(--bb-ink);
}
/* 2-line name clamp (round 2 height standardization). Visual only —
   the link text and the title attribute keep the full name. min-height
   reserves the second line so one-word names don't sit shorter than
   wrapping neighbors. */
.ds-w4-tiles .bloom-foot__name-text {
  min-width: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  min-height: calc(2 * var(--lh-tight) * 1em);
}
/* Price voice, round 3 (supersedes round 2's ink/12px/600/body):
   quieter still — mono, 10px, weight 500, --fg-2 — and right-aligned
   on whichever row hosts it via margin-left:auto. One shared rule for
   grid / shelf rails / upsell strips; the host rows' uppercase and
   .06em tracking are neutralized for the money string. */
.ds-w4-tiles .bloom-foot__price {
  flex: none;
  margin-left: auto;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-weight: var(--weight-medium);
  font-size: 10px;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--fg-2);
}
.ds-w4-tiles .bloom-foot__roast {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--bb-purple-500);
}
.ds-w4-tiles .bloom-foot__dots {
  display: inline-flex;
  gap: 2px;
  letter-spacing: 0;
}
.ds-w4-tiles .bloom-foot__dot--off {
  color: var(--bb-line);
}
.ds-w4-tiles .bloom-foot__notes {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--fg-3);
}
.ds-w4-tiles .bloom-foot__notes-text {
  min-width: 0;
}
/* Own-line price for tiles with neither roast nor notes rows (gift). */
.ds-w4-tiles .bloom-foot__priceline {
  display: flex;
  line-height: 1;
}

@media (prefers-reduced-motion: reduce) {
  .ds-w4-tiles .product-grid-item--bloom .product-grid-item__image {
    transition: none;
  }
}

/* === W4-07 — hero Drop gradient (GATED: ds-w4-hero) ========================
   The Drop hero treatment's legibility device (spec: DS project
   preview/hero_block.html; ruling: docs/w4-07-hero-presets-proposal-
   2026-07-05.md, Option 2). The slideshow's flat overlay_opacity scrim
   cannot express the angled gradient, so the slide block gained a
   "Drop gradient" checkbox (section-slideshow.liquid) that emits
   .slide--drop-gradient on the slide; the "Hero — Drop" preset ships
   with it checked. Both gates must be true for any pixel to change:
   the ds_w4_hero theme setting (body class) AND the per-slide checkbox.

   Mechanism: .slider .item is a one-cell grid stacking the image
   container (z auto), .overlay (z 2) and .slide__text__container (z 100).
   The ::after below joins that grid as one more stacked cell — no
   absolute positioning, so it behaves identically before and after
   Flickity takes over slide positioning.

   Raw rgba() exception to this file's tokens-only rule — intentional:
   the rgba(15, 30, 20, …) stops below are the hero spec's photographic
   ink overlay (hero_block.html), tuned to sit over product photography.
   They are NOT a brand color token and must not drift if tokens change,
   so they are pinned here verbatim at the spec's stop positions. */
.ds-w4-hero .slider .item.slide--drop-gradient::after {
  content: "";
  grid-row-start: 1;
  grid-column-start: 1;
  width: 100%;
  min-height: 100%;
  z-index: 3; /* above .overlay (2), below .slide__text__container (100) */
  pointer-events: none;
  /* hero spec: 105° ink gradient 82% → 55% → 5%, plus the bottom fade
     that keeps the mono meta row readable over bright photo bottoms */
  background:
    linear-gradient(to top, rgba(15, 30, 20, 0.35) 0%, transparent 55%),
    linear-gradient(105deg, rgba(15, 30, 20, 0.82) 0%, rgba(15, 30, 20, 0.55) 42%, rgba(15, 30, 20, 0.05) 78%);
}

/* === W5-02 — shelf-module density (GATED: ds-w4-tiles) =====================
   The bloom tiles' second density (DS canon preview/product_cards.html):
   same visual DNA as the storefront 4-up tiles, just denser — for
   cross-sell rails. Same ds_w4_tiles gate: one feature, two densities,
   so everything here also sits under .ds-w4-tiles and, like W4-06, only
   ever touches bloom cards (never photo cards).

   Rail contexts:
   - "You May Also Like" + the Recently-viewed tab = the related-products
     section (.related-products), and the standalone recently-viewed
     section (.recent-products). Both funnel through product-grid-item,
     so their tiles are scoped by CONTAINER below; the recently-viewed
     cards are JS-injected via sections/api-product-grid-item.liquid but
     land inside these same containers, so they inherit.
   - PDP buy-box "Pairs well with" / complementary products and the
     cart + cart-drawer pair-products strips render their own markup
     (snippets/upsell-product.liquid / upsell-products.liquid); those
     call bloom-tile with density: 'shelf', which emits the
     .bloom-cover--shelf / .bloom-foot--shelf hooks used below.

   Compact deltas vs the W4-06 base card (canon values, owner-adjusted):
   radius 14 → 12, footer padding 12px 14px 14px → 11px 13px 13px,
   name 15 → 14px, roast + notes rows 9 → 8.5px with 8px dots. The
   hairline top border is already the base footer's own top border —
   nothing to change there. */
.ds-w4-tiles .related-products .product-grid-item--bloom .product-grid-item__image,
.ds-w4-tiles .recent-products .product-grid-item--bloom .product-grid-item__image {
  border-radius: 12px 12px 0 0;
}
.ds-w4-tiles .related-products .product-grid-item--bloom .product-grid-item__info,
.ds-w4-tiles .recent-products .product-grid-item--bloom .product-grid-item__info {
  border-radius: 0 0 12px 12px;
}
.ds-w4-tiles .related-products .product-grid-item--bloom .bloom-foot,
.ds-w4-tiles .recent-products .product-grid-item--bloom .bloom-foot,
.ds-w4-tiles .bloom-foot--shelf {
  padding: 11px 13px 13px;
}
.ds-w4-tiles .related-products .product-grid-item--bloom .bloom-foot__name,
.ds-w4-tiles .recent-products .product-grid-item--bloom .bloom-foot__name,
.ds-w4-tiles .bloom-foot--shelf .bloom-foot__name {
  font-size: 14px;
}
.ds-w4-tiles .related-products .product-grid-item--bloom .bloom-foot__roast,
.ds-w4-tiles .recent-products .product-grid-item--bloom .bloom-foot__roast,
.ds-w4-tiles .bloom-foot--shelf .bloom-foot__roast,
.ds-w4-tiles .related-products .product-grid-item--bloom .bloom-foot__notes,
.ds-w4-tiles .recent-products .product-grid-item--bloom .bloom-foot__notes,
.ds-w4-tiles .bloom-foot--shelf .bloom-foot__notes {
  font-size: 8.5px;
}
.ds-w4-tiles .related-products .product-grid-item--bloom .bloom-foot__dots,
.ds-w4-tiles .recent-products .product-grid-item--bloom .bloom-foot__dots,
.ds-w4-tiles .bloom-foot--shelf .bloom-foot__dots {
  font-size: 8px;
}

/* Upsell strips: the bloom parts drop into the strip's OWN layout — the
   cover replaces the photo inside the existing .product-upsell__image
   link and the footer replaces the title/price row inside
   .product-upsell__content (which already carries the strip's padding,
   so the shelf footer padding collapses here).

   W4-06e rhythm polish (owner QA round 2 — "spacing and layout looks a
   little off"): everything scoped to :has(.bloom-cover) so photo strips
   are untouched. A consistent 4/8px vertical scale; the sold-out note
   is grouped with the button row instead of floating mid-column; the
   cover becomes a top-aligned rounded SQUARE flush with the text block
   (it previously stretched to the strip height). The price itself is
   already the round-2 secondary style via the shared
   .bloom-foot__price rule, baseline-aligned in the name row. */
.ds-w4-tiles .product-upsell .bloom-foot--shelf {
  padding: 0;
  margin: 0 0 8px;
}
.ds-w4-tiles .product-upsell:has(.bloom-cover) .product-upsell__image {
  align-self: flex-start;
  aspect-ratio: 1;
  border-radius: 10px;
  overflow: hidden;
}
.ds-w4-tiles .product-upsell:has(.bloom-cover) .product-upsell__subtitle {
  margin: 0 0 8px;
}
/* Sold-out state: quiet mono note that hugs the button row (the auto
   top margin takes over the column's flexible gap, so note + button sit
   together at the bottom instead of the note floating alone). */
.ds-w4-tiles .product-upsell:has(.bloom-cover) .product-upsell__unavailable {
  margin: auto 0 4px;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--fg-3);
  opacity: 1;
}
.ds-w4-tiles .product-upsell:has(.bloom-cover) .product-upsell__unavailable ~ .product-upsell__btn {
  margin-top: 0;
}
/* Same grouping for the single-variant form's out-of-stock note. */
.ds-w4-tiles .product-upsell:has(.bloom-cover) .product-upsell__out-of-stock {
  margin: 4px 0 0;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--fg-3);
}

/* Shelf-density footer reservation (round 2 height standardization,
   companion to the W4-06 grid rule): compact rails equalize too. */
.ds-w4-tiles .related-products .product-grid-item--bloom .bloom-foot,
.ds-w4-tiles .recent-products .product-grid-item--bloom .bloom-foot {
  min-height: 86px;
}

/* === W5-03a — expression chips (GATED: ds-w5-plp) ===========================
   Pill-chip nav above the collection product grid (markup:
   snippets/expression-chips.liquid, rendered by sections/collection.liquid
   only when the ds_w5_plp setting AND the section's show_expression_chips
   checkbox are on — the Liquid renders zero bytes off, this block is
   belt-and-braces). Canon: DS project ui_kits/website/Shop.jsx chips-row;
   the kit's emoji are DELIBERATELY replaced by the illustrated tasting
   SVGs (README v1.0 retired emoji from product UI) as ~18px <img>s.

   Chips are links: paper fill on a --bb-line hairline pill, --fg-2 label
   13px/600; active = purple-500 fill + white text; hover = purple-50;
   keyboard focus = the W2-01 purple halo. The row scrolls horizontally
   with no scrollbar on mobile — chips never wrap. */
.ds-w5-plp .expression-chips {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 20px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.ds-w5-plp .expression-chips::-webkit-scrollbar {
  display: none;
}
.ds-w5-plp .expression-chips__chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 8px 16px;
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-pill);
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: var(--weight-semibold);
  line-height: 1.2;
  color: var(--fg-2);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}
/* Theme link rules underline on hover; chips stay clean. */
.ds-w5-plp .expression-chips__chip:hover {
  background: var(--bb-purple-50);
  text-decoration: none;
  color: var(--fg-2);
}
/* W2-01 halo pattern — visible on paper AND on the active purple fill. */
.ds-w5-plp .expression-chips__chip:focus-visible {
  outline: 0;
  box-shadow: 0 0 0 3px var(--bb-purple-100), 0 0 0 1px var(--bb-purple-500);
}
.ds-w5-plp .expression-chips__chip.is-active,
.ds-w5-plp .expression-chips__chip.is-active:hover {
  background: var(--bb-purple-500);
  border-color: var(--bb-purple-500);
  color: var(--bb-white);
}
.ds-w5-plp .expression-chips__icon {
  display: block;
  width: 18px;
  height: 18px;
  object-fit: contain;
  pointer-events: none;
}

/* === W5-03b — composed collection headers (GATED: ds-w5-plp) ================
   Decorative tasting-SVG layer inside the collection header band
   (markup: snippets/collection-header-art.liquid, rendered by
   sections/collection-image-with-title.liquid only when the flag + the
   section's show_composed_header checkbox are on AND no banner image is
   showing). The layer fills .banner-inner behind the centered title;
   icons sit in the OUTER THIRDS and vertically inside the band so the
   composition survives the desktop→mobile center-crop. Slot boxes have
   fixed width AND height with object-fit: contain (the bloom-tile slot
   principle — consistent visual mass regardless of each SVG's intrinsic
   aspect ratio): l1/r1 ≈ 180px, l2/r2 ≈ 120px, rotations within the
   tile system's ±8–12° band. Two placement variants (v0/v1), picked
   deterministically by the handle seed in the snippet.

   ≤767px the slots shrink with the band; UNDER 480px the layer hides
   entirely (judgment, stated: the title's wrapper--narrow spans nearly
   the full viewport there, so the outer thirds no longer exist and any
   icon would sit underneath the title text). */
.ds-w5-plp .plp-header-art {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  pointer-events: none;
}
.ds-w5-plp .plp-header-art__icon {
  position: absolute;
  object-fit: contain;
  pointer-events: none;
}
/* Slot boxes — one large + one small per side at most. */
.ds-w5-plp .plp-header-art__icon--l1,
.ds-w5-plp .plp-header-art__icon--r1 {
  width: 180px;
  height: 150px;
  top: 50%;
}
.ds-w5-plp .plp-header-art__icon--l2,
.ds-w5-plp .plp-header-art__icon--r2 {
  width: 120px;
  height: 100px;
}
.ds-w5-plp .plp-header-art__icon--l1 { left: 2.5%; }
.ds-w5-plp .plp-header-art__icon--r1 { right: 2.5%; }
.ds-w5-plp .plp-header-art__icon--l2 { left: 14%; top: 8%; }
.ds-w5-plp .plp-header-art__icon--r2 { right: 14%; bottom: 8%; }
/* Rotation variants (translate keeps l1/r1 vertically centered in the band). */
.ds-w5-plp .plp-header-art--v0 .plp-header-art__icon--l1 { transform: translateY(-50%) rotate(-9deg); }
.ds-w5-plp .plp-header-art--v0 .plp-header-art__icon--r1 { transform: translateY(-50%) rotate(10deg); }
.ds-w5-plp .plp-header-art--v0 .plp-header-art__icon--l2 { transform: rotate(11deg); }
.ds-w5-plp .plp-header-art--v0 .plp-header-art__icon--r2 { transform: rotate(-8deg); }
.ds-w5-plp .plp-header-art--v1 .plp-header-art__icon--l1 { transform: translateY(-50%) rotate(8deg); }
.ds-w5-plp .plp-header-art--v1 .plp-header-art__icon--r1 { transform: translateY(-50%) rotate(-10deg); }
.ds-w5-plp .plp-header-art--v1 .plp-header-art__icon--l2 { transform: rotate(-12deg); }
.ds-w5-plp .plp-header-art--v1 .plp-header-art__icon--r2 { transform: rotate(9deg); }
/* Tablet/mobile: shrink with the band, hug the edges a little tighter. */
@media only screen and (max-width: 767px) {
  .ds-w5-plp .plp-header-art__icon--l1,
  .ds-w5-plp .plp-header-art__icon--r1 {
    width: 110px;
    height: 92px;
  }
  .ds-w5-plp .plp-header-art__icon--l2,
  .ds-w5-plp .plp-header-art__icon--r2 {
    width: 72px;
    height: 60px;
  }
  .ds-w5-plp .plp-header-art__icon--l1 { left: 2%; }
  .ds-w5-plp .plp-header-art__icon--r1 { right: 2%; }
  .ds-w5-plp .plp-header-art__icon--l2 { left: 12%; }
  .ds-w5-plp .plp-header-art__icon--r2 { right: 12%; }
}
/* Under 480px the outer thirds are gone — hide, don't crowd the title. */
@media only screen and (max-width: 479px) {
  .ds-w5-plp .plp-header-art {
    display: none;
  }
}

/* === W5-04a — homepage hero sections: drop-rail + story-rail (opt-in via
   editor Add-section; no flag — adding the section is the gate, #110
   pattern). BUILD REGION: agent A writes between the markers only. ====== */
/* @w5-04a-start */

/* Shared hero DNA (drop + story): 1.55fr/1fr grid, ~540px tall, and the
   numbered rail column from the mock — mono number, display-900 heading,
   small muted body, hairline separators plus a hairline on the rail's
   leading edge. Namespace bb-w5h- per the W5-04 build spec. */
.bb-w5h-drop__grid,
.bb-w5h-story__grid {
  display: grid;
  grid-template-columns: 1.55fr 1fr;
  min-height: 540px;
  border-radius: var(--bb-radius-lg);
  overflow: hidden;
}

.bb-w5h-rail {
  display: flex;
  flex-direction: column;
}
.bb-w5h-rail__item {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-6);
}
.bb-w5h-rail__num {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: var(--weight-bold);
  letter-spacing: 0.14em;
  color: var(--bb-purple-500);
}
.bb-w5h-rail__heading {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: var(--bb-text-md);
  letter-spacing: -0.01em;
  color: var(--fg-1);
}
.bb-w5h-rail__text {
  margin: 0;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.55;
  color: var(--fg-3);
}
/* Drop hero rail: cream with neutral hairlines. */
.bb-w5h-rail--cream {
  background: var(--bb-cream);
  border-left: 1px solid var(--bb-line);
}
.bb-w5h-rail--cream .bb-w5h-rail__item + .bb-w5h-rail__item {
  border-top: 1px solid var(--bb-line);
}
/* Story hero rail: white with purple-200 hairlines. */
.bb-w5h-rail--white {
  background: var(--bb-white);
  border-left: 1px solid var(--bb-purple-200);
}
.bb-w5h-rail--white .bb-w5h-rail__item + .bb-w5h-rail__item {
  border-top: 1px solid var(--bb-purple-200);
}

/* --- Drop hero: photo panel. Background image is set per-instance in
   the section's {% style %}; ink panel until a photo is picked. The
   ::before overlay copies the mock's exact gradient stops — alpha
   overlays have no tokens, so the rgba values are intentional. */
.bb-w5h-drop__media {
  position: relative;
  display: flex;
  align-items: flex-end;
  background-color: var(--bb-ink);
  background-size: cover;
  background-position: center;
}
.bb-w5h-drop__media::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Round 3 (2026-07-13 owner ruling): hold the dark band across the left
     ~60% so the text zone is reliably legible and the photo subject on the
     right reads clean. Stops pushed right vs round 1. */
  background:
    linear-gradient(100deg, rgba(8, 18, 12, 0.92), rgba(8, 18, 12, 0.82) 42%, rgba(8, 18, 12, 0.5) 62%, rgba(8, 18, 12, 0.12) 82%),
    linear-gradient(to top, rgba(8, 18, 12, 0.55), rgba(8, 18, 12, 0.1) 50%, rgba(8, 18, 12, 0));
}
.bb-w5h-drop__content {
  position: relative;
  z-index: 1;
  /* Round 3: cap the text column at 58% of the hero on desktop so it never
     crosses into the product/photo subject on the right; the px ceiling keeps
     it from ballooning on ultrawide. Mobile resets this to full width below. */
  max-width: min(640px, 58%);
  padding: var(--space-7) var(--space-6) var(--space-6);
}
.bb-w5h-drop__eyebrow {
  margin: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--bb-text-2xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bb-white);
  text-shadow: 0 1px 2px rgba(8, 18, 12, 0.5);
}
.bb-w5h-drop__heading {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  /* Round 3: ceiling lowered 78→60px. At 78px the heading sprawled past the
     58% column into the photo subject on laptop widths; 60px wraps cleanly
     inside the narrower text column. */
  font-size: clamp(40px, 4.4vw, 60px);
  line-height: 1.04;
  letter-spacing: -0.025em;
  color: var(--bb-white);
  text-shadow: 0 2px 18px rgba(8, 18, 12, 0.35), 0 1px 2px rgba(8, 18, 12, 0.5);
}
.bb-w5h-drop__meta {
  margin: 0 0 var(--space-3);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--bb-text-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bb-white);
  text-shadow: 0 1px 2px rgba(8, 18, 12, 0.5);
}
.bb-w5h-drop__meta-dot {
  flex: none;
  width: 4px;
  height: 4px;
  border-radius: var(--bb-radius-pill);
  background: currentColor;
  opacity: 0.6;
}
.bb-w5h-drop__text {
  margin: 0 0 var(--space-5);
  max-width: 440px;
  font-family: var(--font-body);
  font-size: var(--bb-text-base);
  line-height: 1.55;
  color: var(--bb-white);
  text-shadow: 0 1px 2px rgba(8, 18, 12, 0.5);
}
.bb-w5h-drop__ctas,
.bb-w5h-story__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* CTA pills for on-photo (white / outline-white) and the story ghost.
   These ride on .bb-btn for base pill/typography/focus; the white-alpha
   hover wash has no token (alpha overlay), matching the gradient note. */
.bb-w5h-btn--white {
  background: var(--bb-white);
  color: var(--bb-ink);
}
.bb-w5h-btn--white:hover {
  background: var(--bb-cream);
  transform: translateY(-1px);
}
.bb-w5h-btn--outline-white {
  background: transparent;
  color: var(--bb-white);
  box-shadow: inset 0 0 0 1px var(--bb-white);
}
.bb-w5h-btn--outline-white:hover {
  background: rgba(255, 255, 255, 0.12);
  transform: translateY(-1px);
}
.bb-w5h-btn--outline-white:focus-visible {
  box-shadow: inset 0 0 0 1px var(--bb-white), 0 0 0 3px var(--bb-purple-100);
}
.bb-w5h-btn--outline-purple {
  background: transparent;
  color: var(--bb-purple-600);
  box-shadow: inset 0 0 0 1px var(--bb-purple-300);
}
.bb-w5h-btn--outline-purple:hover {
  background: var(--bb-purple-100);
  transform: translateY(-1px);
}
.bb-w5h-btn--outline-purple:focus-visible {
  box-shadow: inset 0 0 0 1px var(--bb-purple-300), 0 0 0 3px var(--bb-purple-100);
}

/* --- Story hero: purple-50 panel. Round 2 (2026-07-13 owner ruling):
   the four-corner compose layer read as noisy — the decorative layer is
   now one small purple flower in the top-right corner, clear of the
   copy (decorative, pointer-events off, below the text). */
.bb-w5h-story__panel {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  background: var(--bb-purple-50);
  padding: var(--space-7) var(--space-6);
}
.bb-w5h-story__compose {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.bb-w5h-story__art {
  position: absolute;
  display: block;
  height: auto;
}
.bb-w5h-story__art--accent { top: 9%; right: 5%; width: 90px; }
.bb-w5h-story__content {
  position: relative;
  z-index: 1;
  max-width: 560px;
}
.bb-w5h-story__eyebrow {
  margin: 0 0 var(--space-3);
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: var(--bb-text-2xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-purple-500);
}
.bb-w5h-story__eyebrow-dash {
  flex: none;
  width: 24px;
  height: 1px;
  background: var(--bb-purple-500);
}
.bb-w5h-story__heading {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: clamp(36px, 4.3vw, 62px);
  line-height: 1.04;
  letter-spacing: -0.025em;
  color: var(--bb-purple-500);
}
.bb-w5h-story__text {
  margin: 0 0 var(--space-5);
  max-width: 54ch;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  color: var(--fg-2);
}

/* --- Mobile: single column, panel above, rail becomes a full-width
   stacked list (leading hairline rotates from left edge to top edge).

   Round 2 (2026-07-13 owner ruling, mobile-first pass at 390px):
   - Drop hero sheds its descriptor paragraph and everything past the
     second meta item (roast + origin/score survive; most traffic is
     mobile and the full stack read as a wall of text).
   - Display headings get a smaller floor so they hold ~2 lines at
     390px instead of 4.
   - Story panel height follows its (now one-sentence) content instead
     of reserving 380px; its single flower accent shrinks. */
@media only screen and (max-width: 767px) {
  .bb-w5h-drop__grid,
  .bb-w5h-story__grid {
    grid-template-columns: 1fr;
    min-height: 0;
  }
  .bb-w5h-drop__media {
    min-height: 460px;
  }
  /* Round 3: the 58% desktop cap would crush the column on a phone — reset to
     full width. Mobile stacks the photo behind text top-to-bottom, so a
     stronger bottom-up scrim (not the horizontal band) carries legibility. */
  .bb-w5h-drop__media::before {
    background:
      linear-gradient(to top, rgba(8, 18, 12, 0.82), rgba(8, 18, 12, 0.45) 46%, rgba(8, 18, 12, 0.12) 78%);
  }
  .bb-w5h-drop__content {
    max-width: 100%;
    padding: var(--space-6) var(--space-5) var(--space-5);
  }
  .bb-w5h-drop__heading {
    font-size: clamp(32px, 9.5vw, 40px);
  }
  .bb-w5h-drop__text {
    display: none;
  }
  .bb-w5h-drop__meta > :nth-child(n+4) {
    display: none;
  }
  .bb-w5h-story__panel {
    min-height: 0;
    padding: var(--space-6) var(--space-5);
  }
  .bb-w5h-story__heading {
    font-size: clamp(30px, 8.5vw, 36px);
  }
  .bb-w5h-rail--cream,
  .bb-w5h-rail--white {
    border-left: 0;
  }
  .bb-w5h-rail--cream { border-top: 1px solid var(--bb-line); }
  .bb-w5h-rail--white { border-top: 1px solid var(--bb-purple-200); }
  .bb-w5h-rail__item {
    padding: var(--space-4) var(--space-5);
  }
  .bb-w5h-story__art--accent { top: 6%; right: 4%; width: 64px; }
}


/* --- W5-04a fix (2026-07-13 owner feedback) -------------------------------
   1) Art variant — the no-photo state. The old fallback was the bare ink
      panel (read as broken). With bb-w5h-drop--art: purple-50 panel,
      corner tasting illustrations, ink copy, no overlay/shadows; the CTA
      pair swaps to the light-bg purple pills in Liquid.
   2) Optional rail — bb-w5h-drop__grid--full collapses to one column
      when the section has zero rail blocks. */
.bb-w5h-drop__grid--full {
  grid-template-columns: 1fr;
}
.bb-w5h-drop--art .bb-w5h-drop__media {
  background-color: var(--bb-purple-50);
}
.bb-w5h-drop--art .bb-w5h-drop__media::before {
  content: none;
}
.bb-w5h-drop__compose {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.bb-w5h-drop__art {
  position: absolute;
  display: block;
  height: auto;
}
.bb-w5h-drop__art--c1 { top: -30px; right: -26px; width: 230px; }
.bb-w5h-drop__art--c2 { bottom: 10%; right: 18%; width: 130px; }
.bb-w5h-drop__art--c3 { top: 14%; left: 3%; width: 120px; }
.bb-w5h-drop--art .bb-w5h-drop__content {
  position: relative;
}
.bb-w5h-drop--art .bb-w5h-drop__eyebrow {
  color: var(--bb-purple-500);
  text-shadow: none;
}
.bb-w5h-drop--art .bb-w5h-drop__heading {
  color: var(--fg-1);
  text-shadow: none;
}
.bb-w5h-drop--art .bb-w5h-drop__meta {
  color: var(--bb-purple-500);
  text-shadow: none;
}
.bb-w5h-drop--art .bb-w5h-drop__text {
  color: var(--fg-2);
  text-shadow: none;
}
@media only screen and (max-width: 767px) {
  .bb-w5h-drop__art--c1 { width: 150px; top: -20px; right: -20px; }
  .bb-w5h-drop__art--c2 { display: none; }
  .bb-w5h-drop__art--c3 { width: 84px; }
}

/* --- W5-04a fix · Values band (sections/values-band.liquid) ---------------
   The values trio, relocated out of the hero rail (owner: "don't look
   like they should belong at the very top"). Paper strip between
   hairlines so it reads as a distinct card row on the mist page bg —
   same logic as the product cards. Type = the rail voice. */
.bb-w5h-vband {
  background: var(--bb-paper);
  border-top: 1px solid var(--bb-line);
  border-bottom: 1px solid var(--bb-line);
}
.bb-w5h-vband__row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  margin: 0;
  padding: 0;
  list-style: none;
}
.bb-w5h-vband__item {
  padding: 30px 32px;
}
.bb-w5h-vband__item + .bb-w5h-vband__item {
  border-left: 1px solid var(--bb-line);
}
.bb-w5h-vband__num {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-purple-500);
  margin: 0 0 10px;
}
.bb-w5h-vband__heading {
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: 18px;
  letter-spacing: -0.01em;
  color: var(--fg-1);
  margin: 0 0 6px;
}
.bb-w5h-vband__text {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg-3);
  margin: 0;
}
@media only screen and (max-width: 767px) {
  .bb-w5h-vband__row {
    grid-auto-flow: row;
  }
  .bb-w5h-vband__item {
    padding: 22px 0;
  }
  .bb-w5h-vband__item + .bb-w5h-vband__item {
    border-left: 0;
    border-top: 1px solid var(--bb-line);
  }
}
/* @w5-04a-end */

/* === W5-04b — homepage press belt + about teaser (opt-in via editor
   Add-section). BUILD REGION: agent B writes between the markers only. == */
/* @w5-04b-start */

/* --- W5-04b · Press belt (sections/press-belt.liquid) ---------------------
   Full-bleed cream strip between hairlines. Items come from blocks as
   richtext, set in display-900 micro caps; <strong> segments read
   purple-500 (that is how part of a press name gets the brand tint).
   Desktop spreads the row edge-to-edge; under 768px it becomes a
   one-row auto-scrolling marquee (owner ruling 2026-07-17: one row,
   automatically sliding, not touch-slide). The section renders the item
   list twice inside .bb-w5h-belt__track; on desktop the track is
   display:contents (so the first row behaves exactly as before and the
   duplicate is hidden), on mobile the track becomes a flex marquee. */
.bb-w5h-belt {
  background: var(--bb-cream);
  border-top: 1px solid var(--bb-line);
  border-bottom: 1px solid var(--bb-line);
}
/* Desktop: track is transparent to layout; duplicate row is not shown. */
.bb-w5h-belt__track {
  display: contents;
}
.bb-w5h-belt__row {
  display: flex;
  /* Round 4 (2026-07-14 owner ruling): never more than ONE row on desktop.
     Type and gaps shrink fluidly at mid widths; if a viewport is still too
     narrow the row side-scrolls (scrollbar hidden) instead of wrapping.
     Under 768px the mobile block below restores centered wrapping. */
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: clamp(14px, 2.4vw, 32px);
  margin: 0;
  padding: 22px 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
}
.bb-w5h-belt__row::-webkit-scrollbar {
  display: none;
}
/* Must sit AFTER .bb-w5h-belt__row: equal specificity, so source order is
   what keeps the marquee duplicate hidden on desktop. The mobile block
   below re-shows it later in the sheet, which wins the same way. */
.bb-w5h-belt__row--dupe {
  display: none;
}
.bb-w5h-belt__item {
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: clamp(10px, 1.05vw, 12px);
  line-height: 1.2;
  letter-spacing: clamp(0.1em, 0.35vw, 0.18em);
  text-transform: uppercase;
  color: var(--fg-3);
  white-space: nowrap;
}
/* Richtext arrives wrapped in <p> — flatten it into the flex item. */
.bb-w5h-belt__item p {
  display: inline;
  margin: 0;
  font-size: inherit;
  line-height: inherit;
}
.bb-w5h-belt__item strong {
  font-weight: inherit;
  color: var(--bb-purple-500);
}
@media only screen and (max-width: 767px) {
  /* One-row auto-scroll marquee. The viewport clips the wide track (so the
     page never gains horizontal scroll); the track holds two identical
     rows and slides left by exactly one copy + one gap, then repeats
     seamlessly. --belt-gap is the single source of truth for both the
     inter-item gap and the translate math. */
  .bb-w5h-belt__viewport {
    overflow: hidden;
  }
  .bb-w5h-belt__track {
    --belt-gap: 24px;
    display: flex;
    width: max-content;
    gap: var(--belt-gap);
    animation: bb-belt-marquee 20s linear infinite;
    will-change: transform;
  }
  .bb-w5h-belt__row {
    flex: 0 0 auto;
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: var(--belt-gap);
    width: auto;
    padding: 18px 0;
    overflow-x: visible;
  }
  .bb-w5h-belt__row--dupe {
    display: flex;
  }
  .bb-w5h-belt__item {
    font-size: 10px;
    letter-spacing: 0.14em;
  }
}
@keyframes bb-belt-marquee {
  from { transform: translateX(0); }
  /* -50% lands halfway; the extra -gap/2 accounts for the gap between the
     two row copies so the second copy aligns exactly onto the first. */
  to   { transform: translateX(calc(-50% - (var(--belt-gap) / 2))); }
}
/* Accessibility: no auto-motion — fall back to a static centered wrap. */
@media (prefers-reduced-motion: reduce) and (max-width: 767px) {
  .bb-w5h-belt__track {
    animation: none;
    display: flex;
    width: auto;
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px 24px;
  }
  .bb-w5h-belt__row {
    flex-wrap: wrap;
    justify-content: center;
  }
  .bb-w5h-belt__row--dupe {
    display: none;
  }
}

/* --- W5-04b · About teaser (sections/about-teaser.liquid) -----------------
   Two-column card: paper text panel (vertically centered, section-label
   eyebrow with the 24px purple dash, display-900 heading, fg-2 body)
   beside a purple-50 illustration panel (Beanie is sanctioned in the
   About/teaser context). Pills reuse the dsv1 .bb-btn primitives; the
   ghost variant is re-ringed purple here per the mock (dsv1 ghost ring
   is the neutral --bb-line-2). Stacks under 768px, image panel last. */
.bb-w5h-about__card {
  display: grid;
  grid-template-columns: 1fr 1fr;
  overflow: hidden;
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-lg);
}
.bb-w5h-about__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  padding: var(--space-8) var(--space-8);
}
.bb-w5h-about__eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-purple-500);
}
.bb-w5h-about__eyebrow::before {
  content: "";
  flex: 0 0 auto;
  width: 24px;
  height: 1px;
  background: var(--bb-purple-500);
}
.bb-w5h-about__heading {
  margin: 0 0 16px;
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: clamp(32px, 3.4vw, 48px);
  line-height: 1.05;
  letter-spacing: -0.025em;
  color: var(--fg-1);
}
.bb-w5h-about__text {
  max-width: 54ch;
  color: var(--fg-2);
}
.bb-w5h-about__text p {
  margin: 0 0 14px;
  font-size: 15px;
  line-height: 1.55;
}
.bb-w5h-about__text p:last-child {
  margin-bottom: 0;
}
.bb-w5h-about__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: var(--space-5);
}
/* Anchor context: theme link rules underline on hover; pills stay clean. */
.bb-w5h-about__cta,
.bb-w5h-about__cta:hover {
  text-decoration: none;
}
.bb-w5h-about__cta--ghost {
  color: var(--bb-purple-600);
  box-shadow: inset 0 0 0 1px var(--bb-purple-300);
}
.bb-w5h-about__cta--ghost:focus-visible {
  box-shadow: inset 0 0 0 1px var(--bb-purple-300), 0 0 0 3px var(--bb-purple-100);
}
.bb-w5h-about__media {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 480px;
  padding: var(--space-6) 0;
  background: var(--bb-purple-50);
}
.bb-w5h-about__img {
  display: block;
  width: 78%;
  height: auto;
}
/* Round 2 (2026-07-13 owner ruling): the panel carries a real photo —
   human touch — so a picked image fills the panel edge-to-edge; the
   centered 78% treatment stays for the illustration fallback only. */
.bb-w5h-about__media--photo {
  position: relative;
  padding: 0;
}
.bb-w5h-about__img--photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
@media only screen and (max-width: 767px) {
  .bb-w5h-about__card {
    grid-template-columns: 1fr;
  }
  .bb-w5h-about__body {
    padding: var(--space-6) var(--space-5);
  }
  .bb-w5h-about__media {
    min-height: 320px;
    padding: var(--space-5) 0;
  }
  .bb-w5h-about__media--photo {
    padding: 0;
  }
}

/* @w5-04b-end */

/* === W5-04c — homepage featured-grid header recomposition (GATED:
   ds-w5-home) ==============================================================
   Canon: DS preview/page_home.html "Product grid" section frame — mono
   eyebrow kicker with a purple dash, display-900 section title, quiet sub.
   The eyebrow element itself (.bb-w5h-eyebrow) only exists when the section
   setting is filled, so its base style is ungated; the RESTYLE of the
   theme's existing heading/text is what the flag gates. Scoped to
   .featured-collection so other section__heading uses are untouched. */
.bb-w5h-eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-purple-500);
  margin-bottom: 10px;
}
.bb-w5h-eyebrow::before {
  content: "";
  width: 24px;
  height: 1px;
  background: var(--bb-purple-500);
}
.text-center .bb-w5h-eyebrow {
  justify-content: center;
}
.ds-w5-home .featured-collection .section__heading {
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: calc(var(--bb-text-xl, 2rem) * 1.3);
  letter-spacing: -0.025em;
  line-height: 1;
  color: var(--fg-1);
}
.ds-w5-home .featured-collection .section__text {
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--fg-2);
  line-height: 1.55;
  max-width: 54ch;
}
.ds-w5-home .featured-collection .text-center .section__text {
  margin-left: auto;
  margin-right: auto;
}
@media only screen and (max-width: 767px) {
  .ds-w5-home .featured-collection .section__heading {
    font-size: calc(var(--bb-text-xl, 2rem) * 1.05);
  }
}

/* @w5-04d-start */
/* === W5-04d — homepage responsive overrides for LEGACY sections (GATED:
   ds-w5-home; round 4, 2026-07-14 owner QA on real devices) ================
   These fix how two old Pipeline mechanisms behave on the recomposed
   homepage. Scoped to the ds-w5-home body class so the current live
   composition is untouched until the flag ships with the new homepage.

   1) Shoppable-blog cards on phones: theme.css forces 2-up from 480-767px
      (--item-width-mobile, theme.css ~32976) and a hard 1:1.5 portrait
      computed from the DESKTOP width var (theme.css ~33116) — the "broken,
      wrong resolution" crop. Owner ruling: 1 card per view on phones,
      shorter card, bigger title.
   2) Section vertical padding: theme scales to 60% on phones (theme.css
      ~6025) but 60px-configured legacy sections still stack to big gaps.
      Cap the resolved padding on the homepage. */
/* Desktop too (owner ruling 2026-07-14 "shorter/denser"): the theme's hard
   1:1.5 portrait card becomes ~square, so the row reads as a compact band
   instead of a wall of tall crops. */
.ds-w5-home .blog-item__article {
  height: calc(var(--item-width) * 1.0);
  min-height: 260px;
}
@media only screen and (max-width: 767px) {
  .ds-w5-home .shoppable-blog__slider {
    --item-width-mobile: calc(var(--wrapper-width) - var(--grid-gutter));
  }
  .ds-w5-home .blog-item__article {
    height: calc(var(--item-width-mobile) * 0.66);
    min-height: 220px;
  }
  .ds-w5-home .blog-item__article__title {
    font-size: 22px;
    line-height: 1.15;
  }
  .ds-w5-home .section-padding {
    --padding-top: min(calc(var(--PT) * 0.6), 28px);
    --padding-bottom: min(calc(var(--PB) * 0.6), 28px);
  }
}
/* @w5-04d-end */

/* @w5-06-start */
/* === W5-06 — sold-out PDP notify-me (GATED: ds-w5-notify) ==================
   Back-in-stock email capture rendered by snippets/bb-notify-me.liquid
   inside the buy-box form wrapper, only when the buy-box shows "Sold out".
   Klaviyo client Back in Stock Subscriptions API; see the snippet header
   for the key-resolution order. Calm, quiet card — it sits under the
   disabled sold-out button and should read as help, not a promo.

   Visibility is double-gated: the body class (flag) shows the block, and
   the theme's own .variant--soldout wrapper class (toggled by theme.js on
   variant change) hides it again the moment a buyable variant is picked. */
.bb-nm {
  display: none;
}
.ds-w5-notify .bb-nm {
  display: block;
  margin-top: 14px;
  padding: 16px;
  background: var(--bb-paper);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-md);
}
/* Theme.js swaps .variant--soldout on [data-form-wrapper] when the shopper
   changes variants — follow it so the form never shows next to a live ATC. */
.ds-w5-notify .product__form__wrapper:not(.variant--soldout) .bb-nm {
  display: none;
}
.ds-w5-notify .bb-nm__help {
  margin: 0 0 10px;
  font-size: var(--bb-text-xs);
  line-height: 1.5;
  color: var(--bb-ink);
}
.ds-w5-notify .bb-nm__form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.ds-w5-notify .bb-nm__input {
  flex: 1 1 180px;
  min-width: 0;
  padding: 10px 14px;
  font-size: var(--bb-text-xs);
  color: var(--bb-ink);
  background: var(--bb-white);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius-xs);
}
.ds-w5-notify .bb-nm__input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--bb-purple-100), 0 0 0 1px var(--bb-purple-500);
}
.ds-w5-notify .bb-nm__btn {
  flex: 0 0 auto;
  padding: 10px 18px;
  font-size: var(--bb-text-xs);
  font-weight: 700;
  color: var(--bb-white);
  background: var(--bb-purple-500);
  border: 0;
  border-radius: var(--bb-radius-xs);
  cursor: pointer;
}
.ds-w5-notify .bb-nm__btn:hover {
  background: var(--bb-purple-600);
}
.ds-w5-notify .bb-nm__btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--bb-purple-100), 0 0 0 1px var(--bb-purple-500);
}
.ds-w5-notify .bb-nm__btn[disabled] {
  opacity: 0.6;
  cursor: default;
}
.ds-w5-notify .bb-nm__msg {
  margin: 10px 0 0;
  font-size: var(--bb-text-2xs);
  line-height: 1.5;
  color: var(--bb-purple-700);
}
.ds-w5-notify .bb-nm__msg--error {
  color: var(--bb-berry);
}
@media (forced-colors: active) {
  .ds-w5-notify .bb-nm__input:focus-visible,
  .ds-w5-notify .bb-nm__btn:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}
/* @w5-06-end */

/* @a11y-p0-start — 2026-07-21 WCAG P0 corrections (docs/a11y-audit-2026-07-20.md)
   Tokens-only, appended per w4-08 close-out §1. Corrections only — render
   changes are limited to the new marquee pause control (which only appears
   where the belt is in motion, <768px). No flags. */

/* --- P0 #2 · press-belt marquee pause/play control (WCAG 2.2.2) -----------
   sections/press-belt.liquid auto-scrolls on phones (<768px). 2.2.2 requires
   a user-operable pause mechanism; prefers-reduced-motion is not one. This is
   a CSS-only checkbox toggle (checked = paused): keyboard-operable (Tab +
   Space), no JS. Where the control shows (<768px, motion on) the checkbox is
   visually hidden but focusable; its focus ring transfers to the visible
   label. Everywhere the label is hidden (desktop static belt, reduced-motion)
   the input must be display:none TOO — a clipped-but-focusable input with no
   visible label would be an invisible tab stop (fails 2.4.7). So default is
   display:none; the mobile block re-enables it alongside the label. */
.bb-w5h-belt__pause {
  display: none;
}
.bb-w5h-belt__pause-input {
  display: none;
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.bb-w5h-belt__pause-txt--paused {
  display: none;
}
@media only screen and (max-width: 767px) {
  .bb-w5h-belt__pause-input {
    display: block;
  }
  .bb-w5h-belt__pause {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 6px;
    padding: 6px 14px;
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--bb-purple-600);
    background: var(--bb-paper);
    border: 1px solid var(--bb-purple-300);
    border-radius: var(--bb-radius-pill);
    cursor: pointer;
  }
  .bb-w5h-belt__pause-icon {
    flex: 0 0 auto;
    width: 8px;
    height: 10px;
    /* two bars = "pause" (playing state, action = pause) */
    border-left: 3px solid currentColor;
    border-right: 3px solid currentColor;
  }
  /* checked = paused: swap the label to "Play" + a triangle glyph */
  .bb-w5h-belt__pause-input:checked ~ .bb-w5h-belt__pause .bb-w5h-belt__pause-txt--playing {
    display: none;
  }
  .bb-w5h-belt__pause-input:checked ~ .bb-w5h-belt__pause .bb-w5h-belt__pause-txt--paused {
    display: inline;
  }
  .bb-w5h-belt__pause-input:checked ~ .bb-w5h-belt__pause .bb-w5h-belt__pause-icon {
    width: 0;
    height: 0;
    border-left: 8px solid currentColor;
    border-right: 0;
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
  }
  /* checked = paused: stop the marquee animation */
  .bb-w5h-belt__pause-input:checked ~ .bb-w5h-belt__viewport .bb-w5h-belt__track {
    animation-play-state: paused;
  }
  /* transfer the hidden checkbox's focus ring to the visible label */
  .bb-w5h-belt__pause-input:focus-visible ~ .bb-w5h-belt__pause {
    outline: 2px solid var(--bb-purple-500);
    outline-offset: 2px;
  }
}
@media (prefers-reduced-motion: reduce) and (max-width: 767px) {
  /* no auto-motion here, so no control is needed — hide the input too,
     or it would remain an invisible tab stop (2.4.7) */
  .bb-w5h-belt__pause,
  .bb-w5h-belt__pause-input {
    display: none;
  }
}
/* --- P0 #4 · footer newsletter input contrast (WCAG 1.4.3) ----------------
   #Email--footer-block-3 (.newsletter-form .input-group__field) failed on 7
   scans. Root cause: the footer section background is purple-500 (#5A4FE8,
   sections/group-footer.json color_bg) and theme.css forces
   `.newsletter-form .input-group__field { background: transparent }` (0,2,0),
   which defeats dsv2's W2-02 paper fill (0,1,0). The dark field text (--fg-1)
   and placeholder (--fg-3) then sit directly on purple: ~3.1:1 and ~1.1:1.
   No dark text can reach 4.5:1 on #5A4FE8 (pure black is only 3.7:1), so the
   only real fix is restoring an opaque light field surface — exactly the
   W2-02 design intent ("the field surface is always paper"). On --bb-paper
   the value text is ~17:1 and the placeholder (--fg-3) is ~6.2:1, both AA.
   The --fg-3 token is unchanged (it already passes on paper and cream).
   dsv2 loads after theme.css, so equal specificity (0,2,0) wins here. */
.newsletter-form .input-group__field {
  background: var(--bb-paper);
}
/* @a11y-p0-end */
