/*
 * Xyvira Luxe — Shop All page.
 *
 * Ported from `27 august/shop-all-layout-export/shop-all.css`, declaration for
 * declaration.
 *
 * ⚠️ CLASS NAMES ARE PREFIXED, THE VALUES ARE NOT CHANGED.
 *
 * The export styles `.btn`, `.card`, `.grid`, `.chip`, `.banner`, `.promo`,
 * `.count`, `.sort`, `.code` and `.wrap`. In a WooCommerce store those are not
 * neutral names — `.card` and `.btn` in particular appear in plugin markup all
 * over the admin-facing front end. Every selector here is `.x-sa*` instead, and
 * the declarations inside are the export's, unchanged.
 *
 * `.wrap` is the exception: the site's `.x-container` is already
 * `max-width: 1240px` with `padding-inline: clamp(12px, 4vw, 40px)` — the
 * export's rule exactly — so the markup uses that and this file adds none.
 *
 * ⚠️ THE CARD'S BUTTONS ARE THE THEME'S, RESTYLED — NOT THE EXPORT'S, REBUILT.
 *
 * The export's card has two plain buttons. The store's existing `.x-actions`
 * component has an Add-to-bag form, a quantity stepper that replaces it once the
 * product is in the bag, a Buy-now form that hides itself at the same moment,
 * ajax, and an aria-live region announcing the change. Replacing that with two
 * buttons would have matched the export and thrown away working behaviour
 * nobody asked to lose. The markup stays; these rules give it the export's
 * appearance.
 */

/*
 * ⚠️ THE EXPORT'S `body` RULE, SCOPED TO THE BLOCK.
 *
 * The export sets Jost / #123023 on `body` and leaves the size at the browser
 * default; the theme's body is Outfit 15px / 24px / near-black. Everything this
 * file does not size explicitly would otherwise inherit the theme's. This is the
 * same root cause that made the About and policy pages fail to match, and it is
 * set first here for that reason.
 */
.x-sa {
  font-family: var(--xg-sans);
  font-size: 16px;
  line-height: normal;
  color: var(--xg-green);
}

.x-sa [hidden] { display: none !important; }

.x-sa__kicker {
  font-size: 11px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: #8a6a2f;
}
.x-sa__kicker--light { color: var(--xg-accent-soft); }
.x-sa__kicker--ongold { color: rgba(18, 48, 35, .7); }

/* ────────────────────────────────────────────────────────────────────────────
 * Buttons
 * ──────────────────────────────────────────────────────────────────────────── */

.x-sa__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 44px;
  padding: 11px 10px;
  border: 0;
  border-radius: 999px;
  font-family: inherit;
  font-size: 12px;
  letter-spacing: .05em;
  text-transform: uppercase;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
}
.x-sa__btn--inline { width: auto; padding: 12px 20px; font-size: 13px; }
.x-sa__btn--dark { background: var(--xg-green); color: var(--xg-cream-ink); }
.x-sa__btn--dark:hover { background: var(--xg-green-mid); color: var(--xg-cream-ink); }
.x-sa__btn--ghost {
  background: transparent;
  color: var(--xg-green);
  border: 1px solid rgba(18, 48, 35, .25);
}
.x-sa__btn--ghost:hover { background: var(--xg-cream-2); }

/*
 * ⚠️ WHITE LABEL — THE RAMP MUST STAY DARK END TO END.
 *
 * `--from` and `--to` are set per card from the product's carton colour, mixed
 * 45% and 68% toward the brand green. The raw carton colour is never used here:
 * all twelve fail 4.5:1 against white at full strength, and the pale ones fail
 * quietly — readable indoors, gone on a phone in sunlight. See
 * xyvira_routine_ink() for where the two stops come from.
 */
.x-sa__btn--carton {
  background: linear-gradient(135deg, var(--x-sa-from, var(--xg-accent-ink)), var(--x-sa-to, var(--xg-accent-deep)));
  color: #fff;
}
.x-sa__btn--carton:hover { filter: brightness(1.1); color: #fff; }

.x-sa__btn--accent {
  width: auto;
  min-height: 50px;
  padding: 15px 28px;
  font-size: 13.5px;
  letter-spacing: .06em;
  background: linear-gradient(135deg, var(--xg-accent-ink), var(--xg-accent-deep));
  color: #fff;
}
.x-sa__btn--accent:hover { filter: brightness(1.12); color: #fff; }

/* ────────────────────────────────────────────────────────────────────────────
 * Banners
 * ──────────────────────────────────────────────────────────────────────────── */

/*
 * ⚠️ THE SCRIM NEEDS AN OPACITY FLOOR — the export's own warning, kept.
 *
 * Its lightest stop must still darken, because at phone width the copy spans
 * nearly the whole gradient sweep and a real photograph dropped into the slot
 * may be light. Do not drop the last stop below ~.6 alpha.
 */
.x-sa__banner {
  position: relative;
  overflow: hidden;
  margin-top: clamp(32px, 5vw, 64px);
}
.x-sa__bannerimg {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(135deg, #e7e0d1 0 14px, #dfd7c5 14px 28px);
}
.x-sa__bannerimg img { width: 100%; height: 100%; object-fit: cover; display: block; }
.x-sa__bannerscrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(var(--x-sa-dir, 105deg),
    rgba(14, 42, 30, .9) 0%, rgba(18, 48, 35, .78) 52%, rgba(120, 90, 40, .66) 100%);
}
.x-sa__bannerbody {
  position: relative;
  padding-block: clamp(40px, 7vw, 84px);
  color: var(--xg-cream-ink);
}
.x-sa__bannerhead {
  font-family: var(--xg-serif);
  font-weight: 400;
  font-size: clamp(30px, 8.5vw, 52px);
  line-height: 1.04;
  margin: 12px 0;
  max-width: 20ch;
  color: var(--xg-cream-ink);
  text-align: left;
}
.x-sa__bannersub {
  margin: 0 0 22px;
  max-width: 46ch;
  font-size: 15.5px;
  line-height: 1.65;
  color: rgba(244, 237, 223, .86);
  font-weight: 300;
}

/* ────────────────────────────────────────────────────────────────────────────
 * Motion
 * ──────────────────────────────────────────────────────────────────────────── */

.x-sa__btn,
.x-sa__chip { transition: background .25s ease, color .25s ease, filter .25s ease, transform .2s ease; }
.x-sa__btn:active,
.x-sa__chip:active { transform: scale(.98); }

@media (prefers-reduced-motion: reduce) {
  .x-sa *,
  .x-sa *::before,
  .x-sa *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ────────────────────────────────────────────────────────────────────────────
 * Intro
 * ──────────────────────────────────────────────────────────────────────────── */

.x-sa__intro { padding-top: clamp(20px, 3vw, 34px); }
.x-sa__crumbs {
  display: flex;
  gap: 7px;
  flex-wrap: wrap;
  font-size: 12px;
  color: rgba(18, 48, 35, .72);
  margin-bottom: 12px;
}
.x-sa__crumbs a { color: inherit; text-decoration: none; }
.x-sa__intro h1 {
  font-family: var(--xg-serif);
  font-weight: 400;
  font-size: clamp(30px, 9vw, 52px);
  line-height: 1.02;
  margin: 0 0 10px;
  color: var(--xg-green);
  text-align: left;
}
.x-sa__lede {
  margin: 0;
  font-size: 15px;
  line-height: 1.6;
  color: rgba(18, 48, 35, .75);
  font-weight: 300;
  max-width: 48ch;
}

/* ────────────────────────────────────────────────────────────────────────────
 * Toolbar
 * ──────────────────────────────────────────────────────────────────────────── */

/*
 * ⚠️ THE TWO LEDES ARRIVE IN DIFFERENT MARKUP, AND MUST NOT LOOK LIKE IT.
 *
 * Shop All prints `<p class="x-sa__lede">` — one element, no margins. A category
 * prints `<div class="x-sa__lede">` around `term_description()`, which WordPress
 * runs through wpautop, so the text arrives already wrapped in its own `<p>`
 * carrying the stylesheet's default 15px above and below.
 *
 * Measured: 10px under the heading and 0 above the toolbar on Shop All, against
 * 15px and 15px on every category — the same block sitting 5px lower and pushing
 * the chips 15px further down. Zeroing the first and last child's outer margins
 * makes the wrapped case measure like the bare one, while leaving a second
 * paragraph or a list spaced normally if an editor writes one.
 */
.x-sa__lede > :first-child { margin-top: 0; }
.x-sa__lede > :last-child  { margin-bottom: 0; }

.x-sa__toolbar { padding-top: clamp(18px, 2.4vw, 28px); }

.x-sa__chips {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 4px;
  scrollbar-width: none;
}
.x-sa__chips::-webkit-scrollbar { height: 0; }

.x-sa__chip {
  flex: 0 0 auto;
  min-height: 42px;
  padding: 10px 16px;
  border-radius: 999px;
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  letter-spacing: .04em;
  white-space: nowrap;
  background: #fff;
  color: var(--xg-green);
  border: 1px solid rgba(18, 48, 35, .18);
  text-decoration: none;
  /* The export's chip is a <button>, which centres its own label. Ours is a
     link — see the note in shop-all.php — so the centring is explicit. Block
     rather than flex so the computed box matches the export's exactly. */
  text-align: center;
}
.x-sa__chip.is-active {
  background: var(--xg-green);
  color: var(--xg-cream-ink);
  border-color: var(--xg-green);
}

.x-sa__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding: 14px 0 16px;
  border-bottom: 1px solid rgba(18, 48, 35, .1);
}
.x-sa__count { font-size: 13px; color: rgba(18, 48, 35, .72); }
.x-sa__toollink {
  font-size: 13px;
  color: rgba(18, 48, 35, .72);
  border-bottom: 1px solid rgba(18, 48, 35, .25);
  padding-bottom: 2px;
  margin-right: auto;
  text-decoration: none;
}
.x-sa__sort {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 13px;
  color: rgba(18, 48, 35, .72);
}

/* The "Sort by:" prefix. See the note in xl-shop-archive.php for why it is a
   sibling of the select rather than part of every option label. */
.x-sa__sortlabel {
  font-size: 13px;
  color: rgba(18, 48, 35, .72);
  white-space: nowrap;
}
/*
 * ⚠️ 13px HERE, BUT 16px ON A PHONE, AND THAT IS DELIBERATE.
 *
 * style.css carries `select { font-size: 16px !important }` under 640px because
 * iOS Safari auto-zooms the viewport on any focused control smaller than that.
 * Its comment also records the trap: never "fix" the zoom with
 * `maximum-scale=1`, which disables pinch zoom and is an accessibility failure.
 *
 * So this control measures 13px against the export on a desktop and 16px on a
 * phone. The export is a bare preview page with no such rule and cannot know
 * about it; overriding the !important to match would trade a real iPhone bug for
 * a screenshot. Left alone on purpose.
 */
.x-sa__sort select {
  font-family: inherit;
  font-size: 13px;
  color: var(--xg-green);
  background: #fff;
  border: 1px solid rgba(18, 48, 35, .18);
  border-radius: 999px;
  padding: 9px 12px;
  min-height: 40px;
}

/* ────────────────────────────────────────────────────────────────────────────
 * Product grid — 1 up mobile, 2 up tablet, 3 up small desktop, 4 up desktop
 *
 * ⚠️ THE BREAKPOINTS ARE THE PROJECT'S, NOT THIS FILE'S.
 *
 *   ≤ 767px    small + large mobile   1 column
 *   768–991    tablet                 2
 *   992–1199   small desktop          3
 *   ≥ 1200     desktop                4
 *
 * ⚠️ ONE COLUMN ON MOBILE, NOT TWO.
 *
 * A phone-width card gets the full measure: the photograph is large enough to
 * read the carton, and the format line, name, price and Add button sit on their
 * own lines instead of wrapping inside a ~160px column.
 * ──────────────────────────────────────────────────────────────────────────── */

.x-sa__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(8px, 1.4vw, 20px);
  padding-top: clamp(16px, 2.4vw, 24px);
}
@media (min-width: 768px)  { .x-sa__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 992px)  { .x-sa__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1200px) { .x-sa__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* Same containing-block fix as the rails — see the note in bestsellers.css.
   The shop grid does not scroll sideways, so it never showed the symptom, but
   the same escaping span is in every one of these cards. */
.x-sa__card {
  position: relative;
  background: #fff;
  border: 1px solid rgba(18, 48, 35, .07);
  border-radius: 16px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.x-sa__media {
  position: relative;
  display: block;
  aspect-ratio: 4 / 5;
  /* The tint is the raw carton colour at low alpha — safe, because nothing
     white sits on it. Set per card from the product's own colour. */
  background: linear-gradient(160deg,
    rgba(var(--x-sa-carton, 185, 143, 66), .1),
    rgba(var(--x-sa-carton, 185, 143, 66), .22));
}
.x-sa__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.x-sa__badge {
  position: absolute;
  top: 8px;
  left: 8px;
  color: #fff;
  font-size: 10px;
  letter-spacing: .09em;
  text-transform: uppercase;
  padding: 5px 9px;
  border-radius: 999px;
  pointer-events: none;
  background: var(--x-sa-from, var(--xg-accent-ink));
}
/*
 * ── Card gallery ────────────────────────────────────────────────────────────
 *
 * ⚠️ THE TRACK SCROLLS NATIVELY. THE SCRIPT ONLY DRIVES THE CONTROLS.
 *
 * Scroll snapping does the swipe, the momentum and the settling — on a phone
 * that is the whole interaction, and it behaves like every other gallery the
 * reader has used because it is the platform's own. Rebuilding that in JS would
 * be more code and worse.
 */
.x-sa__track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;

  /* The scrollbar would sit across the bottom of the photograph. */
  scrollbar-width: none;
  -ms-overflow-style: none;

  /* A horizontal scroller inside a vertically-scrolling page: this keeps a
     diagonal swipe from dragging the page sideways at the ends of the track. */
  overscroll-behavior-x: contain;
}
.x-sa__track::-webkit-scrollbar { display: none; }

.x-sa__slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  /* `min-width: 0` is what stops flex items refusing to shrink below their
     image's intrinsic width, which would make every slide 600px wide. */
  min-width: 0;
}

@media (prefers-reduced-motion: reduce) {
  .x-sa__track { scroll-behavior: auto; }
}

/*
 * The controls sit outside the anchor — see xl_shop_card_gallery_controls().
 * This layer is `pointer-events: none` so the photograph underneath stays a
 * link; only the buttons themselves take a press.
 */
.x-sa__gallery {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  /* The image plate is 4/5 of the card's width; the controls belong to it, not
     to the text below. */
  aspect-ratio: 4 / 5;
  pointer-events: none;
  z-index: 2;
}

.x-sa__dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 10px;
  display: flex;
  justify-content: center;
  gap: 5px;
}
.x-sa__dot {
  pointer-events: auto;
  width: 6px;
  height: 6px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .55);
  box-shadow: 0 0 0 1px rgba(18, 48, 35, .12);
  cursor: pointer;
  transition: background-color .18s ease, width .18s ease;
}
.x-sa__dot.is-active { background: #fff; width: 16px; }
.x-sa__dot:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

.x-sa__gnav {
  pointer-events: auto;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid rgba(18, 48, 35, .10);
  border-radius: 999px;
  background: rgba(255, 255, 255, .88);
  color: var(--xg-green);
  cursor: pointer;

  /*
   * ⚠️ HIDDEN UNTIL THE CARD IS HOVERED, AND ONLY WHERE HOVER EXISTS.
   *
   * `@media (hover: hover)` matters: on a touch screen `:hover` sticks after a
   * tap, so without this the arrows would appear on the last card touched and
   * stay there. Touch gets the swipe and the dots, which is the better control
   * anyway.
   */
  opacity: 0;
  transition: opacity .18s ease;
}
.x-sa__gnav--prev { left: 8px; }
.x-sa__gnav--next { right: 8px; }

@media (hover: hover) {
  li.x-sa__card:hover .x-sa__gnav,
  .x-sa__gnav:focus-visible { opacity: 1; }
}
@media (hover: none) {
  .x-sa__gnav { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .x-sa__dot, .x-sa__gnav { transition: none; }
}

/*
 * The share control, mirroring the badge across the card's top edge.
 *
 * ⚠️ IT SITS OUTSIDE THE CARD'S ANCHOR — see xl_shop_card_share().
 *
 * The badge can use `pointer-events: none` because it is decoration inside the
 * link. This one has to receive the click, so it is a real `<button>` printed
 * after the anchor closes and positioned back over the image.
 *
 * Translucent white rather than the badge's solid ink: the badge is announcing a
 * fact about the product and should read first, while this is a tool that should
 * be findable without competing with it.
 */
.x-sa__share {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;

  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;

  border: 1px solid rgba(18, 48, 35, .10);
  border-radius: 999px;
  background: rgba(255, 255, 255, .88);
  color: var(--xg-green);
  cursor: pointer;

  /* The image behind it can be any colour; this keeps the glyph legible on a
     pale carton without a hard shadow. */
  backdrop-filter: saturate(140%) blur(2px);
  transition: background-color .18s ease, color .18s ease, transform .18s ease;
}
.x-sa__share:hover { background: #fff; transform: translateY(-1px); }
.x-sa__share:focus-visible {
  outline: 2px solid var(--xg-green);
  outline-offset: 2px;
}
.x-sa__share svg { display: block; }

/*
 * The copy confirmation, on the button itself.
 *
 * ⚠️ `right: 0` AND `left: auto` — the card clips at its own edge.
 *
 * The button sits 8px from the right edge of a card that is `overflow: hidden`,
 * so a note centred on the button would be cut in half. Anchoring its right edge
 * to the button's makes it grow leftwards into the card instead.
 */
.x-sa__share[data-share-note]::after {
  content: attr(data-share-note);
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  white-space: nowrap;
  font-size: 11px;
  line-height: 1;
  padding: 6px 8px;
  border-radius: 6px;
  background: var(--xg-green);
  color: var(--xg-cream-ink, #fff);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .x-sa__share { transition: none; }
  .x-sa__share:hover { transform: none; }
}

.x-sa__body {
  padding: 12px 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
}
.x-sa__format {
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--x-sa-from, var(--xg-accent-ink));
}
.x-sa__name {
  margin: 0;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.25;
  font-family: var(--xg-sans);
  color: var(--xg-green);
  text-align: left;
}
.x-sa__name a { color: inherit; text-decoration: none; }
.x-sa__tag {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.4;
  color: rgba(18, 48, 35, .72);
  font-weight: 300;
}
.x-sa__price {
  display: flex;
  align-items: baseline;
  gap: 7px;
  flex-wrap: wrap;
  margin-top: auto;
  padding-top: 6px;
}
.x-sa__price b { font-size: 16.5px; font-weight: 500; color: var(--xg-green); }
.x-sa__price s { font-size: 12.5px; color: rgba(18, 48, 35, .7); }
.x-sa__cta { display: grid; gap: 6px; margin-top: 4px; }

/* ────────────────────────────────────────────────────────────────────────────
 * The store's own add-to-bag component, wearing the export's buttons
 * ──────────────────────────────────────────────────────────────────────────── */

/*
 * ⚠️ THE COLUMN COUNT AND THE MARGINS BOTH HAVE TO BE RESET, NOT JUST `display`.
 *
 * woocommerce.css sets `.x-actions { display: grid; grid-template-columns: 1fr 1fr }`
 * plus `margin: .875rem 1rem 1rem` and a top padding — it is designed to sit
 * flush against the edge of the old card and put Add-to-bag and Buy-now side by
 * side. Declaring only `display: grid` here changed nothing visible, because it
 * was already grid: the two-column track survived and the buttons stayed in a
 * row where the design stacks them.
 */
.x-sa__cta .x-actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  margin: 0;
  padding-top: 0;
}
.x-sa__cta .x-actions form { margin: 0; }

/* Add to bag → .btn--dark */
.x-sa__cta .x-actions__addbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 44px;
  padding: 11px 10px;
  border: 0;
  border-radius: 999px;
  font-family: inherit;
  font-size: 12px;
  letter-spacing: .05em;
  text-transform: uppercase;
  cursor: pointer;
  text-align: center;
  background: var(--xg-green);
  color: var(--xg-cream-ink);
}
.x-sa__cta .x-actions__addbtn:hover { background: var(--xg-green-mid); color: var(--xg-cream-ink); }

/* Buy now → .btn--carton, taking this card's two derived stops */
.x-sa__cta .x-btn--accent {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 44px;
  padding: 11px 10px;
  border: 0;
  border-radius: 999px;
  font-family: inherit;
  font-size: 12px;
  letter-spacing: .05em;
  text-transform: uppercase;
  cursor: pointer;
  background: linear-gradient(135deg, var(--x-sa-from, var(--xg-accent-ink)), var(--x-sa-to, var(--xg-accent-deep)));
  color: #fff;
}
.x-sa__cta .x-btn--accent:hover { filter: brightness(1.1); color: #fff; }

/* Read more, on anything not purchasable → .btn--ghost */
.x-sa__cta .x-btn--ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 44px;
  padding: 11px 10px;
  border-radius: 999px;
  font-family: inherit;
  font-size: 12px;
  letter-spacing: .05em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  background: transparent;
  color: var(--xg-green);
  border: 1px solid rgba(18, 48, 35, .25);
}
.x-sa__cta .x-btn--ghost:hover { background: var(--xg-cream-2); color: var(--xg-green); }

/*
 * The stepper takes the place of both buttons once the product is in the bag,
 * so it is sized to match one of them exactly — otherwise the card's height
 * changes on add and the whole grid below it jumps.
 */
.x-sa__cta .x-qty {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 44px;
  padding: 0 6px;
  border-radius: 999px;
  border: 1px solid rgba(18, 48, 35, .25);
  background: #fff;
}
.x-sa__cta .x-qty__step {
  min-width: 34px;
  min-height: 34px;
  border: 0;
  background: transparent;
  color: var(--xg-green);
  font-size: 16px;
  cursor: pointer;
  border-radius: 999px;
}
.x-sa__cta .x-qty__step:hover { background: var(--xg-cream-2); }
.x-sa__cta .x-qty__label {
  font-size: 12px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--xg-green);
}

/* ────────────────────────────────────────────────────────────────────────────
 * Promos
 * ──────────────────────────────────────────────────────────────────────────── */

.x-sa__promos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 12px;
  padding-top: clamp(32px, 5vw, 56px);
}
.x-sa__promo {
  border-radius: 18px;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-height: 200px;
}
.x-sa__promo h2 {
  font-family: var(--xg-serif);
  font-weight: 400;
  font-size: clamp(24px, 6vw, 32px);
  margin: 0;
  line-height: 1.08;
  text-align: left;
}
.x-sa__promo--dark {
  background: linear-gradient(150deg, var(--xg-green), #24513a 70%, #3a6b4e);
  color: var(--xg-cream-ink);
}
.x-sa__promo--dark h2 { color: var(--xg-cream-ink); }
.x-sa__promo--gold {
  background: linear-gradient(150deg, var(--xg-accent-hi), var(--xg-accent));
  color: var(--xg-green);
}
.x-sa__promo--gold h2 { color: var(--xg-green); }
.x-sa__promo--cream { background: var(--xg-cream-2); }
.x-sa__promo--cream h2 { color: var(--xg-green); }
.x-sa__promo .x-sa__btn--inline { margin-top: auto; align-self: flex-start; }

.x-sa__code {
  margin-top: auto;
  align-self: flex-start;
  border: 1px dashed var(--xg-accent-soft);
  color: var(--xg-accent-soft);
  padding: 10px 16px;
  border-radius: 10px;
  font-family: ui-monospace, Menlo, monospace;
  letter-spacing: .14em;
  font-size: 13px;
}

.x-sa__concerns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: auto; }
.x-sa__concerns a {
  background: #fff;
  border-radius: 999px;
  padding: 10px 14px;
  font-size: 13px;
  color: var(--xg-green);
  text-decoration: none;
}
.x-sa__concerns a:hover { color: #8a6a2f; }

/*
 * ⚠️ NOTHING HERE STARTS AT opacity: 0.
 *
 * The export says so explicitly: its reveal rules depend on a site-wide script
 * adding `.xl-in`, and it ships without one. This page does have that script,
 * but the grid is the page's entire purpose — if the script ever fails to run,
 * a reveal-on-scroll rule would leave a shop with no products in it. Transitions
 * only, exactly as the export.
 */


/* ════════════════════════════════════════════════════════════════════════════
 * WooCommerce's own loop markup
 *
 * ⚠️ THE CARD IS NO LONGER OURS, SO THESE SELECTORS FOLLOW WOOCOMMERCE'S.
 *
 * The shop page used to be rendered wholesale by the theme, which let this file
 * style markup it had written itself — `.x-sa__media`, `.x-sa__body`,
 * `.x-sa__cta` and so on. WooCommerce owns the loop now, so those elements do
 * not exist: the card is an <li class="product">, the image and title sit inside
 * `a.woocommerce-loop-product__link`, and the price is `.x-card__price`.
 *
 * `.x-sa__card` and `.x-sa__grid` are added through `woocommerce_post_class` and
 * `woocommerce_product_loop_start`, which is why those two still work.
 * ════════════════════════════════════════════════════════════════════════════ */

/* The grid: WooCommerce adds `columns-4`, which brings its own float layout. */
/*
 * ⚠️ `.woocommerce ul` IS LOAD-BEARING SPECIFICITY, NOT DECORATION.
 *
 * woocommerce.css styles the loop as `.woocommerce ul.products` — (0,2,1).
 * `.x-sa__grid.products` is (0,2,0), so it LOSES no matter how much later this
 * file is enqueued: specificity is compared before order. Every column count
 * below silently did nothing and the grid stayed on woocommerce.css's ladder
 * (2 up / 3 up at 768 / 4 up at 1024) — measured at fourteen widths, not
 * assumed. Matching its shape at (0,3,1) is what makes these rules apply.
 */
.woocommerce ul.x-sa__grid.products {
  /*
   * ⚠️ THE GRID CARRIES ITS OWN CONTAINER, BECAUSE NOTHING ELSE CAN GIVE IT ONE.
   *
   * The old renderer printed `<section class="x-container">` around the grid.
   * The hook rebuild cannot: `woocommerce_product_loop_start` hands over the
   * `<ul>` and nothing outside it, and the archive is deliberately unwrapped so
   * the banners can reach both edges. Without this the cards ran the full 1440
   * while the intro, chips and promos sat inside 1240 — the products touching the
   * screen edge and every other section inset from it.
   *
   * These are `.x-container`'s own values (brand.css); they must not drift apart.
   */
  max-width: 77.5rem;
  margin-inline: auto;
  padding-inline: clamp(12px, 4vw, 40px);

  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(8px, 1.4vw, 20px);
  padding-top: clamp(16px, 2.4vw, 24px);
  /* margin-block only — a shorthand `margin: 0` here would reset the auto
     inline margins above and un-centre the grid. */
  margin-block: 0;
  list-style: none;
}
@media (min-width: 768px)  { .woocommerce ul.x-sa__grid.products { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 992px)  { .woocommerce ul.x-sa__grid.products { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1200px) { .woocommerce ul.x-sa__grid.products { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* ⚠️ WooCommerce floats loop items and sets a percentage width. Both have to go
   or the grid tracks are ignored and the cards stack in a column. */
li.x-sa__card {
  float: none !important;
  width: auto !important;
  margin: 0 !important;
  clear: none !important;
  background: #fff;
  border: 1px solid rgba(18, 48, 35, .07);
  border-radius: 16px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: relative;
}

/* The image and title share one anchor; it becomes the top of the card. */
li.x-sa__card .woocommerce-loop-product__link {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}

/*
 * The image plate, tinted with the product's carton colour.
 *
 * ⚠️ THE LONG SELECTOR IS THE POINT — woocommerce.css SETS `aspect-ratio: 1 / 1`.
 *
 * `.woocommerce ul.products li.product img` is (0,3,3). The obvious
 * `li.x-sa__card ... img` is (0,2,2) and loses on specificity however late this
 * file loads, so the card quietly rendered SQUARE instead of the design's 4:5 —
 * the rebuild changed the shape of every product photograph and nothing said so.
 * Matching its shape at (0,4,3) is what makes this apply.
 */
.woocommerce ul.products li.x-sa__card .woocommerce-loop-product__link img,
.woocommerce ul.products li.x-sa__card .attachment-woocommerce_thumbnail,
.woocommerce ul.products li.x-sa__card .attachment-woocommerce_single {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  display: block;
  margin: 0;
  background: linear-gradient(160deg,
    rgba(var(--x-sa-carton, 185, 143, 66), .1),
    rgba(var(--x-sa-carton, 185, 143, 66), .22));
}

li.x-sa__card .x-sa__badge {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
  background: var(--x-sa-ink, var(--xg-accent-ink));
}

/* Everything below the image. WooCommerce leaves these as siblings rather than
   wrapping them, so the padding is applied per element. */
li.x-sa__card .x-sa__format,
li.x-sa__card .x-card__title,
li.x-sa__card .x-sa__tag,
li.x-sa__card .x-card__price,
li.x-sa__card .x-actions {
  padding-inline: 12px;
}
li.x-sa__card .x-sa__format { padding-top: 12px; }

/*
 * ⚠️ THE NAME IS HELD TO TWO LINES SO EVERY CARD'S TEXT BLOCK IS THE SAME HEIGHT.
 *
 * Two of twelve names wrap to three. That extra 19px came straight out of the
 * space above the buttons, so those cards read as cramped while their neighbours
 * in the same row looked loose — the row is as tall as its tallest member either
 * way, so the slack has to land somewhere.
 *
 * ⚠️ IT TRUNCATES, AND THAT IS THE COST. "Himalayan Bloom Polishing Face Scrub"
 * becomes "…Polishing Face…". Acceptable only because the format line directly
 * above already states the type — "SCRUB · 100 G" — so the word being cut is not
 * carrying the meaning, and the full name is on the product page. If a name ever
 * needs to read in full here, raise the clamp and the min-height together.
 */
li.x-sa__card .x-card__title {
  /* No top margin: the format line above already carries the spacing, and the
     6px here stacked on top of it read as an uneven step between the two. */
  margin: 0;
  font-family: var(--xg-sans);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.25;
  color: var(--xg-green);
  text-align: left;
}
li.x-sa__card .x-card__title > span {
  /* The clamp goes on the span, not the h2 — see xyvira_loop_title(). A flex
     item blockifies, and `-webkit-line-clamp` needs `-webkit-box` to survive. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;

  /*
   * ⚠️ THE TWO-LINE RESERVE BELONGS HERE, NOT ON THE `<h2>`.
   *
   * Put on the h2 it silently did nothing: `box-sizing: border-box` counts that
   * element's 14px padding-top inside the min-height, so `2.5em` reserved 23.5px
   * of text — less than one line — and a one-line name still produced a card
   * 19px shorter than a two-line one. On the span there is no padding to absorb
   * it, so this is two lines of text and nothing else.
   *
   * Names run to one or two lines at this width; the clamp is the guard for a
   * longer name added later, not something that truncates anything today.
   */
  min-height: 2.5em;
}

li.x-sa__card .x-sa__tag { margin: 6px 0 0; }

li.x-sa__card .x-card__price {
  display: flex;
  align-items: baseline;
  gap: 7px;
  flex-wrap: wrap;
  margin: 6px 0 0;
  padding-top: 6px;
  /* Pins the price and buttons to the bottom so a row of cards with different
     title lengths still lines up. */
  margin-top: auto;
  font-size: 16.5px;
  font-weight: 500;
  color: var(--xg-green);
}
li.x-sa__card .x-card__price del,
li.x-sa__card .x-card__price del .amount {
  font-size: 12.5px;
  font-weight: 400;
  color: rgba(18, 48, 35, .7);
  text-decoration: line-through;
}
li.x-sa__card .x-card__price ins { text-decoration: none; }

/*
 * The add-to-bag component, stacked as the design has it.
 *
 * ⚠️ `padding-top` IS THE GUARANTEED GAP. `margin-top: auto` CANNOT BE.
 *
 * The rule below pins this block to the bottom of the card so buttons line up
 * across a row. `auto` gives whatever space is left over — and on a card with no
 * slack that is zero, putting the price hard against the button.
 *
 * Two things eat the slack, both common rather than edge cases: a name that wraps
 * to three lines, and the tall two-button block (Add to bag + Buy now, 108px)
 * that every in-stock product shows to a visitor with an empty bag. Measured on
 * the live grid: four of twelve cards sat at a 0px gap.
 *
 * Padding is inside the box, so `auto` cannot absorb it — the gap is at least
 * this much on every card, and cards with slack simply get more.
 */
li.x-sa__card .x-actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  margin: 8px 0 0;
  padding-bottom: 14px;
  padding-top: 14px;

  /*
   * ⚠️ THE BLOCK RESERVES ITS TALLEST FORM, SO THE GAP ABOVE IT NEVER MOVES.
   *
   * This card shows one of three things: a quantity stepper (46px), a single
   * "Read more" (44px), or Add to bag + Buy now stacked (44 + 6 + 44 = 94px).
   * With the block bottom-pinned, that 50px difference landed in the gap above
   * it — so the same row held a 14px gap beside a 63px one, decided by whether a
   * product happened to be in the bag.
   *
   * Reserving the tall form makes every card's internal layout identical, so the
   * gap is the padding above and nothing else.
   */
  min-height: calc(var(--x-sa-btn, 44px) * 2 + 6px + 28px);

  /*
   * ⚠️ `start`, NOT THE DEFAULT `stretch`.
   *
   * A grid stretches its rows to fill a min-height, which would inflate a lone
   * "Read more" into a 94px slab. `start` keeps every control its natural size
   * and puts the reserved space below it — which is the trade this makes: the
   * gap above the buttons is now identical on every card, and the slack shows as
   * breathing room at the card's foot instead.
   */
  align-content: start;
}
li.x-sa__card .x-actions form { margin: 0; }

li.x-sa__card .x-actions__addbtn,
li.x-sa__card .x-btn--ghost,
li.x-sa__card .x-btn--accent {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 11px 10px;
  border-radius: 999px;
  font-family: var(--xg-sans);
  font-size: 12px;
  font-weight: 400;
  line-height: normal;
  letter-spacing: .05em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  border: 0;
}
li.x-sa__card .x-actions__addbtn { background: var(--xg-green); color: var(--xg-cream-ink); }
li.x-sa__card .x-actions__addbtn:hover { background: var(--xg-green-mid); }

/* ⚠️ THE BUY-NOW BUTTON WAS CORAL — the palette this layer is migrating away
   from, and the last coral on the shop page. It takes the product's own two
   derived stops instead, as the design specifies. */
li.x-sa__card .x-btn--accent {
  background: linear-gradient(135deg, var(--x-sa-ink, var(--xg-accent-ink)), var(--xg-accent-deep));
  color: #fff;
}
li.x-sa__card .x-btn--accent:hover { filter: brightness(1.1); color: #fff; }

li.x-sa__card .x-btn--ghost {
  background: transparent;
  color: var(--xg-green);
  border: 1px solid rgba(18, 48, 35, .25);
}
li.x-sa__card .x-btn--ghost:hover { background: var(--xg-cream-2); }

/* The stepper replaces both buttons once the item is in the bag; sized to match
   one exactly so the card does not change height and jog the grid. */
li.x-sa__card .x-qty {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 44px;
  padding: 0 6px;
  border-radius: 999px;
  border: 1px solid rgba(18, 48, 35, .25);
  background: #fff;
}
li.x-sa__card .x-qty__step {
  min-width: 34px;
  min-height: 34px;
  border: 0;
  background: transparent;
  color: var(--xg-green);
  font-size: 16px;
  border-radius: 999px;
  cursor: pointer;
}
li.x-sa__card .x-qty__label {
  font-size: 12px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--xg-green);
}

/* ⚠️ THE `hidden` ATTRIBUTE NEEDS DEFENDING. The browser rule is user-agent
   weight, so woocommerce.css setting `display` on these beats it — leaving the
   button and the stepper both on screen at once. */
.x-sa__grid [hidden] { display: none !important; }

/* WooCommerce's sorting form, restyled to sit where the design's select did. */
.x-sa__sort .woocommerce-ordering { margin: 0; }

/*
 * ⚠️ `background-color`, NOT `background` — THE SHORTHAND EATS THE url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' stroke='%23123023' stroke-opacity='.55' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E").
 *
 * `appearance: none` (woocommerce.css) removes the native arrow, and that file
 * draws one back as a `background-image`. This rule is the same specificity and
 * loads later, so a `background: #fff` shorthand here reset that image to `none`
 * and the control rendered as a bare pill — a select with no affordance, which
 * reads as a text box that refuses to accept typing.
 *
 * The chevron is declared here rather than left to the other file, so the two
 * cannot get out of order again. Its colour is the brand green at 55%, matching
 * the label rather than woocommerce.css's warm grey.
 */
.x-sa__sort select {
  font-family: var(--xg-sans);
  font-size: 13px;
  color: var(--xg-green);
  background-color: #fff;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' stroke='%23123023' stroke-opacity='.55' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 11px;
  border: 1px solid rgba(18, 48, 35, .18);
  border-radius: 999px;
  padding: 9px 34px 9px 14px;
  min-height: 40px;
}

/* ⚠️ WOOCOMMERCE'S PLACEHOLDER IS AN OPAQUE GREY OUTLINE, AND IT SITS ON TOP.
 *
 * A product with no photograph gets `woocommerce-placeholder` — a grey picture
 * icon on white. That covers the carton tint painted behind it, so the designed
 * empty state never showed and the card read as broken rather than as awaiting a
 * photograph. Hiding the image itself leaves the tinted plate, which is what the
 * design specifies.
 */
li.x-sa__card img.woocommerce-placeholder { opacity: 0; }
li.x-sa__card .woocommerce-loop-product__link:has(img.woocommerce-placeholder) {
  background: linear-gradient(160deg,
    rgba(var(--x-sa-carton, 185, 143, 66), .1),
    rgba(var(--x-sa-carton, 185, 143, 66), .22));
}

/* The unwrapped archive: each section opens its own container, so the banners
   can reach both edges. See the note in woocommerce.php. */
.x-shop--full { max-width: none; padding-inline: 0; }
.x-shop--full .woocommerce { max-width: none; }

/*
 * ⚠️ THE SORT CONTROL FILLS THE ROW ON MOBILE.
 *
 * Below 768px — small and large mobile in the project's breakpoint table — the
 * grid is one column and this row has nothing left to sit beside: the product
 * count was removed and the chips wrap onto their own line above. A ~150px pill
 * parked at the left edge of a full-width row reads as a stray tag rather than a
 * control.
 *
 * Full width also makes the whole row a tap target instead of a small pill, which
 * matters more here than anywhere else on the page — a select is the one control
 * a thumb has to hit exactly.
 *
 * ⚠️ THIS HAS TO COME AFTER THE `.x-sa__sort select` BLOCK ABOVE, NOT BEFORE.
 *
 * That block is the same specificity (0,2,1), so order is what decides. A media
 * query adds no specificity — moving this earlier in the file would silently
 * un-do it, which is the failure this stylesheet has already hit three times.
 */
@media (max-width: 767px) {
  .x-sa__row {
    align-items: stretch;
    padding-top: 12px;
  }

  /*
   * ⚠️ NO TWO-LINE RESERVE ON MOBILE.
   *
   * The reserve exists so cards sitting side by side in a row line up. Below
   * 768px the grid is a single column, so there is no neighbour to match — the
   * reserve only adds an empty line under every short name.
   */
  li.x-sa__card .x-card__title > span { min-height: 0; }

  .x-sa__sort { width: 100%; }

  /*
   * ⚠️ THE SELECT NO LONGER TAKES THE WHOLE ROW — THE LABEL SHARES IT.
   *
   * `space-between` puts "Sort by:" against the left edge and the control against
   * the right, so the row is still full width. `flex: 1` on the form rather than
   * a fixed width lets the control take whatever the label leaves, at any string
   * length — the label is translatable and "Trier par :" is wider.
   */
  .x-sa__sort .woocommerce-ordering {
    flex: 1 1 auto;
    min-width: 0;
  }

  .x-sa__sort select { width: 100%; }

  /* Centre the label text now that the control is the width of the screen —
     left-aligned it reads as an input waiting to be typed into. Padding is
     symmetric so the centring is optical, not just arithmetic; the chevron sits
     in the right-hand gutter that padding leaves. */
  .x-sa__sort select {
    text-align: center;
    text-align-last: center;
    padding-inline: 34px;
  }
}
