/* public/css/shop.css
 *
 * Fantasy Goblin Shop foundation (Phase 2B) -- styling scoped to the
 * /shop pages (View/Shop/*.ejs) only. Kept in its own file rather than
 * folded into public/css/style.css so this foundation's styling can grow
 * independently without turning the global stylesheet into a dumping
 * ground -- every selector below is prefixed `.shop-` and is meaningless
 * outside View/Shop/*.ejs.
 *
 * Reuses this codebase's existing design tokens (public/css/style.css's
 * :root custom properties: --df-surface, --df-surface-alt,
 * --df-border-subtle, --df-text-light, --df-text-muted, --df-GoblinGold,
 * --color-good, --color-bad, --df-radius, --df-shadow) rather than
 * inventing a second palette -- these pages should read as part of Fantasy
 * Goblin, not a bolted-on storefront.
 *
 * Mobile-first: every rule below targets a single-column/stacked layout by
 * default; the `min-width` media query near the bottom is the ONLY place
 * a wider (tablet/desktop) grid is introduced.
 */

.shop-page {
  --shop-gap: 1rem;
}

/* ── Goblin avatar wrappers -- sizing only, never touches the SVG figure
   itself (that's public/css/goblinville.css's own .goblinville-npc--*
   classes, applied via the sizeClass passed to _userGoblinAvatar.ejs). ── */
.shop-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
}

.shop-avatar--large {
  min-height: 96px;
}

/* ── Compact summary cards (goblin toolbar, cart-header goblin strip) ──── */
.shop-card--compact .card-body {
  padding: .85rem 1.1rem;
}

.shop-toolbar .shop-cart-link {
  white-space: nowrap;
}

/* ── Status/availability badges -- small, consistent pill labels ───────── */
.shop-badge {
  display: inline-block;
  padding: .15rem .55rem;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
}

.shop-badge--available {
  background: color-mix(in srgb, var(--color-good) 22%, transparent);
  color: var(--color-good);
}

.shop-badge--unavailable {
  background: color-mix(in srgb, var(--color-bad) 22%, transparent);
  color: var(--color-bad);
}

.shop-badge--internal {
  background: color-mix(in srgb, var(--df-GoblinGold) 25%, transparent);
  color: var(--df-GoblinGold);
}

/* Phase 2E: coming_soon's OWN public-facing badge -- deliberately a
   different color from --internal above (that one reads "staff only,"
   this one is customer-facing marketing copy, never confusable with the
   admin-only internal-test marker). */
.shop-badge--coming-soon {
  background: color-mix(in srgb, var(--df-text-light) 18%, transparent);
  color: var(--df-text-light);
}

/* Fixed-Artwork Printful Support, Phase 3B -- a fixed-design (printful_synced)
   product's own badge: neutral, informational -- never the attention-
   grabbing gold --sale treatment, and never confusable with --internal
   (staff-only) or --coming-soon (marketing) above. */
.shop-badge--fixed-design {
  background: color-mix(in srgb, var(--df-text-light) 12%, transparent);
  color: var(--df-text-light);
}

/* Merch Sale-Pricing Phase 3 -- "New Release Sale" is a SOLID gold fill
   (never the translucent tint --internal/--coming-soon use above), the
   same treatment public/css/shop.css's own active-state controls already
   give --df-GoblinGold elsewhere (e.g. .shop-product__swatch.is-active) --
   deliberately the most attention-grabbing badge on the page, since a
   customer needs to notice it, but still just one small pill next to the
   price/title, never a banner. */
.shop-badge--sale {
  background: var(--df-GoblinGold);
  color: var(--df-accent-contrast);
}

/* ── Sale price pair: crossed-out regular + prominent sale amount ────────
   Shared by View/Shop/_priceDisplay.ejs everywhere a price is shown
   (catalog card, product detail, cart line) -- font-SIZE is intentionally
   left to each caller's own existing price container
   (.shop-product-card__price / .shop-product__price / a cart line's own
   small text), so this only ever governs color/weight/strikethrough, never
   layout. */
.shop-price__regular {
  color: var(--df-text-muted);
  font-weight: 400;
  margin-right: .4em;
  /* Accessibility: strikethrough alone never carries the meaning -- the
     visually-hidden "Regular price: " label inside this span is the real
     signal for assistive tech; a slightly heavier line here just keeps the
     strike legible at small sizes for sighted users too. */
}

.shop-price__regular s {
  text-decoration-thickness: 1.5px;
}

.shop-price__sale {
  color: var(--df-GoblinGold);
  font-weight: 700;
}

/* ── Product grid -- consistent card proportions, mobile-first single
   column, widening at the tablet breakpoint below. ─────────────────────── */
.shop-product-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--shop-gap);
}

.shop-product-card {
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, var(--df-surface), var(--df-surface-alt));
  border: 1px solid var(--df-border-subtle);
  border-radius: var(--df-radius);
  box-shadow: var(--df-shadow);
  overflow: hidden;
}

.shop-product-card__media {
  display: block;
  aspect-ratio: 4 / 3;
  background: var(--df-surface-alt);
}

.shop-product-card__media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: var(--df-surface-alt);
}

.shop-product-card__placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--df-text-muted);
  font-size: .85rem;
  background:
    repeating-linear-gradient(
      45deg,
      var(--df-surface-alt),
      var(--df-surface-alt) 10px,
      var(--df-surface) 10px,
      var(--df-surface) 20px
    );
}

.shop-product-card__body {
  padding: .85rem 1rem 1rem;
  display: flex;
  flex-direction: column;
  gap: .25rem;
}

.shop-product-card__title {
  font-size: 1rem;
  font-weight: 600;
  color: var(--df-text-light);
  margin: 0;
}

.shop-product-card__price {
  color: var(--df-text-light);
  font-weight: 600;
}

/* Phase 2E.1: the coming_soon card is never an interactive product link
   (View/Shop/_productCard.ejs's own header comment) -- a slightly muted
   media area is the only visual difference; the badge + copy already say
   "not purchasable yet", this just avoids the card looking clickable. */
.shop-product-card--coming-soon .shop-product-card__media {
  opacity: .85;
}

/* ── Cart lines ──────────────────────────────────────────────────────────── */
.shop-cart-line--stale {
  opacity: .8;
  border-color: var(--color-bad) !important;
}

.shop-cart-line__qty {
  flex: 0 0 auto;
}

.shop-cart-line__qty-input {
  width: 4.5rem;
}

.shop-cart-line__total {
  min-width: 4.5rem;
  text-align: right;
}

/* Merch Sale-Pricing Phase 3 -- badge + crossed-out regular + sale amount
   wrap onto their own line cleanly at narrow widths rather than forcing a
   horizontal scroll (the card body around this is already `flex-wrap`). */
.shop-cart-line__price {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem;
}

/* ── Tablet and up: multi-column product grid ───────────────────────────── */
@media (min-width: 576px) {
  .shop-product-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 992px) {
  .shop-product-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   Shop Goblin Creator (Phase 2C) -- View/Shop/Creator.ejs +
   public/js/shopGoblinCreatorWizard.js. Reuses public/css/goblinville.css's
   .composer-thumb/.gv-npc-part/.goblinville-npc--composer rules verbatim
   for the actual preview/thumbnail mechanics -- everything below is layout
   and this creator's own larger, retail-friendly touch targets, never a
   redefinition of those shared rules.
   ═══════════════════════════════════════════════════════════════════════ */

.shop-creator__intro {
  margin-bottom: 1rem;
}

/* ── Step progress -- numbered dots + label, state shown by shape/border/
   icon as well as color (never color alone).
   Phase 2C.2: the 5 main steps used to be a single nowrap row inside an
   `overflow-x: auto` strip -- readable, but required a horizontal swipe (or
   a faded edge hinting at it) to discover "Add a Jersey Number"/"Review
   Your Goblin" on a phone. Below the existing >=992px desktop breakpoint
   (this file's own "the ONLY place a wider grid is introduced" convention,
   see this file's own header comment) it's now a wrapping 2-column grid
   instead -- every step visible with zero horizontal scroll, the 5th
   (odd) step spanning both columns so it doesn't read as a stray
   half-width tile. >=992px reverts to the original single-row layout
   (plenty of width there for all 5, unchanged from before this phase). ─── */
.shop-creator__progress {
  margin-bottom: 1.25rem;
}

.shop-creator__steps {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
  gap: .5rem;
}

/* The 5th step is alone in the last row -- span both columns so it reads
   as a normal full-width row, not a stray half-width tile dangling under
   an empty second column. */
.shop-creator__steps > .shop-creator__step-dot:last-child {
  grid-column: 1 / -1;
}

/* Very narrow phones (this phase's own ~320px target): two columns leaves
   too little room for the longer labels ("Clothing & Accessories", "Add a
   Jersey Number") to read as a single tidy line without going tight/tall,
   so this is the "safe responsive fallback" the phase asked for -- one
   column, every step full-width, nothing clipped or squeezed. */
@media (max-width: 359px) {
  .shop-creator__steps {
    grid-template-columns: 1fr;
  }

  .shop-creator__steps > .shop-creator__step-dot:last-child {
    grid-column: auto;
  }
}

.shop-creator__step-dot {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .4rem .7rem;
  border-radius: 999px;
  border: 1px solid var(--df-border-subtle);
  background: var(--df-surface-alt);
  color: var(--df-text-muted);
  font-size: .75rem;
  cursor: pointer;
  /* Labels wrap onto a second line inside the pill rather than being
     clipped or forcing the pill to overflow its grid column -- a step
     button is a normal multi-line-capable control, same as any other
     wrapped button label on this page. */
  white-space: normal;
  text-align: left;
  min-width: 0;
}

.shop-creator__step-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  border: 1px solid currentColor;
  font-weight: 700;
  flex-shrink: 0;
}

.shop-creator__step-dot.is-active {
  border-color: var(--df-GoblinGold);
  color: var(--df-text-light);
  font-weight: 600;
}

.shop-creator__step-dot.is-active .shop-creator__step-number {
  background: var(--df-GoblinGold);
  border-color: var(--df-GoblinGold);
  color: var(--df-accent-contrast);
}

/* Completed steps get a checkmark glyph, not just a color change, so
   progress is legible without relying on color perception. */
.shop-creator__step-dot.is-complete .shop-creator__step-number::before {
  content: "\2713";
}

.shop-creator__step-dot.is-complete:not(.is-active) {
  color: var(--color-good);
  border-color: var(--color-good);
}

/* ── Layout: mobile-first single column, preview stacked above controls,
   sticky so it stays visible without pushing controls too far down. ───── */
.shop-creator__layout {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* Phase 2C.1 fix: `position: sticky` belongs on `.shop-creator__preview-col`
   itself, not on `.shop-creator__preview-sticky` inside it. A sticky
   element can only stay pinned for as long as ITS OWN PARENT's box remains
   on screen -- and in this mobile-first stacked (column) `.shop-creator__layout`,
   preview-col's box is only as tall as the small goblin graphic + caption
   it contains. With sticky on the INNER element, the goblin scrolled away
   after ~50px of a long category grid instead of staying visible, because
   preview-col (its containing block) itself ended there. Putting sticky on
   preview-col instead makes its containing block `.shop-creator__layout` --
   which spans preview-col's height PLUS controls-col's full height (a
   column flex container's auto height is the SUM of its children, not the
   max) -- so the preview now stays pinned for the whole step, exactly the
   same "sticky sidebar" mechanism the >=992px rule below already uses for
   the desktop row layout. `top: 0` is safe (never overlaps the site
   header): View/Headers_Footers/Header.ejs's own `.header-bg`
   (public/css/style.css) is a normal in-flow element with no
   `position: sticky`/`fixed` of its own, so it has already scrolled out of
   view by the time this preview's own sticky behavior engages -- there is
   nothing above y=0 in the viewport for it to compete with. */
.shop-creator__preview-col {
  position: sticky;
  top: 0;
  z-index: 5;
}

.shop-creator__preview-sticky {
  background: var(--df-bg);
  padding: .5rem 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Compact preview while stacked above the controls on mobile -- large
   enough to see selections clearly, small enough that it never eats the
   whole first screen. The SVG's own viewBox (120x170, public/css/goblinville.css)
   is fixed regardless of which parts are toggled active, so switching a
   selection never resizes -- and therefore never reflows/clips -- this
   box; only the width below ever changes it, and only at a breakpoint. */
.shop-creator__preview-sticky .goblinville-npc--composer {
  width: 150px;
}

/* Narrower phones (this phase's own ~320px/375px targets) -- a further
   compact step so the sticky preview still leaves most of the viewport for
   category choices, never consuming most of the screen. */
@media (max-width: 380px) {
  .shop-creator__preview-sticky {
    padding: .35rem 0;
  }

  .shop-creator__preview-sticky .goblinville-npc--composer {
    width: 108px;
  }

  .shop-creator__preview-sticky .form-text {
    margin-top: .25rem !important;
    font-size: .7rem;
  }
}

.shop-creator__control-label {
  font-size: .78rem;
  font-weight: 600;
  color: var(--df-text-light);
  margin-bottom: .35rem;
}

/* ── Category tabs (Phase 2C.1; wrapping instead of horizontal scroll as
   of Phase 2C.2) -- "Choose Your Look" / "Clothing & Accessories" own
   secondary selector. Originally a `nowrap` + `overflow-x: auto` strip;
   that required a swipe (with nothing on screen hinting more categories
   existed off to the side) to reach every category on a phone. Now a plain
   `flex-wrap: wrap` row: each button is still sized to its OWN label's
   natural width (`flex: 0 0 auto`, `white-space: nowrap` on the button
   itself) -- no label is ever truncated or squeezed to fit a column, even
   a long one like "Front Accessory" -- and the ROW simply continues onto
   as many lines as it needs. On a wide viewport every category still fits
   on one row, so this reads identically to before there; no >=992px
   override is needed. This page still has exactly one vertical scroll
   region (the document itself) -- wrapping adds height here, never a
   second scroll container. ─────────────────────────────────────────────── */
.shop-creator__category-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-bottom: 1rem;
}

.shop-creator__category-tab {
  flex: 0 0 auto;
  padding: .45rem .9rem;
  border-radius: 999px;
  border: 1px solid var(--df-border-subtle);
  background: var(--df-surface-alt);
  color: var(--df-text-muted);
  font-size: .8rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

/* Selected state shown by border/background/weight together, not color
   alone -- same "state is legible without color perception" rule the step
   progress dots above already follow. */
.shop-creator__category-tab.is-active {
  border-color: var(--df-GoblinGold);
  background: var(--df-GoblinGold);
  color: var(--df-accent-contrast);
  font-weight: 700;
}

.shop-creator__category-tab:focus-visible {
  outline: 2px solid var(--df-GoblinGold);
  outline-offset: 2px;
}

.shop-creator__category-panel:focus-visible {
  outline: 2px solid var(--df-GoblinGold);
  outline-offset: 2px;
}

/* Larger than public/css/goblinville.css's own base .composer-thumb
   (44x56, sized for a dense admin grid) -- a retail-facing picker needs
   real touch targets and visible artwork, not tiny icons. */
.composer-thumb--large {
  width: 78px;
  height: 98px;
}

/* ── Color presets ──────────────────────────────────────────────────────── */
.shop-color-preset {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .35rem .65rem;
  border-radius: 999px;
  border: 1px solid var(--df-border-subtle);
  background: var(--df-surface-alt);
  color: var(--df-text-light);
  font-size: .75rem;
  cursor: pointer;
}

.shop-color-preset__swatch {
  display: inline-block;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .25);
  flex-shrink: 0;
}

/* One fixed gradient per generic preset -- descriptive names only (never a
   real team), colors matched to View/Shop/Creator.ejs's own COLOR_PRESETS
   array. Kept here (not inline) per this phase's own "no inline styles"
   requirement. */
.shop-color-preset--blue-gold .shop-color-preset__swatch { background: linear-gradient(135deg, #1c3f94 50%, #f2c14e 50%); }
.shop-color-preset--red-white .shop-color-preset__swatch { background: linear-gradient(135deg, #c8102e 50%, #ffffff 50%); }
.shop-color-preset--green-white .shop-color-preset__swatch { background: linear-gradient(135deg, #1e5631 50%, #ffffff 50%); }
.shop-color-preset--purple-gold .shop-color-preset__swatch { background: linear-gradient(135deg, #4b2e83 50%, #f2c14e 50%); }
.shop-color-preset--black-orange .shop-color-preset__swatch { background: linear-gradient(135deg, #111111 50%, #ff7f11 50%); }
.shop-color-preset--navy-silver .shop-color-preset__swatch { background: linear-gradient(135deg, #12233e 50%, #c0c0c0 50%); }

.shop-creator__jersey-input {
  max-width: 8rem;
}

/* Character Builder Redesign Phase 2 -- #shop-creator-jersey-no-number
   (View/Shop/Creator.ejs), same gold "currently selected" treatment
   .composer-thumb.is-active (public/css/goblinville.css) and
   .shop-product__swatch.is-active above already use, so "No number" reads
   as selected the same way any other picker choice on this page does --
   never color alone (the button's own text and its aria-pressed attribute,
   toggled by public/js/shopGoblinCreatorWizard.js, both already carry the
   same state). */
#shop-creator-jersey-no-number.is-active {
  border-color: var(--df-GoblinGold);
  background: var(--df-GoblinGold);
  color: var(--df-accent-contrast);
  font-weight: 700;
}

.shop-creator__nav {
  position: sticky;
  bottom: 0;
  background: var(--df-bg);
  padding: .75rem 0;
  border-top: 1px solid var(--df-border-subtle);
}

/* ── Desktop: side-by-side, preview stays large and fixed while the
   controls column scrolls through each step. ──────────────────────────── */
@media (min-width: 992px) {
  .shop-creator__layout {
    flex-direction: row;
    align-items: flex-start;
  }

  .shop-creator__preview-col {
    flex: 0 0 260px;
    position: sticky;
    top: 1rem;
  }

  .shop-creator__preview-sticky {
    position: static;
    padding: 0;
  }

  .shop-creator__preview-sticky .goblinville-npc--composer {
    width: 240px;
  }

  .shop-creator__controls-col {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* Phase 2C.2: at this width all 5 steps have always fit comfortably in
     one row -- revert the narrower-viewport wrapping 2-column grid back to
     the original single-row layout, unchanged from before this phase. */
  .shop-creator__steps {
    display: flex;
    grid-template-columns: none;
    flex-wrap: nowrap;
  }

  .shop-creator__steps > .shop-creator__step-dot:last-child {
    grid-column: auto;
  }

  .shop-creator__step-dot {
    white-space: nowrap;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   My Goblin Builder -- visual correction phase. View/Accounts/MyGoblin.ejs
   ONLY (via its own .my-goblin-builder modifier class on
   .shop-creator__layout -- Shop's own View/Shop/Creator.ejs never carries
   that class, so none of the rules below ever match anything on Shop's own
   page, or on the admin Character Composer, which loads neither this class
   nor shop.css at all). Overrides ONLY background/border/padding on the
   SAME shared .shop-creator__preview-sticky/.shop-creator__nav classes
   above -- their layout, sizing, sticky positioning, and every other rule
   in this file stay completely untouched and still apply identically on
   both pages. The live preview's cream background (--df-bg, the site's
   warm page-background token -- see public/css/style.css) and the nav
   bar's matching cream background read as a boxed-in card against this
   page's own dark-green panel; removing just the background/border here
   lets the goblin artwork and the nav controls sit directly on that panel
   instead, with zero change to what actually renders inside either. ───── */

.my-goblin-builder .shop-creator__preview-sticky {
  background: transparent;
}

/* .form-text's own default (Bootstrap's muted gray) was legible against
   the cream background this replaces; against the dark-green panel it is
   not -- reuses --df-text-light, the SAME light/cream token this page's
   own body copy already uses elsewhere, rather than inventing a new color. */
.my-goblin-builder .shop-creator__preview-sticky .form-text {
  color: var(--df-text-light);
}

.my-goblin-builder .shop-creator__nav {
  background: transparent;
  border-top: none;
}

/* ═══════════════════════════════════════════════════════════════════════
   Shop Product Detail (Phase 2D) -- View/Shop/Product.ejs. Mobile-first,
   single column (gallery stacked above info); the >=992px block near the
   bottom of this section is the ONLY place a wider side-by-side layout is
   introduced, same convention this file's own header comment establishes
   for the rest of it.
   ═══════════════════════════════════════════════════════════════════════ */

.shop-product {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

/* ── Gallery ────────────────────────────────────────────────────────────── */

.shop-product__gallery-main {
  position: relative;
  aspect-ratio: 4 / 5;
  background: var(--df-surface-alt);
  border: 1px solid var(--df-border-subtle);
  border-radius: var(--df-radius);
  overflow: hidden;
}

/* A stable aspect-ratio box (above) means swapping which image is visible
   never reflows/shifts anything around it -- only the <img> inside changes. */
.shop-product__gallery-image {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* public/js/shopProductPage.js toggles the HTML `hidden` attribute (not a
   class) to show one gallery image at a time -- the class rule above's own
   `display: block` has EQUAL author-stylesheet specificity to the
   browser's built-in `[hidden] { display: none }` UA rule, so without this
   explicit override an author rule can win the tiebreak and leave a
   "hidden" image still rendered. A same-element attribute selector
   ([hidden] appended to the class) has strictly higher specificity than
   the class alone, so this always wins regardless of source order. */
.shop-product__gallery-image[hidden] {
  display: none;
}

.shop-product__gallery-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .6rem;
}

.shop-product__gallery-thumb {
  width: 3.5rem;
  height: 3.5rem;
  padding: 0;
  border: 2px solid var(--df-border-subtle);
  border-radius: calc(var(--df-radius) * .6);
  background: var(--df-surface-alt);
  overflow: hidden;
  cursor: pointer;
  flex: 0 0 auto;
}

.shop-product__gallery-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* Selected state shown by border color AND weight, not color alone. */
.shop-product__gallery-thumb.is-active {
  border-color: var(--df-GoblinGold);
  border-width: 3px;
}

.shop-product__gallery-thumb:focus-visible {
  outline: 2px solid var(--df-GoblinGold);
  outline-offset: 2px;
}

.shop-product__gallery-placeholder {
  aspect-ratio: 4 / 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  color: var(--df-text-muted);
  background:
    repeating-linear-gradient(
      45deg,
      var(--df-surface-alt),
      var(--df-surface-alt) 10px,
      var(--df-surface) 10px,
      var(--df-surface) 20px
    );
  border: 1px solid var(--df-border-subtle);
  border-radius: var(--df-radius);
}

.shop-product__gallery-placeholder-icon {
  font-size: 2.5rem;
}

/* ── Info column ────────────────────────────────────────────────────────── */

.shop-product__price {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--df-text-light);
}

.shop-product__quantity {
  max-width: 6rem;
}

/* ── Variant selectors ──────────────────────────────────────────────────── */

.shop-product__attribute {
  margin-bottom: 1rem;
}

.shop-product__swatch-row {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
}

.shop-product__swatch {
  padding: .45rem .9rem;
  border-radius: 999px;
  border: 1px solid var(--df-border-subtle);
  background: var(--df-surface-alt);
  color: var(--df-text-muted);
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
}

.shop-product__swatch.is-active {
  border-color: var(--df-GoblinGold);
  background: var(--df-GoblinGold);
  color: var(--df-accent-contrast);
  font-weight: 700;
}

/* Disabled (impossible-combination) state is shown by shape/opacity/
   cursor together, not color alone -- a struck-through label reads clearly
   even without color perception. */
.shop-product__swatch:disabled {
  opacity: .45;
  cursor: not-allowed;
  text-decoration: line-through;
}

.shop-product__swatch:focus-visible {
  outline: 2px solid var(--df-GoblinGold);
  outline-offset: 2px;
}

/* ── Desktop: side-by-side gallery + info ──────────────────────────────── */
@media (min-width: 992px) {
  .shop-product {
    flex-direction: row;
    align-items: flex-start;
    gap: 2rem;
  }

  .shop-product__gallery-col {
    flex: 0 0 45%;
    max-width: 45%;
  }

  .shop-product__info-col {
    flex: 1 1 auto;
    min-width: 0;
  }
}
