/* ==========================================================================
   bundle-builder.css — presentation for the theme-owned `bundle_builder`
   section. Token-only: every colour, radius, space and font resolves through
   layered var() fallbacks so the same file works on a --clr-* theme, a
   --color-* theme, or a theme with neither.

   Nothing in here knows about a specific bundle, product, group or vendor.
   ========================================================================== */

.bb {
  --bb-text:        var(--clr-body,    var(--color-body,    #1f2937));
  --bb-heading:     var(--clr-primary, var(--color-primary, #111827));
  --bb-muted:       var(--clr-muted,   var(--color-muted,   #6b7280));
  --bb-surface:     var(--clr-white,   var(--color-white,   #ffffff));
  --bb-surface-alt: var(--clr-light,   var(--color-light,   #fafafa));
  --bb-line:        var(--clr-gray,    var(--color-gray,    #e5e7eb));
  --bb-accent:      var(--clr-accent,  var(--color-accent,  var(--clr-primary, #111827)));
  --bb-on-accent:   var(--clr-white,   var(--color-white,   #ffffff));
  --bb-success:     var(--clr-success, var(--color-success, #10b981));
  --bb-warning:     var(--clr-warning, var(--color-warning, #b45309));
  --bb-radius:      var(--rounded-lg,  var(--radius-lg, 12px));
  --bb-radius-sm:   var(--rounded-sm,  var(--radius-sm, 8px));
  --bb-gap:         var(--space-md, 16px);

  /* Media sizing tokens.
     `--bb-option-min` is the option grid's column floor; an option card pads
     itself by `--bb-option-pad` on each side, so the option THUMBNAIL is
     (column - 2 * pad) wide. The included item is the anchor of the bundle, so
     its thumbnail is one spacing step LARGER than a whole option column —
     never smaller than the option thumbnail it sits above. */
  --bb-option-min:      116px;
  --bb-option-pad:      8px;
  --bb-included-size:   calc(var(--bb-option-min) + var(--space-md, 16px));

  /* Content shell. These MATCH the other PDP sections in this theme
     (related_products, pdp_ingredients, pdp_stories: 1280px wide, 64px side
     padding, stepping to 24px then 16px) so every section's content shares one
     left/right edge down the page. Change them together or the bundle will
     visibly hang out past its neighbours. */
  --bb-container-max:   1280px;
  --bb-container-pad:   64px;

  color: var(--bb-text);
  font-family: var(--ff-body, inherit);
}

/* --------------------------------------------------------------------------
   Scoped compat layer.
   Host themes commonly (a) hijack `button, .btn` into a black flex box,
   (b) set -webkit-appearance:none on inputs, which makes native radios and
   checkboxes invisible, and (c) declare `ul, li { list-style: none }`, which
   kills bullets in rich text. Everything below is scoped to `.bb` and only
   undoes those three collisions inside this section.
   -------------------------------------------------------------------------- */
.bb button.bb__control,
.bb .btn.bb__control {
  -webkit-appearance: none;
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  min-width: 0;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  color: inherit;
  font: inherit;
  line-height: inherit;
  letter-spacing: normal;
  text-transform: none;
  text-align: left;
  cursor: pointer;
}

/* Native controls: keep them real (and reachable) rather than relying on the
   host theme not having nulled their appearance. They are visually replaced by
   .bb__mark but remain focusable, announced and keyboard-operable. */
.bb input[type="radio"].bb__input,
.bb input[type="checkbox"].bb__input {
  -webkit-appearance: none;
  appearance: none;
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

.bb .bb__prose ul { list-style: disc outside; padding-left: 1.25em; }
.bb .bb__prose ol { list-style: decimal outside; padding-left: 1.25em; }
.bb .bb__prose li { list-style: inherit; display: list-item; }

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */

/* Content shell. The section element itself stays full-bleed so a background
   can still run edge to edge; the container is what holds the content in.
   Without this rule the section had NO horizontal padding at all and the hero,
   option grid and Add-to-cart button ran into both edges of the viewport. */
.bb__container {
  max-width: var(--bb-container-max);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--bb-container-pad);
  padding-right: var(--bb-container-pad);
}

/* These two breakpoints are deliberately the NEIGHBOURING SECTIONS' (991/767),
   not this section's own (900/767/640): they exist to keep the content edge
   aligned with the sections above and below, so they have to change where
   those change. */
@media (max-width: 991px) { .bb { --bb-container-pad: 24px; } }
@media (max-width: 767px) { .bb { --bb-container-pad: 16px; } }

.bb__head { display: grid; gap: 6px; margin-bottom: var(--bb-gap); }

.bb__title {
  margin: 0;
  font-family: var(--ff-heading, inherit);
  font-size: var(--fs-h3, 1.5rem);
  line-height: 1.15;
  color: var(--bb-heading);
}

.bb__subtitle { margin: 0; color: var(--bb-muted); font-size: var(--fs-caption, 0.875rem); }

/* ── Two-column shell ──────────────────────────────────────────────────────
   The product shot used to sit in the vertical flow above the groups, costing
   a full screen of scroll before the first option. It now occupies a sticky
   left column while the groups scroll past it in the right rail.

   The media column is deliberately capped narrow: every pixel it takes is a
   pixel the option grid can't use for another column, and fitting more of the
   bundle on one screen is the whole point of this layout. */
.bb__layout {
  display: grid;
  grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
  gap: var(--space-xl, 32px);
  align-items: start;
}

.bb__media {
  position: sticky;
  /* Clears the sticky site header; falls back to a safe value if unset. */
  top: var(--bb-sticky-top, 96px);
}

/* min-width:0 lets the rail's option grid shrink inside the grid track —
   without it a large option grid forces the column wider and overflows. */
.bb__rail { min-width: 0; }

.bb__hero {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  border-radius: var(--bb-radius);
  background: var(--bb-surface-alt);
}

/* Price + running total + CTA pin to the bottom of the viewport so the cost
   of a configuration stays visible while the shopper scrolls the options. */
.bb .bb__footer {
  position: sticky;
  bottom: 0;
  z-index: 2;
  padding-bottom: var(--bb-gap);
  background: var(--clr-white, #ffffff);
  box-shadow: 0 -10px 18px -14px rgba(0, 0, 0, 0.35);
}

@media (max-width: 900px) {
  .bb__layout {
    grid-template-columns: 1fr;
    gap: var(--space-md, 16px);
  }

  .bb__media {
    position: static;
    width: 100%;
    max-width: 300px;
    margin-inline: auto;
  }
}

.bb__groups { display: grid; gap: var(--space-lg, 24px); }

/* NOTE: there is no longer a `.bb__branches` tab strip. Mutually-exclusive
   alternatives all render at once, each panel carrying its own chooser in its
   header — see ensureBranchChoosers() in bundle-builder.js. */

/* 🔴 These are the ONLY way to reach a mutually-exclusive branch, and they
   used to read as plain static text: no border, no pointer, and an active
   state of `background: var(--bb-surface)` — near-white on the strip's
   near-white `--bb-surface-alt`, i.e. invisible in practice. On the Dani test
   bundle the default branch is a single-card group while the rich 5-product
   group sits behind the other tab, so a shopper (and the operator QA'ing it)
   sees a sparse page and never learns there is anything to click. Treat this
   as a real control: obvious affordance, unmistakable selected state. */
/* Specificity, deliberately: these buttons also carry `bb__control`, and the
   later `.bb button.bb__control` rules (0,2,1) outrank a bare `.bb__branch`
   (0,1,0) — which is why the first attempt at this shipped, was confirmed
   present in the delivered stylesheet, and still rendered as plain text.
   `.bb .bb__branches .bb__branch` is (0,3,0) and wins. Do not simplify. */
.bb .bb__group-head .bb__branch {
  flex: 0 0 auto;
  gap: 7px;
  padding: 7px 13px;
  border: 1px solid var(--bb-line, #e5e7eb);
  border-radius: var(--bb-radius-sm);
  font-size: var(--fs-caption, 0.875rem);
  font-weight: 600;
  color: var(--bb-heading);
  background: var(--clr-white, #ffffff);
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.bb .bb__group-head .bb__branch:hover { border-color: currentColor; }
.bb .bb__group-head .bb__branch:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }

/* The radio dot: this is a one-of-N choice, so it should look like one rather
   than like a toggle or a tab. */
.bb__branch-dot {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  border: 2px solid var(--bb-line, #e5e7eb);
  border-radius: 50%;
  background: var(--clr-white, #ffffff);
}

/* Match BOTH state hooks: the engine sets aria-pressed, and the server frame
   marks the active branch with is-active. Relying on one alone left the strip
   with no visible selection in whichever pass didn't set it. */
.bb .bb__group-head .bb__branch[aria-pressed="true"] {
  border-color: transparent;
  background: var(--bb-accent, var(--clr-primary, #e51636));
  color: var(--clr-white, #ffffff);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

.bb .bb__group-head .bb__branch[aria-pressed="true"] .bb__branch-dot {
  border-color: var(--clr-white, #ffffff);
  box-shadow: inset 0 0 0 3px var(--bb-accent, var(--clr-primary, #e51636));
}

/* THE UNCHOSEN ALTERNATIVE. Present and readable, unmistakably not the current
   choice, and clickable as a whole panel.

   Deliberately NOT `pointer-events: none`: a click anywhere inside it CLAIMS
   that branch (see the member guard in bind()). That is the discoverable
   affordance the old tab strip never was, so the cards must keep receiving
   clicks — including the included cards, which otherwise sit at
   `cursor: default`. */
.bb__group.is-branch-inactive { opacity: 0.5; cursor: pointer; }
.bb__group.is-branch-inactive:hover { opacity: 0.72; }
.bb__group.is-branch-inactive .bb__option { cursor: pointer; }

/* An unchosen alternative is a PREVIEW — "here is what this option contains" —
   so its variant pickers collapse. They are not actionable anyway (a click
   claims the branch instead), and a 20-pill scroller inside a dimmed panel both
   buried the comparison and clipped its own last label mid-word. Collapsed, the
   two alternatives can be read side by side; the pickers return the moment the
   option is chosen.

   The second selector is the load-bearing one: `.bb__option.is-selected
   .bb__sizes` above is (0,3,0), so (0,4,0) is required to close it again. */
.bb__group.is-branch-inactive .bb__option .bb__sizes,
.bb__group.is-branch-inactive .bb__option.is-selected .bb__sizes { display: none; }

/* "Included" is a CLAIM, and on an unchosen alternative it is false — nothing
   in that branch is included in anything until the shopper picks it. So the
   group's "Included" chip and every card's "Included" label are suppressed
   while the branch is not the chosen one; they return the moment it is.

   `:not([data-bb-count])` distinguishes the included group's static "Included"
   chip from a choice group's live "1 / 3" counter, which shares the class. */
.bb__group.is-branch-inactive .bb__count:not([data-bb-count]),
.bb__group.is-branch-inactive .bb__option-incl { display: none; }

.bb__group { display: grid; gap: 12px; }
.bb__group[hidden] { display: none; }

/* `flex-wrap` matters now that a branch chooser is a THIRD child here, beside
   the title and the count chip. Without it, a narrow viewport squeezed the
   group title down to a couple of characters per line to keep all three on one
   row. */
.bb__group-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 12px; }

.bb__group-title {
  margin: 0;
  font-family: var(--ff-heading, inherit);
  font-size: var(--fs-h5, 1.125rem);
  color: var(--bb-heading);
}

.bb__group-desc { margin: 0; color: var(--bb-muted); font-size: var(--fs-caption, 0.875rem); }

.bb__count {
  flex: 0 0 auto;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--bb-surface-alt);
  color: var(--bb-muted);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.bb__count.is-complete { background: var(--bb-success); color: var(--bb-on-accent); }
.bb__count.is-over { background: var(--bb-warning); color: var(--bb-on-accent); }

/* Included (auto-added) groups */
.bb__included {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius);
  background: var(--bb-surface-alt);
}

/* The included item is the hero of the bundle, so its thumbnail is a step
   LARGER than an option thumbnail rather than smaller. Size comes from the
   shared media tokens above, so it can never drift out of step with the
   option grid. */
.bb__included-media {
  width: var(--bb-included-size);
  height: var(--bb-included-size);
  object-fit: contain;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--bb-surface);
}
.bb__included img { flex: 0 0 auto; }

/* Keeps the "Included: <item>" label on the row next to a large thumbnail:
   it may wrap, but it must never be pushed out or force a horizontal scroll. */
.bb__included-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.bb__included-label { font-weight: 700; }

  /* An included group is now a row of PRODUCT cards with a variant picker, not
     read-only text: the admin models "these products come with the bundle,
     pick each one's variant". The card cannot be deselected, so it shows an
     "Included" chip where a choice card shows its price delta. */
  .bb__option--included { cursor: default; }

  /* An included card's picker is ALWAYS open (the card is always selected), so
     the choice-card rules need three overrides here:
       • `align-items: start` — the default stretch made every card as tall as
         the tallest. With one product exposing 20 variant combinations that
         stretched a 5-card row to ~1000px.
       • `margin-top: 0` — `.bb__sizes` uses `margin-top: auto` to pin pills to
         the bottom of a stretched choice card. On a non-stretched included
         card that pushed the picker hundreds of pixels below its own product
         name, so the Nuggets and Fries pickers looked like they were missing.
       • a scroll cap — 20 legitimate options must not become an endless wall.
     Wider columns because these labels carry the full option combination
     ("Colby jack Multigrain brioche bun"), not just "Medium". */
  .bb__options--included {
    align-items: start;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  }
  .bb__options--included .bb__sizes { margin-top: 6px; }
  /* 🔴 A long option list has to LOOK scrollable, not broken. `max-height`
     alone sliced the last visible pill through the middle of its own text,
     which reads as a rendering fault rather than "there is more below" — and
     macOS overlay scrollbars are invisible at rest, so there was no affordance
     at all until the shopper happened to scroll. Three changes:
       • a soft bottom fade, so the boundary is a gradient rather than a cut;
       • an always-visible thin scrollbar, so the affordance exists up front;
       • overscroll containment, so reaching the end of the pills does not
         start scrolling the page underneath the cursor.
     `padding-bottom` keeps the final pill clear of the fade once scrolled. */
  .bb__options--included .bb__sizes-row {
    max-height: 190px;
    overflow-y: auto;
    align-content: start;
    padding: 2px 6px 12px 0;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--bb-line, #e5e7eb) transparent;
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 16px), transparent 100%);
            mask-image: linear-gradient(to bottom, #000 calc(100% - 16px), transparent 100%);
  }
  .bb__options--included .bb__sizes-row::-webkit-scrollbar { width: 6px; }
  .bb__options--included .bb__sizes-row::-webkit-scrollbar-track { background: transparent; }
  .bb__options--included .bb__sizes-row::-webkit-scrollbar-thumb {
    background: var(--bb-line, #e5e7eb);
    border-radius: 999px;
  }
  .bb__option-incl {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--bb-muted, #6b7280);
  }
  /* An included group can carry MANY variants of one product (a test bundle in
     this company holds 20 Deluxe Sandwich variants in a single included group).
     The row names the product once, badges how many of its variants are in the
     bundle, and lists them on a quiet second line so a long list stays legible
     instead of becoming 20 identical rows. */
  .bb__included-count {
    display: inline-block;
    margin-left: 2px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--bb-line, #e5e7eb);
    font-size: 11px;
    font-weight: 700;
    vertical-align: 2px;
  }
  .bb__included-variants {
    display: block;
    margin-top: 3px;
    font-size: 12px;
    line-height: 1.45;
    color: var(--bb-muted, #6b7280);
    overflow-wrap: anywhere;
  }

/* Options */
.bb__options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--bb-option-min), 1fr));
  gap: 8px;
  margin: 0;
  padding: 0;
}

.bb__option {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: var(--bb-option-pad);
  border: 1px solid var(--bb-line);
  border-radius: var(--bb-radius);
  background: var(--bb-surface);
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.bb__option:hover { border-color: var(--bb-accent); }
.bb__option.is-selected { border-color: var(--bb-accent); box-shadow: inset 0 0 0 1px var(--bb-accent); }
.bb__option.is-disabled { opacity: 0.45; cursor: not-allowed; }
.bb__option:focus-within { outline: 2px solid var(--bb-accent); outline-offset: 2px; }

.bb__mark {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border: 2px solid var(--bb-line);
  border-radius: 50%;
  background: var(--bb-surface);
  color: transparent;
}

.bb__mark svg { width: 12px; height: 12px; display: block; }

.bb__option--multi .bb__mark { border-radius: 6px; }

.bb__option.is-selected .bb__mark {
  border-color: var(--bb-accent);
  background: var(--bb-accent);
  color: var(--bb-on-accent);
}

.bb__option-media {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  background: var(--bb-surface-alt);
  border-radius: 50%;
}

.bb__option-media--empty { background: var(--bb-surface-alt); }

/* Clamped to two lines: a grid row is as tall as its tallest card, so one
   long product name would otherwise add a line of height to every card in
   that row. The full name stays in the DOM (announced, and shown on hover
   via the card's title attribute) — only the painted lines are capped. */
.bb__option-name {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  width: 100%;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  overflow-wrap: anywhere;
}

.bb__option-price { font-size: 11px; color: var(--bb-muted); text-align: center; }
.bb__option-price[hidden] { display: none; }

/* --------------------------------------------------------------------------
   Size sub-picker — rendered ONLY for a product that contributes more than one
   variant to its group. Labels and price deltas are supplied by the section
   from the variant's own option data; nothing here knows a size name.
   -------------------------------------------------------------------------- */
/* Progressive disclosure: the pills only exist for the card the shopper has
   actually chosen. Three labelled pills stack on a ~116px card and were ~80px
   of every card that carries them — by far the largest remaining block of
   scroll. A size can only be set on a selection anyway, so an unselected card
   showing a size picker was offering a control that does nothing yet.

   display:none (not visibility/opacity) so the hidden pills also leave the
   tab order and the a11y tree — a keyboard user reaches them the moment the
   card becomes selected, and never tabs through dozens of dead pickers to get
   to the next option. Deliberately NOT revealed on :hover: that would reflow
   the whole grid under the cursor. */
.bb__sizes {
  display: none;
  gap: 0;
  width: 100%;
  margin-top: auto;
  padding-top: 6px;
  border-top: 1px dashed var(--bb-line);
}

/* 0-3-0, comfortably over the 0-1-0 base above. :focus-within keeps the picker
   open while a pill inside it holds focus. */
.bb__option.is-selected .bb__sizes,
.bb__option:focus-within .bb__sizes {
  display: grid;
}

/* The visible "Size" caption is redundant next to self-describing pills and
   cost a line of height on nearly every card in the grid. The group keeps its
   aria-label, so assistive tech still announces what the pills are. */
.bb__sizes-label { display: none; }

.bb__sizes-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 3px;
}

/* NOTE: the scoped compat reset is `.bb button.bb__control` (0-2-1). These
   rules are 0-3-1 so they win. */
.bb button.bb__size.bb__control,
.bb .btn.bb__size.bb__control {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  /* `baseline` misaligns the name against its price delta once the name wraps
     onto a second line; center keeps a multi-line pill visually balanced. */
  align-items: center;
  gap: 3px;
  padding: 3px 8px;
  border: 1px solid var(--bb-line);
  border-radius: 999px;
  background: var(--bb-surface);
  color: var(--bb-heading);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.15;
  /* 🔴 This was `white-space: nowrap`, which silently assumed every option
     label is as short as "Small"/"Medium"/"Large". A bundle whose axis is a
     drink flavour ("Coke® zero sugar float") or a sandwich build
     ("Colby jack Multigrain brioche bun") produced pills far wider than the
     ~116px option card, so they overflowed the card's borders, clipped, and
     collided with whatever sat below the grid.

     Long labels now wrap INSIDE the pill and the pill is capped to the card,
     so the card grows taller instead of the content escaping sideways. Keep
     this whenever the option pool is unknown — which, for a universal bundle
     theme, is always. */
  white-space: normal;
  overflow-wrap: anywhere;
  max-width: 100%;
  min-width: 0;
  text-align: center;
}

.bb button.bb__size.bb__control:hover,
.bb .btn.bb__size.bb__control:hover { border-color: var(--bb-accent); }

.bb button.bb__size.bb__control.is-active,
.bb .btn.bb__size.bb__control.is-active {
  border-color: var(--bb-accent);
  background: var(--bb-accent);
  color: var(--bb-on-accent);
}

.bb__size-delta { font-size: 10px; font-weight: 600; opacity: 0.85; }

.bb__stepper { display: inline-flex; align-items: center; gap: 8px; margin-top: 2px; }

.bb button.bb__step.bb__control,
.bb .btn.bb__step.bb__control {
  width: 26px;
  height: 26px;
  border: 1px solid var(--bb-line);
  border-radius: 50%;
  font-size: 15px;
  font-weight: 700;
  color: var(--bb-heading);
  background: var(--bb-surface);
  justify-content: center;
}

.bb button.bb__step.bb__control[disabled],
.bb .btn.bb__step.bb__control[disabled] { opacity: 0.35; cursor: not-allowed; }
.bb__step-value { min-width: 1.5em; text-align: center; font-weight: 700; }

/* Summary + CTA */
.bb__footer {
  display: grid;
  gap: 12px;
  margin-top: var(--space-lg, 24px);
  padding-top: var(--bb-gap);
  border-top: 1px solid var(--bb-line);
}

.bb__summary { display: grid; gap: 4px; font-size: var(--fs-caption, 0.875rem); }
.bb__summary-row { display: flex; justify-content: space-between; gap: 12px; }
.bb__summary-row--total { font-weight: 700; color: var(--bb-heading); }

/* ── Purchase mode (one-time vs subscribe) ─────────────────────────────────
   Sits directly above the CTA, because it changes what the shopper is
   agreeing to pay and how often. Specificity is `.bb .bb__sub .bb__sub-opt`
   (0,3,0) on purpose: these buttons also carry `bb__control`, whose reset is
   (0,2,1) and would otherwise strip them back to bare inline text. */
.bb__sub { margin-bottom: 10px; }

.bb__sub-choice {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(178px, 1fr));
  gap: 8px;
}

.bb .bb__sub .bb__sub-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--bb-line, #e5e7eb);
  border-radius: var(--bb-radius-sm);
  background: var(--bb-surface, #ffffff);
  color: var(--bb-heading);
  font-size: var(--fs-caption, 0.875rem);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.bb .bb__sub .bb__sub-opt:hover { border-color: var(--bb-accent); }
.bb .bb__sub .bb__sub-opt:focus-visible { outline: 2px solid var(--bb-accent); outline-offset: 2px; }
.bb .bb__sub .bb__sub-opt.is-active {
  border-color: var(--bb-accent);
  box-shadow: inset 0 0 0 1px var(--bb-accent);
}

.bb__sub-dot {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  border: 2px solid var(--bb-line, #e5e7eb);
  border-radius: 50%;
  background: var(--bb-surface, #ffffff);
}
.bb .bb__sub .bb__sub-opt.is-active .bb__sub-dot {
  border-color: var(--bb-accent);
  box-shadow: inset 0 0 0 3px var(--bb-accent);
}

.bb__sub-text { flex: 1 1 auto; min-width: 0; }
.bb__sub-every { font-style: normal; font-weight: 400; color: var(--bb-muted); }

.bb__sub-save {
  flex: 0 0 auto;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bb-success, #10b981);
  color: var(--bb-on-accent, #ffffff);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Subscription-only: a statement, not a control. Styled as a notice rather
   than a button so it never invites a click that does nothing. */
.bb__sub-locked {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 10px 12px;
  border: 1px solid var(--bb-accent);
  border-radius: var(--bb-radius-sm);
  background: var(--bb-surface-alt, #fafafa);
}
.bb__sub-badge {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bb-accent);
  color: var(--bb-on-accent, #ffffff);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.bb__sub-note {
  font-size: var(--fs-caption, 0.875rem);
  font-weight: 600;
  color: var(--bb-heading);
}

/* A chosen selection is imposing the schedule. The one-time option is locked
   rather than removed, so the shopper can still see it exists and understand
   that a different choice would unlock it. */
.bb__sub-reason {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--bb-heading);
}
.bb__sub-reason[hidden] { display: none; }

.bb .bb__sub .bb__sub-opt[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  border-color: var(--bb-line, #e5e7eb);
  box-shadow: none;
}
.bb__sub.is-forced .bb__sub-choice { border-left: 3px solid var(--bb-accent); padding-left: 9px; }

/* PER-SELECTION SUBSCRIPTION CONTROL.
   OFF  → quiet grey outline: an offer the shopper has not taken.
   ON   → solid accent + checkmark: a commitment they can see.
   Forced items render ON and disabled, labelled "Subscription Included".

   Specificity is `.bb .bb__option .bb__option-sub` (0,3,0) on purpose: this is
   a <button> carrying `bb__control`, whose scoped reset is (0,2,1) and would
   otherwise strip these rules back to bare inline text. */
.bb .bb__option .bb__option-sub {
  display: block;
  width: 100%;
  padding: 2px 6px;
  border: 1px solid var(--bb-line, #e5e7eb);
  border-radius: 999px;
  background: transparent;
  color: var(--bb-muted, #6b7280);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.05em;
  line-height: 1.5;
  text-transform: uppercase;
  text-align: center;
  overflow-wrap: anywhere;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}

.bb .bb__option .bb__option-sub:hover {
  border-color: var(--bb-accent);
  color: var(--bb-heading);
}
.bb .bb__option .bb__option-sub:focus-visible { outline: 2px solid var(--bb-accent); outline-offset: 2px; }

.bb .bb__option .bb__option-sub.is-on {
  border-color: transparent;
  background: var(--bb-accent);
  color: var(--bb-on-accent, #ffffff);
}
.bb .bb__option .bb__option-sub.is-on::before { content: "\2713\00a0"; }

/* Locked — forced, implied by the whole-bundle Subscribe choice, or an
   included item the cart reconstitutes server-side. Deliberately NOT dimmed:
   a faded badge reads as "unavailable" when the truth is "already settled". */
.bb .bb__option .bb__option-sub[disabled] { cursor: default; opacity: 1; }
.bb .bb__option .bb__option-sub[disabled]:hover { border-color: transparent; color: inherit; }
.bb .bb__option .bb__option-sub[disabled]:not(.is-on) {
  border-color: var(--bb-line, #e5e7eb);
  color: var(--bb-muted, #6b7280);
}

/* (0,4,0) so it outranks the display:block above — the same specificity trap
   that made an earlier `[hidden]` rule silently lose. */
.bb .bb__option .bb__option-sub[hidden] { display: none; }

.bb__price-row { display: flex; align-items: baseline; gap: 10px; }
.bb__price { font-size: var(--fs-h4, 1.25rem); font-weight: 700; color: var(--bb-heading); }
/* A min–max range is about twice as long as a single total, so it steps down one
   size rather than pushing the Add-to-cart control out of the footer row. */
.bb__price--range { font-size: var(--fs-body, 1rem); }

/* NOTE: the scoped compat reset above is `.bb button.bb__control` (0-2-1).
   Any rule that re-dresses a control must MATCH OR EXCEED that specificity or
   the reset silently wins and the control renders as bare inline text. */
.bb button.bb__cta.bb__control,
.bb .btn.bb__cta.bb__control {
  width: 100%;
  min-height: 52px;
  padding: 14px 24px;
  border-radius: var(--bb-radius);
  background: var(--bb-accent);
  color: var(--bb-on-accent);
  font-family: var(--ff-heading, inherit);
  font-size: var(--fs-body, 1rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-align: center;
  justify-content: center;
}

.bb button.bb__cta.bb__control[disabled],
.bb .btn.bb__cta.bb__control[disabled] { opacity: 0.5; cursor: not-allowed; }

.bb__notice {
  margin: 0;
  font-size: var(--fs-caption, 0.875rem);
  color: var(--bb-warning);
}

.bb__notice[hidden] { display: none; }

@media (max-width: 640px) {
  /* Narrow the option column; the included thumbnail follows it automatically
     through --bb-included-size and stays a step larger than an option thumb.
     The step widens to --space-lg here because a narrow grid stretches each
     option column well past its floor, so the small step would nearly tie. */
  .bb {
    --bb-option-min:    104px;
    --bb-included-size: calc(var(--bb-option-min) + var(--space-lg, 24px));
  }
}

/* MOBILE LEGIBILITY FLOOR.
   This theme's global type scale steps down hard below 768px
   (--fs-h5 → 10px, --fs-h4 → 13px, --fs-caption → 8px in assets/config.css),
   which would render the group titles, helper copy and the running TOTAL at
   8–10px on a 390px viewport — unreadable for the one control a shopper has
   to read in order to spend money. The builder pins its own floor here rather
   than changing the global tokens, so nothing outside this component moves. */
@media (max-width: 767px) {
  .bb__title { font-size: 22px; }
  .bb__group-title { font-size: 16px; }
  .bb__subtitle,
  .bb__group-desc,
  .bb__notice { font-size: 13px; }
  .bb__price { font-size: 22px; }
  .bb__price--range { font-size: 16px; }
  .bb button.bb__cta.bb__control,
  .bb .btn.bb__cta.bb__control { font-size: 15px; }
  .bb__sizes-label { font-size: 12px; }
}

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