/* Storefront design system — the primitives every component composes from.
 *
 * Loads first (the theme inlines its stylesheets in sorted order and `_d` sorts
 * before `_m`, `b`, `c`…), after the Ruby-generated colour/spacing/font vars. So
 * everything here may read --color-*, and everything after here may read --ds-*.
 *
 * Why a token layer at all: before this, components hardcoded their own values —
 * `0.75rem` radius here, `0.5rem` there, `200ms ease-out` in one file and
 * `150ms` in the next, and `border: 1px solid var(--color-secondary)` in several
 * places, which is a *near-black* hairline (the default secondary is #0f172a),
 * not the near-invisible one the design wanted. Independent guesses read as
 * amateur work no matter how good each guess is. One scale, used everywhere,
 * reads as designed.
 *
 * Nothing here emits CSS on its own except the small primitives at the bottom
 * (.hi, .ds-focusable, .ds-container). It is a vocabulary, not a stylesheet.
 */

:root {
  /* ── Hairlines & surfaces ───────────────────────────────────────────────
     Derived from the palette rather than fixed, so a client's dark or warm
     background keeps borders that sit *just* off it instead of a grey that
     fights it. 8% is the Stripe/Linear hairline: present, never a line you
     notice. */
  --ds-hairline: color-mix(in srgb, var(--color-text) 8%, transparent);
  --ds-hairline-strong: color-mix(in srgb, var(--color-text) 14%, transparent);
  --ds-hairline-inset: color-mix(in srgb, var(--color-text) 5%, transparent);

  --ds-surface: var(--color-background);
  --ds-surface-raised: color-mix(in srgb, var(--color-background) 97%, var(--color-text));
  --ds-surface-sunken: color-mix(in srgb, var(--color-background) 94%, var(--color-text));
  --ds-surface-inverted: var(--color-secondary);

  /* Tints for state, not decoration: a hover that changes the surface by 4% is
     felt more than seen, which is the point. */
  --ds-tint-hover: color-mix(in srgb, var(--color-text) 4%, transparent);
  --ds-tint-active: color-mix(in srgb, var(--color-text) 7%, transparent);
  --ds-tint-primary: color-mix(in srgb, var(--color-primary) 8%, transparent);
  --ds-tint-primary-strong: color-mix(in srgb, var(--color-primary) 14%, transparent);

  /* ── Elevation ──────────────────────────────────────────────────────────
     Real shadows are two shadows: a tight contact shadow where the object meets
     the surface, and a wide diffuse one from the ambient light. A single blurred
     box-shadow is the thing that makes a card look like 2013. Alpha stays low
     and the colour is pulled from --color-text, so on a warm or dark palette the
     shadow tints with it instead of going muddy grey. */
  --ds-shadow-color: color-mix(in srgb, var(--color-text) 90%, black);
  --ds-shadow-xs:
    0 1px 2px -1px color-mix(in srgb, var(--ds-shadow-color) 8%, transparent);
  --ds-shadow-sm:
    0 1px 2px -1px color-mix(in srgb, var(--ds-shadow-color) 8%, transparent),
    0 2px 6px -2px color-mix(in srgb, var(--ds-shadow-color) 6%, transparent);
  --ds-shadow-md:
    0 1px 2px -1px color-mix(in srgb, var(--ds-shadow-color) 7%, transparent),
    0 4px 12px -3px color-mix(in srgb, var(--ds-shadow-color) 9%, transparent),
    0 12px 28px -8px color-mix(in srgb, var(--ds-shadow-color) 7%, transparent);
  --ds-shadow-lg:
    0 1px 2px -1px color-mix(in srgb, var(--ds-shadow-color) 6%, transparent),
    0 8px 20px -4px color-mix(in srgb, var(--ds-shadow-color) 10%, transparent),
    0 24px 48px -12px color-mix(in srgb, var(--ds-shadow-color) 12%, transparent);
  --ds-shadow-xl:
    0 2px 4px -2px color-mix(in srgb, var(--ds-shadow-color) 6%, transparent),
    0 16px 32px -8px color-mix(in srgb, var(--ds-shadow-color) 12%, transparent),
    0 40px 72px -16px color-mix(in srgb, var(--ds-shadow-color) 14%, transparent);
  /* A 1px inner top highlight — the thing that makes a surface read as lit from
     above rather than printed on. Composed *with* a shadow, never alone. */
  --ds-sheen: inset 0 1px 0 0 color-mix(in srgb, white 55%, transparent);

  /* ── Radii ──────────────────────────────────────────────────────────────
     --button-radius is admin-configurable and must keep winning on buttons; the
     rest is a fixed scale so a card, its image and its badge stay concentric. */
  --ds-radius-xs: 0.375rem;
  --ds-radius-sm: 0.5rem;
  --ds-radius-md: 0.75rem;
  --ds-radius-lg: 1rem;
  --ds-radius-xl: 1.25rem;
  --ds-radius-2xl: 1.75rem;
  --ds-radius-full: 9999px;

  /* ── Motion ─────────────────────────────────────────────────────────────
     One easing curve does most of the work: a fast start that settles long, so
     an element arrives before you finish looking at it. `ease-out` (the CSS
     keyword) is too lazy at the tail to feel expensive. */
  --ds-ease: cubic-bezier(0.16, 1, 0.3, 1);
  --ds-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ds-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  --ds-duration-instant: 90ms;
  --ds-duration-fast: 160ms;
  --ds-duration-base: 240ms;
  --ds-duration-slow: 400ms;
  --ds-duration-slower: 640ms;

  /* ── Type ───────────────────────────────────────────────────────────────
     Fluid across the whole range, so there is no breakpoint where a heading
     visibly jumps. Tracking tightens as size grows — the optical correction that
     separates a set headline from a big one. */
  --ds-text-2xs: 0.6875rem;
  --ds-text-xs: 0.75rem;
  --ds-text-sm: 0.8125rem;
  --ds-text-base: 0.9375rem;
  --ds-text-md: 1rem;
  --ds-text-lg: clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem);
  --ds-text-xl: clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);
  --ds-text-2xl: clamp(1.5rem, 1.3rem + 1vw, 2rem);
  --ds-text-3xl: clamp(1.875rem, 1.5rem + 1.6vw, 2.75rem);
  --ds-text-4xl: clamp(2.25rem, 1.6rem + 2.8vw, 3.75rem);

  --ds-tracking-wide: 0.08em;
  --ds-tracking-normal: 0em;
  --ds-tracking-tight: -0.011em;
  --ds-tracking-tighter: -0.022em;
  --ds-tracking-tightest: -0.032em;

  --ds-leading-none: 1;
  --ds-leading-tight: 1.15;
  --ds-leading-snug: 1.35;
  --ds-leading-normal: 1.55;
  --ds-leading-relaxed: 1.7;

  --ds-weight-normal: 400;
  --ds-weight-medium: 500;
  --ds-weight-semibold: 600;
  --ds-weight-bold: 700;

  /* ── Space ──────────────────────────────────────────────────────────────
     A 4px base, but the large end is fluid: generous white space is the single
     biggest difference between a premium layout and a cramped one, and it should
     grow with the viewport instead of staying at its mobile value on a 27" screen. */
  --ds-space-1: 0.25rem;
  --ds-space-2: 0.5rem;
  --ds-space-3: 0.75rem;
  --ds-space-4: 1rem;
  --ds-space-5: 1.25rem;
  --ds-space-6: 1.5rem;
  --ds-space-8: 2rem;
  --ds-space-10: 2.5rem;
  --ds-space-12: clamp(2.5rem, 2rem + 2vw, 3.5rem);
  --ds-space-16: clamp(3rem, 2.2rem + 3.4vw, 5rem);
  --ds-space-24: clamp(4rem, 2.8rem + 5vw, 7.5rem);

  /* ── Layout ─────────────────────────────────────────────────────────────── */
  --ds-container: 80rem;
  --ds-container-narrow: 46rem;
  --ds-gutter: clamp(1rem, 0.5rem + 2.2vw, 2.5rem);

  /* ── Gradients ──────────────────────────────────────────────────────────
     Barely there on purpose. A gradient you can name is a gradient that dates;
     these read as light falling on a surface, which does not. */
  --ds-gradient-surface: linear-gradient(
    180deg,
    color-mix(in srgb, var(--color-text) 1.5%, var(--color-background)) 0%,
    var(--color-background) 60%
  );
  --ds-gradient-primary: linear-gradient(
    135deg,
    var(--color-primary) 0%,
    color-mix(in srgb, var(--color-primary) 82%, var(--color-text)) 100%
  );
  --ds-gradient-halo: radial-gradient(
    60% 60% at 50% 0%,
    color-mix(in srgb, var(--color-primary) 10%, transparent) 0%,
    transparent 100%
  );
  --ds-gradient-hairline: linear-gradient(
    90deg,
    transparent 0%,
    var(--ds-hairline-strong) 18%,
    var(--ds-hairline-strong) 82%,
    transparent 100%
  );

  /* ── Focus ──────────────────────────────────────────────────────────────
     One ring, everywhere, drawn with box-shadow so it follows border-radius
     instead of boxing a rounded control in a rectangle. The white gap between
     control and ring keeps it legible on any surface. */
  --ds-focus-ring:
    0 0 0 2px var(--color-background),
    0 0 0 4px color-mix(in srgb, var(--color-primary) 65%, transparent);
  --ds-z-sticky: 20;
  --ds-z-overlay: 40;
  --ds-z-modal: 60;
}

/* ── Primitives ──────────────────────────────────────────────────────────── */

/* Inlined Heroicons (Frontend::IconsHelper). They carry width/height attributes
   for the no-CSS case; these rules make them behave like text: sized in `em`,
   coloured by `currentColor`, aligned to the baseline of the label beside them. */
.hi {
  display: inline-block;
  width: 1em;
  height: 1em;
  flex: none;
  vertical-align: -0.135em;
  color: currentColor;
  /* Heroicons' 1.5 stroke is tuned for 24px. Below ~20px it reads heavy, so
     thin it as the icon shrinks. */
  stroke-width: 1.5;
}

.hi--outline {
  fill: none;
}

.hi--solid,
.hi--micro {
  fill: currentColor;
  stroke: none;
}

.ds-focusable:focus-visible,
.ds-focus-ring:focus-visible {
  outline: none;
  box-shadow: var(--ds-focus-ring);
}

/* Focus that only shows for keyboards, but never *silently* — if :focus-visible
   is unsupported the plain :focus ring below is what the user gets. */
.ds-focusable:focus:not(:focus-visible) {
  box-shadow: none;
}

.ds-container {
  width: 100%;
  max-width: var(--ds-container);
  margin-inline: auto;
  padding-inline: var(--ds-gutter);
}

.ds-container--narrow {
  max-width: var(--ds-container-narrow);
}

/* A hairline that fades at both ends instead of butting into the gutter. */
.ds-rule {
  height: 1px;
  border: 0;
  background: var(--ds-gradient-hairline);
}

/* Tabular numerals for anything a customer compares down a column: prices,
   quantities, order totals. Proportional digits make a price column wobble. */
.ds-numeric {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Truncate to N lines without a JS measure pass. */
.ds-clamp-1,
.ds-clamp-2,
.ds-clamp-3 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.ds-clamp-1 { -webkit-line-clamp: 1; }
.ds-clamp-2 { -webkit-line-clamp: 2; }
.ds-clamp-3 { -webkit-line-clamp: 3; }

/* Visible to screen readers, not to the eye. */
.ds-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A skeleton that shimmers rather than blinks — the blink reads as a bug. */
.ds-skeleton {
  background:
    linear-gradient(
      90deg,
      var(--ds-surface-sunken) 25%,
      color-mix(in srgb, var(--color-text) 3%, var(--color-background)) 37%,
      var(--ds-surface-sunken) 63%
    )
    0 0 / 400% 100%;
  animation: ds-shimmer 1.4s var(--ds-ease-in-out) infinite;
  border-radius: var(--ds-radius-sm);
}

@keyframes ds-shimmer {
  to { background-position: -135% 0; }
}

/* ── Reduced motion ──────────────────────────────────────────────────────
   A blanket rule, so a component author cannot forget it. Durations collapse to
   ~0 rather than `animation: none`, which keeps animation *end* events firing —
   scroll-reveal and the mini-cart rely on them, and `none` would leave elements
   stuck at their initial opacity. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

.frontend-main-menu {
  --frontend-menu-dark-background: var(--color-secondary);
  --frontend-menu-logo-height: 1.8rem;
  color: var(--frontend-menu-text);
  position: relative;
  transition: color 0.2s ease;
  z-index: 1000;
}

.frontend-main-menu__shell {
  background: var(--frontend-menu-shell-background);
  box-shadow: var(--frontend-menu-shell-shadow);
  border-bottom: 1px solid var(--frontend-menu-border);
  transition: background-color 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.frontend-main-menu__topbar {
  background: var(--frontend-menu-shell-background);
  border-bottom: 1px solid var(--frontend-menu-border);
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.frontend-main-menu__announcement {
  background: var(--frontend-menu-shell-background);
  border-top: 1px solid var(--frontend-menu-border);
  border-bottom: 1px solid var(--frontend-menu-border);
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.frontend-main-menu__announcement-body {
  font-size: 0.875rem;
  line-height: 1.5;
}

.frontend-main-menu__announcement-body a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.frontend-main-menu__topbar-inner {
  flex-wrap: wrap;
}

.frontend-main-menu__burger {
  color: var(--frontend-menu-text);
}

.frontend-main-menu__burger-icon {
  display: inline-flex;
  flex-direction: column;
  gap: 0.3rem;
  width: 1.45rem;
}

.frontend-main-menu__burger-line {
  background: currentColor;
  border-radius: 999px;
  display: block;
  height: 2px;
  transform-origin: center;
  transition: opacity 0.18s ease, transform 0.22s ease;
  width: 100%;
}

.frontend-main-menu__mobile-nav--open .frontend-main-menu__burger-line:first-child {
  transform: translateY(0.42rem) rotate(45deg);
}

.frontend-main-menu__mobile-nav--open .frontend-main-menu__burger-line:nth-child(2) {
  opacity: 0;
  transform: scaleX(0);
}

.frontend-main-menu__mobile-nav--open .frontend-main-menu__burger-line:last-child {
  transform: translateY(-0.42rem) rotate(-45deg);
}

.frontend-main-menu__mobile-backdrop {
  background: color-mix(in srgb, black 42%, transparent);
  bottom: 0;
  left: 0;
  opacity: 0;
  pointer-events: none;
  position: fixed;
  right: 0;
  top: 6.5rem;
  transition: opacity 0.22s ease, visibility 0.22s ease;
  visibility: hidden;
  z-index: 1000;
}

.frontend-main-menu__mobile-panel {
  background: var(--frontend-menu-mobile-background);
  box-shadow: 24px 0 48px color-mix(in srgb, black 16%, transparent);
  display: block;
  height: calc(100vh - 6.5rem);
  height: calc(100dvh - 6.5rem);
  left: 0;
  max-width: none;
  opacity: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  pointer-events: none;
  position: fixed;
  top: 6.5rem;
  transform: translateX(-100%);
  transition: opacity 0.22s ease, transform 0.22s ease, visibility 0.22s ease;
  visibility: hidden;
  width: 100vw;
  z-index: 1001;
  -webkit-overflow-scrolling: touch;
}

details.frontend-main-menu__mobile-nav:not([open]) .frontend-main-menu__mobile-backdrop,
details.frontend-main-menu__mobile-nav:not([open]) .frontend-main-menu__mobile-panel {
  display: block;
}

.frontend-main-menu__mobile-nav--open .frontend-main-menu__mobile-backdrop {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
}

.frontend-main-menu__mobile-nav--open .frontend-main-menu__mobile-panel {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(0);
  visibility: visible;
}

.frontend-main-menu__mobile-parent {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
}

.frontend-main-menu__mobile-item {
  display: contents;
  interpolate-size: allow-keywords;
}

.frontend-main-menu__mobile-toggle {
  color: var(--frontend-menu-text);
}

.frontend-main-menu__mobile-submenu {
  grid-column: 1 / -1;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transform: translateY(-0.35rem);
  transition: max-height 0.22s ease, opacity 0.18s ease, transform 0.18s ease;
}

.frontend-main-menu__mobile-item--open .frontend-main-menu__mobile-submenu {
  max-height: 32rem;
  opacity: 1;
  transform: translateY(0);
}

@media (min-width: 640px) {
  .frontend-main-menu__mobile-panel {
    max-width: min(24rem, 86vw);
    width: 100%;
  }
}

.frontend-main-menu__desktop-row {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.frontend-main-menu__desktop-row--left {
  flex: 1 1 auto;
  justify-content: space-between;
}

.frontend-main-menu__desktop-row--center {
  position: relative;
  justify-content: center;
}

.frontend-main-menu__desktop-row--centered-nav {
  justify-content: space-between;
  position: relative;
}

.frontend-main-menu__desktop-row--centered-nav .frontend-main-menu__nav {
  /* Transform-free centering (left/right 0 + margin auto + fit-content): a
     translateX(-50%) here made the nav the CONTAINING BLOCK for every
     position:fixed descendant, so the mini-cart drawer opened as a 42px box
     trapped inside the header instead of a viewport overlay. */
  left: 0;
  right: 0;
  margin-inline: auto;
  position: absolute;
  width: fit-content;
}

.frontend-main-menu__desktop-row--centered-nav .frontend-main-menu__actions,
.frontend-main-menu__desktop-row--centered-nav > .frontend-main-menu__languages {
  margin-left: auto;
}

.frontend-main-menu__desktop-row--center .frontend-main-menu__actions,
.frontend-main-menu__desktop-row--center > .frontend-main-menu__languages {
  position: absolute;
  right: 0;
}

.frontend-main-menu__desktop-row--right {
  flex: 1 1 auto;
  justify-content: flex-end;
}

.frontend-main-menu__desktop-row--right-nav {
  flex: 1 1 auto;
  justify-content: flex-start;
}

.frontend-main-menu__desktop-row--right-nav .frontend-main-menu__nav {
  margin-left: auto;
}

.frontend-main-menu__list {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.frontend-main-menu__list--center {
  justify-content: center;
}

.frontend-main-menu__list--right {
  justify-content: flex-end;
}

.frontend-main-menu__top-list {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  justify-content: flex-end;
  list-style: none;
  margin: 0;
  padding: 0;
}

.frontend-main-menu__summary::-webkit-details-marker,
.frontend-main-menu__mobile-summary::-webkit-details-marker,
.frontend-main-menu__language-summary::-webkit-details-marker {
  display: none;
}

.frontend-main-menu__summary,
.frontend-main-menu__mobile-summary,
.frontend-main-menu__language-summary {
  list-style: none;
}

.frontend-main-menu__link,
.frontend-main-menu__top-link,
.frontend-main-menu__summary,
.frontend-main-menu__mobile-link,
.frontend-main-menu__language-summary {
  color: var(--frontend-menu-text);
}

.frontend-main-menu__link:visited,
.frontend-main-menu__top-link:visited,
.frontend-main-menu__top-dropdown-link:visited,
.frontend-main-menu__mobile-link:visited,
.frontend-main-menu__dropdown-link:visited,
.frontend-main-menu__language-option:visited {
  color: var(--frontend-menu-text);
}

.frontend-main-menu__dropdown-copy:visited {
  color: var(--frontend-menu-muted);
}

.frontend-main-menu__item-active {
  background: var(--frontend-menu-surface-hover);
}

.frontend-main-menu__item[open] .frontend-main-menu__summary,
.frontend-main-menu__mobile-item[open] .frontend-main-menu__mobile-summary,
.frontend-main-menu__language-picker[open] .frontend-main-menu__language-summary {
  background: var(--frontend-menu-surface-hover);
}

.frontend-main-menu__summary-icon,
.frontend-main-menu__top-icon,
.frontend-main-menu__mobile-icon,
.frontend-main-menu__language-icon {
  color: var(--frontend-menu-muted);
  transition: transform 0.2s ease;
}

.frontend-main-menu__item[open] .frontend-main-menu__summary .frontend-main-menu__summary-icon,
.frontend-main-menu__top-item:hover .frontend-main-menu__top-icon,
.frontend-main-menu__top-item:focus-within .frontend-main-menu__top-icon,
.frontend-main-menu__mobile-item--open .frontend-main-menu__mobile-toggle .frontend-main-menu__mobile-icon,
.frontend-main-menu__language-picker[open] .frontend-main-menu__language-summary .frontend-main-menu__language-icon,
.frontend-main-menu__mobile-nav--open .frontend-main-menu__mobile-summary .frontend-main-menu__mobile-icon {
  transform: rotate(180deg);
}

.frontend-main-menu__dropdown {
  background: var(--frontend-menu-surface);
  border: 1px solid var(--frontend-menu-border);
  box-shadow: 0 20px 48px color-mix(in srgb, black 14%, transparent);
  position: relative;
  z-index: 1001;
}

.frontend-main-menu__top-dropdown {
  background: var(--frontend-menu-surface);
  border: 1px solid var(--frontend-menu-border);
  border-radius: 1rem;
  box-shadow: 0 20px 48px color-mix(in srgb, black 14%, transparent);
  padding: 0.5rem;
  position: relative;
  z-index: 1001;
}

.frontend-main-menu__language-picker,
.frontend-main-menu__languages,
.frontend-main-menu__actions {
  position: relative;
  cursor: pointer;
}

.frontend-main-menu__language-dropdown {
  background: var(--frontend-menu-surface);
  border: 1px solid var(--frontend-menu-border);
  border-radius: 1rem;
  box-shadow: 0 20px 48px color-mix(in srgb, black 14%, transparent);
  min-width: 10rem;
  opacity: 0;
  padding: 0.5rem;
  pointer-events: none;
  position: absolute;
  right: 0;
  top: calc(100% + 0.75rem);
  transform: translateY(-0.35rem);
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s ease;
  visibility: hidden;
  z-index: 1002;
}

.frontend-main-menu__language-picker--open .frontend-main-menu__language-dropdown {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
  visibility: visible;
}

.frontend-main-menu__dropdown-wrap {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-0.25rem);
  transition: opacity 0.16s ease, transform 0.16s ease, visibility 0.16s ease;
  visibility: hidden;
  z-index: 1001;
}

.frontend-main-menu__top-dropdown-wrap {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-0.25rem);
  transition: opacity 0.16s ease, transform 0.16s ease, visibility 0.16s ease;
  visibility: hidden;
  z-index: 1001;
}

.frontend-main-menu__item:hover .frontend-main-menu__dropdown-wrap,
.frontend-main-menu__item:focus-within .frontend-main-menu__dropdown-wrap {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
  visibility: visible;
}

.frontend-main-menu__top-item:hover .frontend-main-menu__top-dropdown-wrap,
.frontend-main-menu__top-item:focus-within .frontend-main-menu__top-dropdown-wrap {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
  visibility: visible;
}

.frontend-main-menu__item:hover .frontend-main-menu__summary-icon,
.frontend-main-menu__item:focus-within .frontend-main-menu__summary-icon {
  transform: rotate(180deg);
}

.frontend-main-menu__dropdown-link,
.frontend-main-menu__dropdown-title,
.frontend-main-menu__top-dropdown-link,
.frontend-main-menu__mobile-link,
.frontend-main-menu__language-option {
  color: var(--frontend-menu-text);
}

.frontend-main-menu__dropdown-copy {
  color: var(--frontend-menu-muted);
}

.frontend-main-menu__dropdown-link:hover,
.frontend-main-menu__dropdown-copy:hover,
.frontend-main-menu__top-dropdown-link:hover,
.frontend-main-menu__mobile-link:hover,
.frontend-main-menu__language-option:hover {
  background: var(--frontend-menu-surface-hover);
}

.frontend-main-menu__link,
.frontend-main-menu__top-link,
.frontend-main-menu__mobile-link,
.frontend-main-menu__language-summary,
.frontend-main-menu__mobile-toggle {
  transition: background-color 0.18s ease, color 0.18s ease;
}

.frontend-main-menu__link:hover,
.frontend-main-menu__link:focus-visible,
.frontend-main-menu__top-link:hover,
.frontend-main-menu__top-link:focus-visible,
.frontend-main-menu__mobile-link:hover,
.frontend-main-menu__mobile-link:focus-visible,
.frontend-main-menu__language-summary:hover,
.frontend-main-menu__language-summary:focus-visible,
.frontend-main-menu__mobile-toggle:hover,
.frontend-main-menu__mobile-toggle:focus-visible {
  background: var(--frontend-menu-surface-hover);
}

.frontend-main-menu__item:hover > .frontend-main-menu__link,
.frontend-main-menu__item:focus-within > .frontend-main-menu__link {
  background: var(--frontend-menu-surface-hover);
}

.frontend-main-menu__logo-image {
  max-height: var(--frontend-menu-logo-height);
  width: auto;
}

.frontend-main-menu__logo-fallback {
  color: var(--frontend-menu-text);
}

.frontend-main-menu__language-flag {
  border: 1px solid var(--frontend-menu-border);
}

.frontend-main-menu__flag-fallback {
  background: var(--frontend-menu-surface-hover);
  border: 1px solid var(--frontend-menu-border);
  color: var(--frontend-menu-text);
}

.frontend-main-menu--fixed,
.frontend-main-menu--transparent-fixed {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 1000;
}

.frontend-main-menu--transparent {
  position: absolute;
  inset-inline: 0;
  top: 0;
  z-index: 1000;
}

.frontend-main-menu--transparent .frontend-main-menu__shell,
.frontend-main-menu--transparent .frontend-main-menu__announcement,
.frontend-main-menu--transparent .frontend-main-menu__topbar,
.frontend-main-menu--transparent-fixed .frontend-main-menu__shell,
.frontend-main-menu--transparent-fixed .frontend-main-menu__announcement,
.frontend-main-menu--transparent-fixed .frontend-main-menu__topbar {
  background: transparent;
  box-shadow: none;
  border-color: transparent;
}

.frontend-main-menu--transparent-fixed.frontend-main-menu--scrolled .frontend-main-menu__shell,
.frontend-main-menu--transparent-fixed.frontend-main-menu--scrolled .frontend-main-menu__announcement,
.frontend-main-menu--transparent-fixed.frontend-main-menu--scrolled .frontend-main-menu__topbar {
  background: var(--frontend-menu-shell-background);
  box-shadow: var(--frontend-menu-shell-shadow);
  border-color: var(--frontend-menu-border);
}

.frontend-main-menu--squish {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 1000;
  padding-top: 1rem;
}

.frontend-main-menu--squish .frontend-main-menu__shell,
.frontend-main-menu--squish .frontend-main-menu__announcement,
.frontend-main-menu--squish .frontend-main-menu__topbar {
  margin-inline: auto;
  max-width: min(94rem, calc(100% - 2rem));
  border-radius: 1.5rem;
  backdrop-filter: blur(18px);
}

/* Account area — the signed-in customer panel (dashboard, orders, addresses,
 * profile, password).
 *
 * These pages moved their H1 out of the page_shell header and into the content
 * column, so the air the header used to provide has to come from the layout:
 * .account-layout puts it back, and page-shell__header--flush drops the header's
 * own bottom margin because the column stack (space-y-10) owns that rhythm now.
 *
 * Every value is a --ds-* token, colour only from --color-*, so a client palette
 * flows through untouched.
 */

.account-layout {
  padding-top: var(--ds-space-8);
}

@media (min-width: 768px) {
  .account-layout {
    padding-top: var(--ds-space-10);
  }
}

/* Title -> supporting line. A hair more air than the shell default, because in
   the column the line runs the full measure and needs the separation. */
.page-shell__header--flush .page-shell__heading {
  gap: var(--ds-space-3);
}

/* ── Navigation card ─────────────────────────────────────────────────────── */

/* A surface, not a bare list: hairline + soft shadow, matching the storefront
   cards. Sticky so the panel stays reachable while a long order list or a tall
   profile form scrolls; it only engages from lg up, where the sidebar sits
   beside the content instead of stacking above it.
   Not md: a tablet gives the panel the full width above the content (18rem of
   navigation beside a 44rem column left the order lines unreadable), and a
   full-width sticky bar would cover the page as it scrolls. */
.account-nav {
  border-radius: var(--ds-radius-lg);
  background-color: var(--color-surface, var(--color-background));
  border: 1px solid var(--ds-hairline);
  box-shadow: var(--ds-shadow-sm);
  padding: var(--ds-space-3);
}

@media (min-width: 1024px) {
  .account-nav {
    position: sticky;
    /* Clears the sticky site header if the theme has one. */
    top: var(--ds-space-6);
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   article_list — editorial cards for the Articoli section.

   Three layouts off one card: a grid (default), a stacked list where the image
   sits beside the text, and a highlight where the first card runs full width.
   Every rule is scoped with [data-module="article_list"] so nothing leaks onto
   other modules. Colours come from the design-system tokens only.
   ────────────────────────────────────────────────────────────────────────── */

[data-module="article_list"] {
  --al-radius: var(--ds-radius-lg, 1rem);
  --al-hairline: var(--ds-hairline, color-mix(in srgb, var(--color-text) 12%, transparent));
}

/* ── The card ─────────────────────────────────────────────────────────────── */

[data-module="article_list"] .article-card {
  height: 100%;
}

[data-module="article_list"] .article-card__link {
  display: flex;
  flex-direction: column;
  height: 100%;
  gap: 1rem;
  color: inherit;
  text-decoration: none;
}

[data-module="article_list"] .article-card__media {
  position: relative;
  overflow: hidden;
  border-radius: var(--al-radius);
  aspect-ratio: var(--article-card-aspect, 3 / 2);
  background: var(--color-surface);
}

[data-module="article_list"] .article-card__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The image is the only thing that moves on hover: a card that lifts, tilts
     and darkens all at once reads as a button, not as an article. */
  transition: transform var(--ds-duration-base, 0.35s) var(--ds-ease, ease);
}

[data-module="article_list"] .article-card__link:hover .article-card__image {
  transform: scale(1.04);
}

@media (prefers-reduced-motion: reduce) {
  [data-module="article_list"] .article-card__image {
    transition: none;
  }
  [data-module="article_list"] .article-card__link:hover .article-card__image {
    transform: none;
  }
}

[data-module="article_list"] .article-card__body {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

[data-module="article_list"] .article-card__date {
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

[data-module="article_list"] .article-card__title {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--color-text);
}

[data-module="article_list"] .article-card__excerpt {
  color: var(--color-text-muted);
  line-height: 1.6;
}

[data-module="article_list"] .article-card__cta {
  margin-top: auto;
  padding-top: 0.25rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-primary);
}

[data-module="article_list"] .article-card__link:hover .article-card__cta {
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* ── Stacked list: image beside the text, one article per row ─────────────── */

[data-module="article_list"] .article-list__grid--stacked {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

[data-module="article_list"] .article-list__grid--stacked .article-card__link {
  flex-direction: row;
  align-items: flex-start;
  gap: 1.5rem;
}

[data-module="article_list"] .article-list__grid--stacked .article-card__media {
  flex: 0 0 clamp(8rem, 28%, 20rem);
}

@media (max-width: 640px) {
  [data-module="article_list"] .article-list__grid--stacked .article-card__link {
    flex-direction: column;
  }
  [data-module="article_list"] .article-list__grid--stacked .article-card__media {
    flex-basis: auto;
    width: 100%;
  }
}

/* ── The big treatment ────────────────────────────────────────────────────────
 * Two ways in: the `highlight` layout promotes its FIRST card, and any grid
 * promotes a card that turns out to be the ONLY one. The second is the reason
 * this is not just a highlight rule — a band that resolves to a single article
 * would otherwise render one narrow column in a three-column grid, which reads
 * as a mistake rather than as a feature. Pick three and you get three abreast;
 * pick one and it fills the band. */

@media (min-width: 640px) {
  [data-module="article_list"].article-list--highlight .article-card:first-child,
  [data-module="article_list"] .article-list__grid .article-card:only-child {
    grid-column: 1 / -1;
  }

  [data-module="article_list"].article-list--highlight .article-card:first-child .article-card__link,
  [data-module="article_list"] .article-list__grid .article-card:only-child .article-card__link {
    flex-direction: row;
    align-items: center;
    gap: 2rem;
  }

  [data-module="article_list"].article-list--highlight .article-card:first-child .article-card__media,
  [data-module="article_list"] .article-list__grid .article-card:only-child .article-card__media {
    flex: 0 0 55%;
  }

  [data-module="article_list"].article-list--highlight .article-card:first-child .article-card__title,
  [data-module="article_list"] .article-list__grid .article-card:only-child .article-card__title {
    font-size: 1.75rem;
  }
}

/* ── Pagination ───────────────────────────────────────────────────────────── */

[data-module="article_list"] .article-list__pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 3rem;
}

[data-module="article_list"] .article-list__page {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.5rem;
  height: 2.5rem;
  padding: 0 0.75rem;
  border: 1px solid var(--al-hairline);
  border-radius: var(--ds-radius-sm, 0.5rem);
  color: var(--color-text);
  text-decoration: none;
  transition: background-color var(--ds-duration-fast, 0.15s) var(--ds-ease, ease);
}

[data-module="article_list"] .article-list__page:hover {
  background: var(--color-surface);
}

[data-module="article_list"] .article-list__page--current {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
  font-weight: 600;
}

/* ──────────────────────────────────────────────────────────────────────────
   The article page.

   A measured reading column rather than the full-width page shell: an article
   is prose, and prose set edge to edge is not read. The column width is the one
   thing this file really decides; everything else is spacing and tokens.
   ────────────────────────────────────────────────────────────────────────── */

.article-page {
  --article-measure: 44rem;
}

.article-page__container {
  max-width: var(--article-measure);
  margin-inline: auto;
  padding-inline: 1.5rem;
}

.article-page__header {
  padding-block: clamp(2rem, 5vw, 4rem) clamp(1.5rem, 3vw, 2.5rem);
}

.article-page__back {
  margin-bottom: 1.5rem;
}

.article-page__back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-text-muted);
  text-decoration: none;
}

.article-page__back-link:hover {
  color: var(--color-primary);
}

.article-page__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.article-page__badge {
  padding: 0.15rem 0.55rem;
  border-radius: 9999px;
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
  color: var(--color-primary);
  font-weight: 600;
  letter-spacing: 0.06em;
}

.article-page__title {
  font-size: clamp(2rem, 4.5vw, 3rem);
  line-height: 1.15;
  font-weight: 700;
  color: var(--color-text);
  text-wrap: balance;
}

.article-page__standfirst {
  margin-top: 1rem;
  font-size: 1.125rem;
  line-height: 1.6;
  color: var(--color-text-muted);
  text-wrap: pretty;
}

/* The cover runs wider than the text column — a photograph wants room that a
   paragraph does not — but stays inside the page container. */
.article-page__cover {
  max-width: 68rem;
  margin-inline: auto;
  padding-inline: 1.5rem;
  margin-bottom: clamp(2rem, 4vw, 3rem);
}

.article-page__cover-image {
  width: 100%;
  height: auto;
  border-radius: var(--ds-radius-lg, 1rem);
  object-fit: cover;
}

/* Modules keep their own full-bleed behaviour; only the text ones are pulled
   back to the reading measure, which is where the body copy lives. */
.article-page__body [data-module="text-images-base"] .container,
.article-page__body [data-module="text_images_base"] .container {
  max-width: var(--article-measure);
}

.article-page__body {
  padding-bottom: clamp(3rem, 6vw, 5rem);
}

.btn {
  align-items: center;
  border: 1px solid transparent;
  border-radius: var(--button-radius);
  cursor: pointer;
  display: inline-flex;
  font-weight: 500;
  gap: .5rem;
  justify-content: center;
  line-height: 1.2;
  padding: .75rem 1.25rem;
  text-decoration: none;
  transition: background-color .18s ease, border-color .18s ease, color .18s ease, transform .18s ease;
}

[data-module] .text-left {
  --module-buttons-justify: flex-start;
}

[data-module] .text-center {
  --module-buttons-justify: center;
}

[data-module] .text-right {
  --module-buttons-justify: flex-end;
}

[data-module-buttons="true"] {
  justify-content: var(--module-buttons-justify, flex-start);
}

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

.btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
}

.btn-primary {
  background-color: var(--color-primary);
  color: var(--color-primary-contrast);
}

.btn-primary:hover {
  background-color: var(--color-primary-hover);
}

/* Ghost: transparent, inherits the surrounding text colour (adapts to dark
   heroes automatically). Used as the secondary button style. */
.btn-ghost {
  background-color: transparent;
  border-color: currentColor;
  color: inherit;
}

.btn-ghost:hover {
  background-color: color-mix(in srgb, currentColor 12%, transparent);
}

.btn-secondary {
  background-color: var(--color-secondary);
  color: var(--color-secondary-contrast);
}

.btn-secondary:hover {
  background-color: var(--color-secondary-hover);
}

.btn-success {
  background-color: var(--color-success);
  color: var(--color-success-contrast);
}

.btn-success:hover {
  background-color: var(--color-success-hover);
}

.btn-danger {
  background-color: var(--color-danger);
  color: var(--color-danger-contrast);
}

.btn-danger:hover {
  background-color: var(--color-danger-hover);
}

.btn-warning {
  background-color: var(--color-warning);
  color: var(--color-warning-contrast);
}

.btn-warning:hover {
  background-color: var(--color-warning-hover);
}

.btn-outline-primary {
  background-color: transparent;
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.btn-outline-primary:hover {
  background-color: var(--color-primary);
  color: var(--color-primary-contrast);
}

.btn-outline-secondary {
  background-color: transparent;
  border-color: var(--color-secondary);
  color: var(--color-secondary);
}

.btn-outline-secondary:hover {
  background-color: var(--color-secondary);
  color: var(--color-secondary-contrast);
}

.btn-outline-success {
  background-color: transparent;
  border-color: var(--color-success);
  color: var(--color-success);
}

.btn-outline-success:hover {
  background-color: var(--color-success);
  color: var(--color-success-contrast);
}

.btn-outline-danger {
  background-color: transparent;
  border-color: var(--color-danger);
  color: var(--color-danger);
}

.btn-outline-danger:hover {
  background-color: var(--color-danger);
  color: var(--color-danger-contrast);
}

.btn-outline-warning {
  background-color: transparent;
  border-color: var(--color-warning);
  color: var(--color-warning);
}

.btn-outline-warning:hover {
  background-color: var(--color-warning);
  color: var(--color-warning-contrast);
}

.btn-light {
  background-color: #ffffff;
  border-color: #ffffff;
  color: #111827;
}

.btn-light:hover {
  background-color: rgba(255, 255, 255, 0.85);
  border-color: rgba(255, 255, 255, 0.85);
}

.btn-dark {
  background-color: #111827;
  border-color: #111827;
  color: #ffffff;
}

.btn-dark:hover {
  background-color: #1f2937;
  border-color: #1f2937;
}

.btn-outline-light {
  background-color: transparent;
  border-color: #ffffff;
  color: #ffffff;
}

.btn-outline-light:hover {
  background-color: #ffffff;
  color: #111827;
}

.btn-outline-dark {
  background-color: transparent;
  border-color: #111827;
  color: #111827;
}

.btn-outline-dark:hover {
  background-color: #111827;
  color: #ffffff;
}

.btn-link {
  background-color: transparent;
  border-color: transparent;
  color: var(--color-primary);
  padding-left: 0;
  padding-right: 0;
}

.btn-sm {
  font-size: .875rem;
  padding: .5rem .875rem;
}

.btn-lg {
  font-size: 1.125rem;
  padding: 1rem 1.5rem;
}

.text-primary-contrast {
  color: var(--color-primary-contrast);
}

.text-secondary-contrast {
  color: var(--color-secondary-contrast);
}

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

.blaze-slider {
  --slides-to-show: 1;
  --slide-gap: 20px;
  direction: ltr;
}

.blaze-container {
  position: relative;
}

.blaze-track-container {
  overflow: hidden;
}

.blaze-track {
  --slide-width: calc((100% - (var(--slides-to-show) - 1) * var(--slide-gap)) / var(--slides-to-show));
  box-sizing: border-box;
  display: flex;
  gap: var(--slide-gap);
  touch-action: pan-y;
  will-change: transform;
}

.blaze-track > * {
  box-sizing: border-box;
  flex-shrink: 0;
  width: var(--slide-width);
}

.carousel__arrow {
  position: absolute;
  top: 50%;
  z-index: 20;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  color: white;
  opacity: 0;
  background-color: color-mix(in srgb, var(--color-text) 38%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-background) 34%, transparent);
  box-shadow: 0 18px 42px rgba(15, 23, 42, 0.28);
  backdrop-filter: blur(14px);
  transform: translateY(-50%) scale(0.94);
  transition:
    opacity 0.22s ease,
    transform 0.22s ease,
    background-color 0.22s ease,
    border-color 0.22s ease;
}

.carousel__arrow.arrow-cards-slider {
  top: 40%;
}

.carousel__arrow:hover {
  color: white;
  background-color: color-mix(in srgb, var(--color-primary) 84%, transparent);
  border-color: color-mix(in srgb, var(--color-background) 46%, transparent);
  transform: translateY(-50%) scale(1);
}

.blaze-slider:hover .carousel__arrow,
.blaze-slider:focus-within .carousel__arrow {
  opacity: 1;
}

.carousel__arrow:focus-visible,
.carousel__dot:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
}

.carousel__arrow--prev {
  left: 1rem;
  transform: translate(-0.35rem, -50%) scale(0.94);
}

.carousel__arrow--next {
  right: 1rem;
  transform: translate(0.35rem, -50%) scale(0.94);
}

.blaze-slider:hover .carousel__arrow--prev,
.blaze-slider:focus-within .carousel__arrow--prev,
.carousel__arrow--prev:hover {
  transform: translate(0, -50%) scale(1);
}

.blaze-slider:hover .carousel__arrow--next,
.blaze-slider:focus-within .carousel__arrow--next,
.carousel__arrow--next:hover {
  transform: translate(0, -50%) scale(1);
}

.carousel__pagination,
.blaze-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.carousel__pagination {
  position: absolute;
  right: 1rem;
  bottom: 1rem;
  left: 1rem;
  z-index: 20;
}

.carousel__dot,
.blaze-pagination button {
  width: 0.7rem;
  height: 0.7rem;
  padding: 0;
  overflow: hidden;
  font-size: 0;
  color: transparent;
  cursor: pointer;
  background-color: color-mix(in srgb, var(--color-background) 58%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-background) 76%, transparent);
  border-radius: 999px;
}

.carousel__dot.active,
.blaze-pagination button.active {
  width: 1.75rem;
  background-color: var(--color-primary);
  border-color: var(--color-primary);
}

.homepage-hero-carousel,
.homepage-hero-carousel .blaze-container,
.homepage-hero-carousel .blaze-track-container,
.homepage-hero-carousel .blaze-track,
.homepage-hero-carousel .blaze-track > *,
.standard-page-hero-carousel,
.standard-page-hero-carousel .blaze-container,
.standard-page-hero-carousel .blaze-track-container,
.standard-page-hero-carousel .blaze-track,
.standard-page-hero-carousel .blaze-track > * {
  height: 100%;
}

.homepage-hero-carousel__slide,
.standard-page-hero-carousel__slide {
  position: relative;
  flex: 0 0 var(--slide-width);
  width: var(--slide-width);
  min-width: 0;
  height: 100%;
}

/* ──────────────────────────────────────────────────────────────────────────
   category_grid — modern collection cards.

   Full-bleed image with a bottom scrim, the category name overlaid, an optional
   product-count pill, and a CTA that reveals on hover (the collection-card
   pattern used by Shopify / large storefronts). Every rule is scoped with
   [data-module="category_grid"] so it never leaks onto other modules. Colours
   come from the design-system tokens; the overlay ink/scrim are local vars so
   the caption stays legible over any photo in both light and dark themes.
   ────────────────────────────────────────────────────────────────────────── */

[data-module="category_grid"] {
  --cg-ink: #fff;
  /* Bottom scrim behind the caption: derived from the brand primary, darkened
     with black so the overlaid white title keeps enough contrast. Change
     --color-primary and this warms/cools with it automatically. */
  --cg-scrim: color-mix(in srgb, var(--color-primary) 60%, #000 40%);
  --cg-radius: var(--ds-radius-lg, 1rem);

  /* Card drop-shadow: tinted with the brand primary and given a negative spread
     so it stays a soft glow under the card. The negative spread pulls the blur
     in at the sides (thin edges) so it does not read as a hard band against the
     white page; only the bottom shows a gentle primary halo. */
  --cg-shadow: 0 8px 16px -10px color-mix(in srgb, var(--color-primary) 32%, transparent);
  --cg-shadow-hover: 0 16px 30px -12px color-mix(in srgb, var(--color-primary) 42%, transparent);
}

[data-module="category_grid"] .category-grid__grid {
  gap: clamp(0.75rem, calc(0.5rem + 1vw), 1.5rem);
}

/* ── Card ────────────────────────────────────────────────────────────────── */
[data-module="category_grid"] .category-card {
  position: relative;
  border-radius: var(--cg-radius);
  overflow: hidden;
  isolation: isolate;
  background: var(--ds-surface-sunken, #f2f2f2);
  box-shadow: var(--cg-shadow);
  transition:
    transform var(--ds-duration-base, 240ms) var(--ds-ease, ease),
    box-shadow var(--ds-duration-base, 240ms) var(--ds-ease, ease);
}

[data-module="category_grid"] .category-card:hover,
[data-module="category_grid"] .category-card:focus-within {
  transform: translateY(-6px);
  box-shadow: var(--cg-shadow-hover);
}

[data-module="category_grid"] .category-card__link {
  display: block;
  position: relative;
  text-decoration: none;
  color: var(--cg-ink);
}

/* Keyboard focus ring on the whole card (link fills it). */
[data-module="category_grid"] .category-card__link:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
  border-radius: var(--cg-radius);
}

/* ── Media ───────────────────────────────────────────────────────────────── */
[data-module="category_grid"] .category-card__media {
  position: relative;
  aspect-ratio: var(--category-card-aspect, 1 / 1);
  overflow: hidden;
}

[data-module="category_grid"] .category-card__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--ds-duration-slow, 400ms) var(--ds-ease, ease);
}

[data-module="category_grid"] .category-card:hover .category-card__image {
  transform: scale(1.06);
}

[data-module="category_grid"] .category-card__image--placeholder {
  object-fit: contain;
  background: var(--ds-surface-sunken, #f2f2f2);
}

/* Bottom scrim: keeps the overlaid caption legible over any image, and darkens
   further on hover so the revealed CTA reads cleanly. */
[data-module="category_grid"] .category-card__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, var(--cg-scrim) 0%, transparent 55%);
  opacity: 0.9;
  transition: opacity var(--ds-duration-base, 240ms) var(--ds-ease, ease);
  pointer-events: none;
}

[data-module="category_grid"] .category-card:hover .category-card__scrim {
  opacity: 1;
}

/* ── Count pill (glassy, top-right) ──────────────────────────────────────── */
[data-module="category_grid"] .category-card__count {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 2;
  padding: 0.25rem 0.625rem;
  font-size: var(--ds-text-xs, 0.75rem);
  font-weight: 600;
  line-height: 1;
  color: var(--cg-ink);
  background: color-mix(in srgb, #000 38%, transparent);
  border: 1px solid color-mix(in srgb, #fff 35%, transparent);
  border-radius: var(--ds-radius-full, 9999px);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* ── Caption (name + CTA, bottom-left) ───────────────────────────────────── */
[data-module="category_grid"] .category-card__caption {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: clamp(0.875rem, 0.5rem + 1.2vw, 1.25rem);
}

[data-module="category_grid"] .category-card__title {
  margin: 0;
  color: var(--cg-ink);
  font-size: var(--ds-text-lg, 1.125rem);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-shadow: 0 1px 3px color-mix(in srgb, #000 45%, transparent);
}

[data-module="category_grid"] .category-card__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--ds-text-sm, 0.8125rem);
  font-weight: 600;
  color: var(--cg-ink);
  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity var(--ds-duration-base, 240ms) var(--ds-ease, ease),
    transform var(--ds-duration-base, 240ms) var(--ds-ease, ease);
}

[data-module="category_grid"] .category-card:hover .category-card__cta,
[data-module="category_grid"] .category-card:focus-within .category-card__cta {
  opacity: 1;
  transform: translateY(0);
}

[data-module="category_grid"] .category-card__cta-icon {
  width: 1rem;
  height: 1rem;
  transition: transform var(--ds-duration-fast, 160ms) var(--ds-ease, ease);
}

[data-module="category_grid"] .category-card:hover .category-card__cta-icon {
  transform: translateX(3px);
}

/* ── Motion / touch fallbacks ────────────────────────────────────────────── */
@media (hover: none) {
  /* No hover on touch: keep the CTA visible so the affordance is not lost. */
  [data-module="category_grid"] .category-card__cta {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-module="category_grid"] .category-card,
  [data-module="category_grid"] .category-card__image,
  [data-module="category_grid"] .category-card__cta,
  [data-module="category_grid"] .category-card__cta-icon,
  [data-module="category_grid"] .category-card__scrim {
    transition: none;
  }
  [data-module="category_grid"] .category-card:hover {
    transform: none;
  }
  [data-module="category_grid"] .category-card:hover .category-card__image {
    transform: none;
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   Inline content embeds — `#product:ID#` in a module body.

   The card is a block interruption of the copy, so it needs its own vertical
   rhythm (fluid clamp(), calibrated at 690px / 1440px like the modules) and a
   width cap: a full-bleed card inside a text column reads as a broken layout.
   ────────────────────────────────────────────────────────────────────────── */

.content-embed {
  margin: clamp(1.5rem, calc(1.1rem + 1.6vw), 3rem) 0;
}

.content-embed--product {
  max-width: 22rem;
}

/* Inside a prose column the card must not inherit the typographic resets the
   surrounding copy uses. */
.content-embed--product .product-card__title {
  margin: 0;
}

/* standard_page - gap system*/

.default-page-stack--flush-first-gap > .main-standard-page {
  margin-top: calc(var(--mobile-gap) * -1);
}

@media (min-width: 768px) {
  .default-page-stack--flush-first-gap > .main-standard-page {
    margin-top: calc(var(--desktop-gap) * -1);
  }
}

@media (min-width: 768px) {
  .standard-page-breadcrumb--without-banner {
    margin-top: 3.3rem;
  }
}

@media (min-width: 1024px) {
  .main-standard-page--with-side-menu .main-standard-page__content > .text-images-base:first-child > div > .grid {
    align-items: flex-start;
  }
}

.standard-page-side-menu__link--active-root {
  box-shadow: inset 3px 0 0 var(--color-primary);
  font-weight: 700;
}

.main-standard-page__content--hidden-page-title > .text-images-base:first-child {
  margin-bottom: calc(var(--mobile-gap) * -1);
}

.main-standard-page__content--hidden-page-title > .text-images-base:first-child .body.prose > :first-child {
  margin-top: 0;
}

@media (min-width: 768px) {
  .main-standard-page__content--hidden-page-title > .text-images-base:first-child {
    margin-bottom: calc(var(--desktop-gap) * -1);
  }
}

.main-divider {
  width: 100%;
  height: clamp(3rem, 6vw, 7rem);
  overflow: hidden;
  line-height: 0;
  pointer-events: none;
}

.main-divider svg {
  display: block;
  width: 100%;
  height: 100%;
}

.main-divider path {
  fill: currentColor;
}

.main-divider--straight_line {
  height: 1px;
}

turbo-frame.gallery-page-frame[src]:not([complete]) {
  display: block;
  grid-column: 1 / -1;
  min-height: 4rem;
}

turbo-frame.gallery-page-frame[complete],
turbo-frame.gallery-page-frame:not([src]) {
  display: contents;
}

turbo-frame.masonry-loader-frame:not([src]),
turbo-frame.masonry-loader-frame[src]:not([complete]) {
  display: block;
  min-height: 4rem;
}

.masonry-grid:not(.masonry-grid-ready) {
  min-height: 16rem;
}

/* ─────────────────────────────────────────────────────────────────────────
   Hero module typography — fully fluid type + spacing system.

   Every gap uses clamp() on the same vw curve as the font sizes, so the
   proportions stay constant whether the hero is inside a 690px iframe
   (Lookbook preview) or a 1440px full-viewport page.

   Calibration reference points:
     narrow  (690px iframe):  title ~47px  → spacings look correct here
     desktop (1440px full):   title  68px  → spacings must scale to match
   ───────────────────────────────────────────────────────────────────────── */

/*
  Font sizes — delivered as CSS custom properties set inline on the <section>
  so they beat Tailwind @layer base without specificity hacks.

  Scale  Mobile → Desktop
  Title  36px  → 68px   (clamp 2.25rem → 4.25rem, slope 3.8vw)
  Sub    20px  → 30px
  Lead   17px  → 19px
*/

[data-module="hero"] .hero-eyebrow {
  margin: 0 0 clamp(0.75rem, calc(0.5rem + 0.7vw), 1.125rem) !important;
  color: var(--section-muted);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

[data-module="hero"] .hero-title {
  margin: 0;
  color: var(--section-text);
  font-weight: 800;
  letter-spacing: -0.03em;
  /* Mobile: slight breathing room for 2-line wraps. Desktop: tight display. */
  line-height: 1.1;
  text-wrap: balance;
  font-size: var(--hero-title-size) !important;
}

@media (min-width: 1024px) {
  [data-module="hero"] .hero-title { line-height: 1.0; }
}

[data-module="hero"] .hero-subtitle {
  /* ~24px at 690px · ~34px at 1440px */
  margin: clamp(1.25rem, calc(0.9rem + 1.35vw), 2.25rem) 0 0 !important;
  color: var(--section-text);
  font-weight: 400;
  line-height: 1.4;
  text-wrap: balance;
  font-size: var(--hero-subtitle-size) !important;
  opacity: 0.85;
}

[data-module="hero"] .hero-lead {
  /* ~18px at 690px · ~26px at 1440px */
  margin: clamp(1rem, calc(0.65rem + 1.1vw), 1.75rem) 0 0 !important;
  max-width: 42rem;
  color: var(--section-muted);
  font-size: var(--hero-lead-size) !important;
  line-height: 1.7;
}

[data-module="hero"] .hero-lead p { margin: 0; }
[data-module="hero"] .hero-lead p + p { margin-top: 0.75em; }

[data-module="hero"] .text-center .hero-lead { margin-left: auto; margin-right: auto; }

[data-module="hero"] .hero-actions {
  /* ~52px at 690px · ~76px at 1440px */
  margin-top: clamp(2.25rem, calc(1.875rem + 3.2vw), 5rem) !important;
  display: flex;
  flex-wrap: wrap;
  gap: 0.875rem;
}
[data-module="hero"] .text-center .hero-actions { justify-content: center; }
[data-module="hero"] .text-right  .hero-actions { justify-content: flex-end; }

/* Background Image Zoom (One time) */
@keyframes hero-zoom {
  from { transform: scale(1); }
  to { transform: scale(1.15); }
}

/* Text Zoom Effect */
@keyframes hero-text-zoom {
  from { transform: scale(0.9); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* Text Fall Effect (Top to Bottom) */
@keyframes hero-text-fall {
  from { transform: translateY(-20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

/* Text Fade Effect (Step by Step) */
@keyframes hero-text-fade {
  from { opacity: 0; transform: translateY(5px); }
  to { opacity: 1; transform: translateY(0); }
}

.hero-anim-item {
  opacity: 0;
  animation-fill-mode: forwards;
  animation-duration: 1s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); /* Smoother standard ease for step-by-step */
}

.hero-anim--fade { animation-name: hero-text-fade; }
.hero-anim--zoom { animation-name: hero-text-zoom; }
.hero-anim--fall { animation-name: hero-text-fall; }

.hero-font-display { font-family: var(--font-display); }
.hero-font-body    { font-family: var(--font-body); }

.hero-slide--zoom { animation: hero-zoom 15s ease-in-out forwards; }

/* ──────────────────────────────────────────────────────────────────────────
   link_columns — closing band of link columns.

   Every rule is scoped with [data-module="link-columns"] so nothing leaks onto
   the site footer partial, which sits below this band and is not ours.
   Spacing is fluid clamp() calibrated at 690px / 1440px, the same method the
   hero and product_showcase modules use (docs/guides/MODULE_CSS_GUIDE.md §3),
   and every colour reads a token so a client palette recolours the band.
   ────────────────────────────────────────────────────────────────────────── */

[data-module="link-columns"] {
  padding-block: clamp(2.5rem, calc(1.6rem + 3.4vw), 5rem);
  border-top: 1px solid var(--ds-hairline, rgba(0, 0, 0, 0.08));
}

[data-module="link-columns"] .link-columns__header {
  margin-bottom: clamp(2rem, calc(1.4rem + 2.4vw), 3.5rem);
  text-align: center;
}

[data-module="link-columns"] .link-columns__title {
  font-size: var(--ds-text-2xl, 1.5rem);
  font-weight: var(--ds-weight-semibold, 600);
  letter-spacing: var(--ds-tracking-tight, -0.011em);
  color: var(--color-text);
}

[data-module="link-columns"] .link-columns__subtitle {
  margin-top: var(--ds-space-3, 0.75rem);
  font-size: var(--ds-text-lg, 1.0625rem);
  color: var(--color-text-muted);
}

[data-module="link-columns"] .link-columns__grid {
  gap: clamp(2rem, calc(1.4rem + 2vw), 3rem);
}

/* ── Brand cell ──────────────────────────────────────────────────────────── */

[data-module="link-columns"] .link-columns__brand-title {
  font-size: var(--ds-text-xl, 1.25rem);
  font-weight: var(--ds-weight-semibold, 600);
  letter-spacing: var(--ds-tracking-tight, -0.011em);
  color: var(--color-text);
}

[data-module="link-columns"] .link-columns__brand-subtitle {
  margin-top: var(--ds-space-3, 0.75rem);
  max-width: 32ch;
  font-size: var(--ds-text-md, 1rem);
  line-height: var(--ds-leading-relaxed, 1.7);
  color: var(--color-text-muted);
}

[data-module="link-columns"] .link-columns__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-3, 0.75rem);
  margin-top: var(--ds-space-6, 1.5rem);
}

/* ── Link columns ────────────────────────────────────────────────────────── */

[data-module="link-columns"] .link-columns__column-title {
  font-size: var(--ds-text-xs, 0.75rem);
  font-weight: var(--ds-weight-semibold, 600);
  letter-spacing: var(--ds-tracking-wide, 0.08em);
  text-transform: uppercase;
  color: var(--color-text);
}

[data-module="link-columns"] .link-columns__links {
  margin-top: var(--ds-space-4, 1rem);
  font-size: var(--ds-text-base, 0.9375rem);
  color: var(--color-text-muted);
}

/* Tailwind preflight already strips the bullets; this only sets the rhythm. */
[data-module="link-columns"] .link-columns__links li + li,
[data-module="link-columns"] .link-columns__links p + p {
  margin-top: var(--ds-space-3, 0.75rem);
}

[data-module="link-columns"] .link-columns__links a {
  color: var(--color-text-muted);
  text-decoration: none;
  transition: color var(--ds-duration-fast, 160ms) var(--ds-ease, ease);
}

[data-module="link-columns"] .link-columns__links a:hover {
  color: var(--color-primary);
}

[data-module="link-columns"] .link-columns__links a:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
  border-radius: var(--ds-radius-xs, 0.375rem);
}

/* ── Fine print ──────────────────────────────────────────────────────────── */

[data-module="link-columns"] .link-columns__fine-print {
  margin-top: clamp(2rem, calc(1.4rem + 2.4vw), 3.5rem);
  padding-top: var(--ds-space-6, 1.5rem);
  border-top: 1px solid var(--ds-hairline, rgba(0, 0, 0, 0.08));
  font-size: var(--ds-text-sm, 0.8125rem);
  color: var(--color-text-muted);
}

[data-module="link-columns"] .link-columns__fine-print a {
  color: var(--color-text-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
}

[data-module="link-columns"] .link-columns__fine-print a:hover {
  color: var(--color-primary);
}

.page-404__title {
  font-family: var(--font-display);
}

picture.optimize-image {
  --optimize-lqip-blur: 12px;
  --optimize-lqip-bleed: calc(var(--optimize-lqip-blur) * 2);

  display: block;
  overflow: hidden;
  position: relative;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  height: 100%;
  width: 100%;
}

picture.optimize-image::before {
  content: "";
  position: absolute;
  /* Bleed outward so blurred transparent edges are clipped outside the image. */
  inset: calc(var(--optimize-lqip-bleed) * -1);
  z-index: 0;
  background-image: var(--optimize-lqip);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  filter: blur(var(--optimize-lqip-blur));
  opacity: 1;
  transition: opacity 0.35s ease-out;
}

picture.optimize-image.unblur::before {
  opacity: 0;
}

picture.optimize-image img {
  display: block;
  width: 100%;
  height: 100%;
  position: relative;
  z-index: 1;
}

picture.optimize-image.blurred img {
  opacity: 0;
  transition: opacity 0.35s ease-out;
}

/* The LCP image must not wait for the lazy Stimulus controller before it can
   paint. Its LQIP remains underneath until the real image pixels are ready. */
picture.optimize-image.optimize-image--priority img {
  opacity: 1;
  transition: none;
}

picture.optimize-image.unblur img {
  opacity: 1;
}

/* Page shell — the single page chrome shared by CMS pages and the storefront.
 *
 * Rendered by themes/default/views/shared/_page_shell.html.erb. It exists so a
 * controller-driven storefront page (catalog, cart, checkout, account) and a
 * CMS page node measure the same, break at the same gutters and put the same
 * amount of air between the breadcrumb bar, the H1 and the first block.
 *
 * Vertical rhythm is NOT declared here. The shell wrapper carries the theme's
 * page-stack class, so breadcrumb -> body is exactly --mobile-gap /
 * --desktop-gap, the same gap a CMS page leaves between its breadcrumb bar and
 * its first module. Change the theme's vertical spacing and both paths move
 * together, which is the whole point.
 *
 * Container: max-width and gutters mirror the CMS body (Tailwind `container
 * mx-auto px-6 lg:px-8`, which is also what _breadcrumb_bar uses), so the H1
 * and the trail above it share a left edge on every page.
 *
 * Type: the H1 mirrors the CMS page title (`text-3xl md:text-4xl font-bold
 * leading-tight`) to the pixel. It is written in plain CSS rather than utility
 * classes because the storefront views are not the only caller and the value
 * must not drift.
 */

.page-shell {
  width: 100%;
}

.page-shell__body {
  width: 100%;
  margin-inline: auto;
  /* Matches the CMS container gutters: px-6, lg:px-8. */
  padding-inline: 1.5rem;
  /* The page has to clear the footer; a CMS page gets that from its own last
     module, a controller page has nothing below it. Fluid, so it grows with the
     viewport instead of stranding a short page just above the fold. */
  padding-bottom: var(--ds-space-24);
  min-width: 0;
}

@media (min-width: 1024px) {
  .page-shell__body {
    padding-inline: 2rem;
  }
}

/* Measure. The chrome is identical in all three; only how wide the text and the
   controls are allowed to run changes, because a login form and a product grid
   do not want the same line length.

   --wide declares no max-width on purpose. The partial puts the Tailwind
   `container` utility on the same element, which is exactly what a CMS page
   body and the breadcrumb bar use, and which STEPS with the viewport
   (80rem at 1440px, 96rem at 1600px). Pinning it to a single value here is what
   made the storefront 1280px wide next to a 1536px CMS page on a wide monitor. */

.page-shell__body--narrow {
  max-width: calc(var(--ds-container-narrow) + 4rem);
}

.page-shell__body--form {
  max-width: 32rem;
}

/* ── Page header ─────────────────────────────────────────────────────────── */

.page-shell__header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--ds-space-5);
  margin-bottom: var(--ds-space-10);
}

/* Header rendered inside a page column that already owns the vertical rhythm
   (the account pages stack it with space-y-10), so it drops its own margin. */
.page-shell__header--flush {
  margin-bottom: 0;
}

.page-shell__heading {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
  min-width: 0;
}

/* Same computed type as a CMS page title: text-3xl (1.875rem), md:text-4xl
   (2.25rem), font-bold, leading-tight. */
.page-shell__title {
  font-size: 1.875rem;
  font-weight: var(--ds-weight-bold);
  line-height: 1.25;
  letter-spacing: var(--ds-tracking-normal);
  color: var(--color-text);
  text-wrap: balance;
  margin: 0;
}

@media (min-width: 768px) {
  .page-shell__title {
    font-size: 2.25rem;
  }
}

.page-shell__subtitle {
  font-size: var(--ds-text-base);
  color: var(--color-text-muted);
  margin: 0;
}

.page-shell__header-aside {
  flex: none;
}

/* ──────────────────────────────────────────────────────────────────────────
   product_showcase — landing layouts (spotlight / editorial).

   Every rule is scoped with [data-module="product_showcase"] so it cannot leak
   onto the catalogue grid, which reuses the same product-card classes.
   Spacing is fluid clamp() calibrated at 690px / 1440px, the same method the
   hero module uses (docs/guides/MODULE_CSS_GUIDE.md §3), so the blocks keep
   their proportions between the Lookbook iframe and a full desktop viewport.
   ────────────────────────────────────────────────────────────────────────── */

[data-module="product_showcase"] .product-feature {
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  gap: clamp(1.5rem, calc(1rem + 2vw), 3.5rem);
}

@media (min-width: 1024px) {
  [data-module="product_showcase"] .product-feature {
    grid-template-columns: 1fr 1fr;
  }

  /* Zig-zag: the copy takes the first column and the image the second, without
     reordering the DOM (the image stays first for screen readers and for the
     LCP preload of the opening block). */
  [data-module="product_showcase"] .product-feature--reversed .product-feature__media {
    order: 2;
  }
}

/* The aspect ratio lives on the container (the editor's image_aspect_ratio, via
   the --product-card-aspect var set on the section), not on the <img>:
   optimize_image_tag wraps the image in picture.optimize-image, which is sized
   at 100% height by the shared stylesheet. */
[data-module="product_showcase"] .product-feature__media {
  position: relative;
  aspect-ratio: var(--product-card-aspect, 1 / 1);
  overflow: hidden;
  border-radius: var(--ds-radius-lg, 0.75rem);
  background: var(--ds-surface-sunken, #f2f2f2);
}

[data-module="product_showcase"] .product-feature__link {
  display: block;
  width: 100%;
  height: 100%;
}

[data-module="product_showcase"] .product-feature__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}

[data-module="product_showcase"] .product-feature:hover .product-feature__image {
  transform: scale(1.03);
}

[data-module="product_showcase"] .product-feature__image--placeholder {
  display: grid;
  place-items: center;
  background: var(--ds-surface-sunken, #f2f2f2);
  color: var(--color-text-muted, #6b7280);
}

[data-module="product_showcase"] .product-feature__brand {
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted, #6b7280);
}

[data-module="product_showcase"] .product-feature__title {
  /* ~28px at 690px · ~40px at 1440px */
  font-size: clamp(1.5rem, calc(1.1rem + 1.6vw), 2.5rem) !important;
  line-height: 1.15;
  font-weight: 600;
  margin: clamp(0.5rem, calc(0.35rem + 0.5vw), 1rem) 0 0 !important;
}

[data-module="product_showcase"] .product-feature__description {
  color: var(--color-text-muted, #6b7280);
  line-height: 1.6;
  margin: clamp(0.75rem, calc(0.55rem + 0.7vw), 1.25rem) 0 0 !important;
}

[data-module="product_showcase"] .product-feature__price {
  font-size: clamp(1.125rem, calc(1rem + 0.5vw), 1.5rem);
  font-weight: 600;
  margin: clamp(1rem, calc(0.75rem + 0.9vw), 1.75rem) 0 0 !important;
}

[data-module="product_showcase"] .product-feature__stock {
  font-size: 0.875rem;
  color: var(--color-text-muted, #6b7280);
  margin: 0.5rem 0 0 !important;
}

/* Spotlight with the full buy form: the add_to_cart block replaces the CTA
   link and keeps its rhythm. */
[data-module="product_showcase"] .product-feature__buy {
  margin-top: clamp(1.25rem, calc(1rem + 1.2vw), 2.5rem);
}

[data-module="product_showcase"] .product-feature__cta {
  margin-top: clamp(1.25rem, calc(1rem + 1.2vw), 2.5rem) !important;
  display: inline-flex;
  width: auto;
}

/* Spotlight: the secondary card grid sits under the featured block. */
[data-module="product_showcase"] .product-showcase__grid--secondary {
  margin-top: clamp(2rem, calc(1.5rem + 2.4vw), 4.5rem);
}

/* Editorial: one full-width block per product, separated by the same fluid
   rhythm the spotlight uses between its two halves. */
[data-module="product_showcase"] .product-showcase__editorial {
  display: grid;
  gap: clamp(2.5rem, calc(1.75rem + 3.2vw), 6rem);
}

@media (prefers-reduced-motion: reduce) {
  [data-module="product_showcase"] .product-feature__image,
  [data-module="product_showcase"] .product-feature:hover .product-feature__image {
    transition: none;
    transform: none;
  }
}

/*
 * Scroll Reveal — animation library for the scroll-reveal Stimulus controller.
 *
 * All animation variants are defined here. The controller applies one class
 * (e.g. "reveal-fade-up") to the <section> and adds "in" when it enters
 * the viewport. To add a new variant, define its hidden state and let the
 * shared .in rule handle the revealed state.
 */

/* ── Shared base state (opacity + transition for all variants) ─────────── */
.reveal-fade-up,
.reveal-fade-down,
.reveal-fade-in,
.reveal-slide-left,
.reveal-slide-right,
.reveal-zoom-in {
  opacity: 0;
  transition:
    opacity 0.65s cubic-bezier(0.4, 0, 0.2, 1),
    transform 0.65s cubic-bezier(0.4, 0, 0.2, 1);
}

/* ── Revealed state — same for all variants ────────────────────────────── */
.reveal-fade-up.in,
.reveal-fade-down.in,
.reveal-fade-in.in,
.reveal-slide-left.in,
.reveal-slide-right.in,
.reveal-zoom-in.in {
  opacity: 1;
  transform: none;
}

/* ── Individual hidden transforms ──────────────────────────────────────── */

/* Slides up from below */
.reveal-fade-up {
  transform: translateY(32px);
}

/* Slides down from above */
.reveal-fade-down {
  transform: translateY(-32px);
}

/* Opacity only — no movement */
.reveal-fade-in {
  transform: none;
}

/* Slides in from the left */
.reveal-slide-left {
  transform: translateX(-48px);
}

/* Slides in from the right */
.reveal-slide-right {
  transform: translateX(48px);
}

/* Scales up from slightly smaller */
.reveal-zoom-in {
  transform: scale(0.94);
}

/* ── Respect prefers-reduced-motion ────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .reveal-fade-up,
  .reveal-fade-down,
  .reveal-fade-in,
  .reveal-slide-left,
  .reveal-slide-right,
  .reveal-zoom-in {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Skip link — "skip to content", the first tab stop on every page.
 *
 * Rendered by themes/default/views/layouts/_skip_link.html.erb.
 *
 * It must stay FOCUSABLE while invisible, so it is moved off-screen with a
 * clip-rect (the standard visually-hidden recipe) instead of display:none or
 * visibility:hidden, both of which remove it from the tab order entirely and
 * would make the link unreachable.
 *
 * On :focus-visible it becomes a real, opaque button pinned to the top-left,
 * above every sticky header (z-index above the main menu's own stacking).
 *
 * Colours come from the theme custom properties, so a client theme restyles it
 * for free.
 */

.skip-link {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link:focus,
.skip-link:focus-visible {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 9999;
  width: auto;
  height: auto;
  margin: 0;
  padding: 0.75rem 1.25rem;
  overflow: visible;
  clip: auto;
  clip-path: none;
  border-radius: 0.5rem;
  background: var(--color-primary, #1f2937);
  color: var(--color-on-primary, #ffffff);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  outline: 3px solid var(--color-text, #111827);
  outline-offset: 2px;
}

/* The main landmark is a programmatic focus target (tabindex="-1"); never draw
 * a focus ring around the whole page when the skip link sends focus there. */
#main-content:focus {
  outline: none;
}

/* ── Sticky buy bar ──────────────────────────────────────────────────────────
 * Fixed bottom bar the sticky-buy-bar controller slides in once the main buy
 * area has scrolled out of view. Hidden by default via transform + visibility
 * (visibility keeps the off-screen bar out of the focus order and the
 * accessibility tree, and still transitions cleanly). Sits UNDER the mini-cart
 * drawer (z-index 60): a successful add opens the drawer over the bar. */

.sticky-buy-bar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 40;
  background: var(--color-background);
  border-top: 1px solid var(--ds-hairline-strong);
  box-shadow: var(--ds-shadow-lg);
  /* Safe-area aware: on notched phones the bar padding grows past the home
   * indicator instead of hiding the CTA behind it. */
  padding: var(--ds-space-3)
           clamp(var(--ds-space-3), calc(0.5rem + 1.5vw), var(--ds-space-6))
           calc(var(--ds-space-3) + env(safe-area-inset-bottom, 0px));
  transform: translateY(110%);
  visibility: hidden;
  transition:
    transform var(--ds-duration-base) var(--ds-ease),
    visibility var(--ds-duration-base) var(--ds-ease);
}

.sticky-buy-bar--visible {
  transform: none;
  visibility: visible;
}

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

.sticky-buy-bar__inner {
  display: flex;
  align-items: center;
  gap: clamp(var(--ds-space-3), calc(0.5rem + 1vw), var(--ds-space-5));
  max-width: 72rem;
  margin-inline: auto;
}

.sticky-buy-bar__media {
  flex-shrink: 0;
}

.sticky-buy-bar__image,
.sticky-buy-bar__media .optimize-image {
  display: block;
  width: 3rem;
  height: 3rem;
}

.sticky-buy-bar__image {
  object-fit: cover;
  border-radius: var(--ds-radius-sm);
  border: 1px solid var(--ds-hairline);
}

.sticky-buy-bar__image--placeholder {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--ds-tint-primary);
  color: var(--color-text-muted);
}

/* The very narrow screens drop the thumb: title + price + CTA is the payload. */
@media (max-width: 22rem) {
  .sticky-buy-bar__media {
    display: none;
  }
}

.sticky-buy-bar__info {
  flex: 1;
  min-width: 0;
}

.sticky-buy-bar__title {
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sticky-buy-bar__price .price {
  display: inline-flex;
  align-items: baseline;
  gap: var(--ds-space-2);
  font-size: var(--ds-text-sm);
}

/* The bar CTA reuses .add-to-cart__submit--primary so it IS the store's primary
 * button; only the geometry shrinks to bar scale (the form's own buttons are
 * full-width). */
.sticky-buy-bar__cta.add-to-cart__submit {
  width: auto;
  flex-shrink: 0;
  min-height: 2.75rem;
  padding-inline: clamp(var(--ds-space-4), calc(0.75rem + 1vw), var(--ds-space-6));
}

/* Storefront components — catalog and product detail.
 *
 * Built on the tokens in _design_system.css: every radius, duration, shadow and
 * hairline here is a --ds-* var, never a literal. Colour comes only from the
 * theme's --color-* vars, so a client palette flows through untouched.
 *
 * What changed from the first pass, and why:
 *  - Borders were `1px solid var(--color-secondary)`. The default secondary is
 *    #0f172a — near black. Every card, input and stepper was outlined in a hard
 *    dark line, which is the single loudest reason the storefront read as a
 *    wireframe. They are hairlines now, derived from the text colour.
 *  - Cards were a border with content inside. They are surfaces now: hairline +
 *    a two-layer shadow that deepens on hover, with the lift and the image scale
 *    on different durations so the card arrives before the photo settles.
 *  - Type was three sizes and one weight. It is a scale now, with tracking that
 *    tightens as size grows.
 *
 * House rules, unchanged:
 *  - Touch targets >= 44px, including the quantity steppers.
 *  - Focus is always visible, drawn with --ds-focus-ring so it follows the
 *    corner radius instead of boxing a rounded control in a rectangle.
 *  - Animate transform/opacity/shadow only. Never width, height, or top.
 *  - Body text >= 4.5:1. Muted text is for secondary information, never prose.
 *  - Prices are tabular-nums, so a column of them does not jitter.
 */

/* ── Product listing ─────────────────────────────────────────────────────── */

/* Type scale for the whole listing surface — sidebar, grid and card. It had
   drifted to eight sizes, several of them a single pixel apart (--ds-text-base
   0.9375 beside --ds-text-md 1rem, --ds-text-2xs 0.6875 beside --ds-text-xs
   0.75), which reads as sloppiness rather than hierarchy. Four steps now, and
   nothing on this page may introduce a fifth:
     --ds-text-xs (0.75)   micro: eyebrow brand, facet group labels, facet
                           counts, stock line, badges
     --ds-text-sm (0.8125) interface text: facet rows, clear-all, result count,
                           card CTA, pagination, struck price
     --ds-text-md (1rem)   titles and inputs: card title, "Filters", every form
                           control (also the iOS no-zoom floor)
     --ds-text-xl          the price, the one figure meant to carry
   The H1 stays with the shared page shell, which owns it for every page. */

/* The catalog no longer declares a container, a header layout or a title type
   of its own: it renders inside the shared page shell (page_shell.css), which
   owns the width, the gutters, the breadcrumb -> title rhythm and the H1 scale
   for every page, CMS or storefront. What is left here is genuinely catalog
   specific: the sort control and the sidebar/results grid. */

/* The sort control: a native <select> that does not look native. The chevron is
   ours (background-image), the platform arrow is removed, and the padding-right
   reserves room for it. */
.product-listing__sort {
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
  flex: none;
}

.product-listing__sort-label {
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-medium);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.product-listing__sort select {
  appearance: none;
  min-height: 2.75rem;
  padding: 0 2.5rem 0 var(--ds-space-4);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-sm);
  background-color: var(--color-background);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2364748b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--ds-space-3) center;
  background-size: 1rem 1rem;
  color: var(--color-text);
  font-size: var(--ds-text-md);
  font-weight: var(--ds-weight-medium);
  font-family: inherit;
  cursor: pointer;
  box-shadow: var(--ds-shadow-xs);
  transition:
    border-color var(--ds-duration-fast) var(--ds-ease),
    box-shadow var(--ds-duration-fast) var(--ds-ease);
}

.product-listing__sort select:hover {
  border-color: color-mix(in srgb, var(--color-primary) 40%, var(--ds-hairline-strong));
}

.product-listing__layout {
  display: grid;
  gap: var(--ds-space-10);
  align-items: start;
}

/* The sidebar column is wider than a plain list of dropdowns needed: it now
   carries the whole category tree and every brand as visible rows, and a facet
   name that wraps onto three lines reads as noise. The results column keeps the
   remaining width, so the grid still reflows to its own auto-fill floor. */
@media (min-width: 1024px) {
  .product-listing__layout {
    grid-template-columns: 20rem minmax(0, 1fr);
    gap: var(--ds-space-10);
  }
}

@media (min-width: 1440px) {
  .product-listing__layout {
    grid-template-columns: 22rem minmax(0, 1fr);
  }
}

.product-listing__results {
  min-width: 0;
}

/* Below the sidebar breakpoint the two columns stack, and the sidebar comes
   first in the DOM — so the whole filter panel filled the first screen and the
   products a customer came for started below the fold. Visual order only: the
   source order still puts filters before results, which is what a screen reader
   and the tab sequence should follow. */
@media (max-width: 1023px) {
  .product-listing__results {
    order: -1;
  }
}

/* ── Filter sidebar ──────────────────────────────────────────────────────── */

/* Deliberately NOT sticky. The panel lists every category and every brand in
   full — no inner scroll area — so on a real catalogue it is taller than the
   viewport, and pinning it would park the bottom facets permanently off-screen.
   It scrolls with the page instead. */
.filter-sidebar {
  min-width: 0;
}

/* Vertical rhythm is owned by the groups, not by a flex `gap` on the form. The
   groups are a mix of <div> and <fieldset>, and a rendered <legend> is NOT a
   flex item — it sits outside the fieldset's anonymous content box — so a gap
   reached the label in the div groups and not in the fieldset ones, leaving
   CATEGORIA sitting further from its first option than CERCA from its input.
   With gap: 0 every distance below is stated once, in margins that every
   element honours. */
.filter-sidebar__form {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: var(--ds-space-5);
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-lg);
  background: var(--ds-gradient-surface);
  box-shadow: var(--ds-shadow-xs);
}

/* Panel header: what this column is, and the one control that empties it. */
.filter-sidebar__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ds-space-3);
  /* Same distance either side of the rule as the group dividers below. */
  padding-bottom: var(--ds-space-4);
  margin-bottom: var(--ds-space-4);
  border-bottom: 1px solid var(--ds-hairline);
}

.filter-sidebar__title {
  margin: 0;
  font-size: var(--ds-text-md);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-tight);
  color: var(--color-text);
}

.filter-sidebar__clear {
  border: 0;
  padding: 0;
  background: none;
  color: var(--color-primary);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-medium);
  font-family: inherit;
  cursor: pointer;
  border-radius: var(--ds-radius-xs);
  transition: opacity var(--ds-duration-fast) var(--ds-ease);
}

.filter-sidebar__clear:hover {
  opacity: 0.7;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* display: block, not flex — a <legend> cannot be a flex item, so any layout
   that depends on the group being a flex container splits the two kinds of
   group apart again. */
.filter-sidebar__group {
  display: block;
  min-width: 0;
  border: 0;
  padding: 0;
  margin: 0;
}

/* A hairline between facet blocks: with lists instead of four dropdowns the
   panel is long, and space alone stops telling one facet from the next. Equal
   margin above and padding below, so the rule sits centred in the gap. */
.filter-sidebar__group + .filter-sidebar__group {
  margin-top: var(--ds-space-4);
  padding-top: var(--ds-space-4);
  border-top: 1px solid var(--ds-hairline);
}

/* Both bounds full width, stacked: a half-width number input truncates its own
   value the moment a price runs past four digits. */
.filter-sidebar__group--price .filter-sidebar__field + .filter-sidebar__field {
  margin-top: var(--ds-space-2);
}

/* A measurement range reads as one control, so its two bounds sit side by side
   — unlike the price, whose values are long enough to need the full width.
   Millimetres are three digits. [product_typologies] */
.filter-sidebar__range {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--ds-space-2);
}

/* Custom-field facet groups [product_typologies]. Class names shared with the
   full-list facet rows (_filter_option) so the two stylings converge on merge. */
.filter-sidebar__options {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-1);
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 14rem;
  overflow-y: auto;
}

.filter-sidebar__option {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  font-size: var(--ds-text-sm);
  color: var(--color-text);
  cursor: pointer;
}

.filter-sidebar__checkbox {
  accent-color: var(--color-primary);
}

.filter-sidebar__option-name {
  min-width: 0;
}

.filter-sidebar__count {
  margin-left: auto;
  color: var(--color-text-muted);
  font-size: var(--ds-text-xs);
}

/* Print picker + the artwork laid over the product photo [printed_products] */
.print-picker {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
  margin-block: var(--ds-space-4);
}

.print-picker__label {
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.print-picker__options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-2);
}

.print-picker__option input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.print-picker__chip {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  padding: var(--ds-space-1) var(--ds-space-2);
  border: 1px solid var(--color-secondary);
  border-radius: var(--ds-radius-md);
  cursor: pointer;
  font-size: var(--ds-text-sm);
  background: var(--color-background);
}

.print-picker__option input:checked + .print-picker__chip {
  border-color: var(--color-primary);
  box-shadow: inset 0 0 0 1px var(--color-primary);
}

.print-picker__option input:focus-visible + .print-picker__chip {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

.print-picker__thumb {
  width: 2rem;
  height: 2rem;
  object-fit: contain;
  display: grid;
  place-items: center;
}

.print-picker__hint {
  margin: 0;
  font-size: var(--ds-text-xs);
  color: var(--color-text-muted);
}

/* Square artwork over a square-ish photo: position and scale are a share of the
   photo, so the preview holds at every breakpoint without a canvas. */
.print-overlay {
  position: absolute;
  top: 30%;
  left: 50%;
  width: 34%;
  transform: translateX(-50%);
  pointer-events: none;
}

.filter-sidebar__more {
  margin: var(--ds-space-1) 0 0;
  color: var(--color-text-muted);
  font-size: var(--ds-text-xs);
  font-style: italic;
}

/* The one distance from any group label to the control under it — <label> in
   the div groups, <legend> in the fieldsets, same number either way. */
.filter-sidebar__label {
  display: block;
  margin-bottom: var(--ds-space-2);
  /* Zeroes the browser's default legend padding-inline too, so a legend and a
     label start on the same left edge. */
  padding: 0;
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Search field with an inset icon: the glyph is decorative, so it never takes
   the pointer, and the field reserves its lane with padding. */
.filter-sidebar__search {
  position: relative;
  min-width: 0;
}

.filter-sidebar__search-icon {
  position: absolute;
  inset-inline-start: var(--ds-space-3);
  top: 50%;
  width: 1.05rem;
  height: 1.05rem;
  transform: translateY(-50%);
  color: var(--color-text-muted);
  pointer-events: none;
}

/* ── Facet lists (category, brand) ───────────────────────────────────────── */

/* Every option renders: no max-height, no inner scrollbar. A customer sees the
   whole catalogue shape at once instead of discovering it by scrolling a 200px
   box inside a page that also scrolls. */
.filter-sidebar__list {
  display: flex;
  flex-direction: column;
  /* No gap. The rows are their own rhythm — 2.25rem each — and a stray pixel
     between them was neither a separation anyone reads nor part of the scale. */
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: none;
  overflow: visible;
}

.filter-sidebar__item {
  min-width: 0;
}

.filter-sidebar__option {
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
  /* 2.25rem rows keep the whole tree on one screen where it fits; the label is
     the hit area, so the target is the full row, not the 16px radio. */
  min-height: 2.25rem;
  padding: var(--ds-space-1) var(--ds-space-3);
  /* Child categories step in under their parent; --facet-level comes from the
     partial, which computes depth in memory. */
  padding-inline-start: calc(var(--ds-space-3) + var(--facet-level, 0) * var(--ds-space-4));
  border-radius: var(--ds-radius-sm);
  color: var(--color-text);
  font-size: var(--ds-text-sm);
  cursor: pointer;
  transition:
    background-color var(--ds-duration-fast) var(--ds-ease),
    color var(--ds-duration-fast) var(--ds-ease);
}

.filter-sidebar__option:hover {
  background: var(--ds-tint-hover);
}

.filter-sidebar__option:has(.filter-sidebar__radio:checked) {
  background: var(--ds-tint-primary);
  color: var(--color-primary);
  font-weight: var(--ds-weight-semibold);
}

.filter-sidebar__radio {
  flex: none;
  width: 0.95rem;
  height: 0.95rem;
  margin: 0;
  accent-color: var(--color-primary);
  cursor: pointer;
}

.filter-sidebar__option-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}

.filter-sidebar__count {
  flex: none;
  color: var(--color-text-muted);
  font-size: var(--ds-text-xs);
  font-variant-numeric: tabular-nums;
}

/* A facet the current search/price scope leaves empty stays selectable — it is
   still a valid place to go — but it stops competing for attention. */
.filter-sidebar__option--empty .filter-sidebar__option-name,
.filter-sidebar__option--empty .filter-sidebar__count {
  opacity: 0.5;
}

.filter-sidebar__field {
  width: 100%;
  min-height: 2.75rem;
  padding: var(--ds-space-2) var(--ds-space-3);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-sm);
  background: var(--color-background);
  color: var(--color-text);
  /* --ds-text-md is 1rem, which is also the floor iOS needs: anything smaller
     and it zooms the page on focus. The token, not a literal, so the field sits
     on the same step as the card titles instead of beside the scale. */
  font-size: var(--ds-text-md);
  font-family: inherit;
  text-align: left;
  transition:
    border-color var(--ds-duration-fast) var(--ds-ease),
    box-shadow var(--ds-duration-fast) var(--ds-ease);
}

/* Must sit AFTER .filter-sidebar__field: that rule sets `padding` as a
   shorthand, so at equal specificity it resets the inline-start padding this
   one adds, and the value typed into the search field ran under the icon. */
.filter-sidebar__field--search {
  padding-inline-start: 2.4rem;
}

.filter-sidebar__field::placeholder {
  color: color-mix(in srgb, var(--color-text-muted) 70%, transparent);
}

.filter-sidebar__field:hover {
  border-color: color-mix(in srgb, var(--color-primary) 40%, var(--ds-hairline-strong));
}

.filter-sidebar__field:focus,
.filter-sidebar__field:focus-visible {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--ds-tint-primary-strong);
}

.filter-sidebar__submit {
  min-height: 2.75rem;
  padding-inline: var(--ds-space-4);
  border: 0;
  border-radius: var(--button-radius, var(--ds-radius-sm));
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  cursor: pointer;
}

/* ── Product grid ────────────────────────────────────────────────────────── */

/* Result count: a quiet line above the results, aligned with the first card. It
   is not a page subtitle — it belongs to the grid, and it is replaced together
   with it on every filter change. */
.product-grid__count {
  margin-bottom: var(--ds-space-4);
  color: var(--color-text-muted);
  font-size: var(--ds-text-sm);
  font-variant-numeric: tabular-nums;
}

.product-grid__items {
  display: grid;
  /* auto-fill with a floor: the admin column count is the *maximum*, and the
     grid reflows below it on narrow viewports without a single media query.
     One rule replaces the four --cols-N breakpoint blocks. */
  grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
  gap: var(--ds-space-6) var(--ds-space-5);
}

@media (min-width: 640px) {
  .product-grid__items--cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 768px) {
  .product-grid__items--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 1280px) {
  .product-grid__items--cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .product-grid__items--cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .product-grid__items--cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

.product-grid__empty {
  padding: var(--ds-space-16) var(--ds-space-6);
  border: 1px dashed var(--ds-hairline-strong);
  border-radius: var(--ds-radius-lg);
  text-align: center;
  color: var(--color-text-muted);
  font-size: var(--ds-text-md);
}

.product-grid__pagination {
  display: flex;
  justify-content: center;
  margin-top: var(--ds-space-12);
  padding-top: var(--ds-space-8);
  border-top: 1px solid var(--ds-hairline);
}

.product-grid__pagination .pagy,
.product-grid__pagination nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-1);
}

.product-grid__pagination a,
.product-grid__pagination span {
  display: inline-grid;
  place-items: center;
  min-width: 2.5rem;
  min-height: 2.5rem;
  padding-inline: var(--ds-space-2);
  border-radius: var(--ds-radius-sm);
  color: var(--color-text-muted);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-medium);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  transition:
    background-color var(--ds-duration-fast) var(--ds-ease),
    color var(--ds-duration-fast) var(--ds-ease);
}

.product-grid__pagination a:hover {
  background: var(--ds-tint-hover);
  color: var(--color-text);
}

.product-grid__pagination .current,
.product-grid__pagination [aria-current="page"] {
  background: var(--color-primary);
  color: var(--color-primary-contrast);
}

.product-grid__pagination .gap {
  min-width: 1.5rem;
}

/* ── Product card ────────────────────────────────────────────────────────── */

.product-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-lg);
  background: var(--color-background);
  box-shadow: var(--ds-shadow-xs);
  /* Clips the image scale to the rounded corner without `overflow: hidden` on
     the card itself, which would also clip the focus ring and the badges. */
  isolation: isolate;
  transition:
    transform var(--ds-duration-base) var(--ds-ease),
    box-shadow var(--ds-duration-base) var(--ds-ease),
    border-color var(--ds-duration-base) var(--ds-ease);
}

.product-card:hover {
  transform: translateY(-3px);
  border-color: var(--ds-hairline-strong);
  box-shadow: var(--ds-shadow-md);
}

/* The whole card is the click target, via a stretched pseudo-element on the
   title link — so a click on the padding still opens the product, while the
   accessible name stays on the real <a> and text inside remains selectable. */
.product-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  z-index: 1;
}

/* Focus lands on the invisible stretched link; draw the ring on the card. */
.product-card:has(.product-card__title a:focus-visible) {
  box-shadow: var(--ds-focus-ring), var(--ds-shadow-md);
}

.product-card__media {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: calc(var(--ds-radius-lg) - 1px) calc(var(--ds-radius-lg) - 1px) 0 0;
  background: var(--ds-surface-sunken);
  overflow: hidden;
}

.product-card__link {
  display: block;
  width: 100%;
  height: 100%;
}

/* `contain`, not `cover`. The box stays 4:5 so the grid keeps one rhythm, but
   what goes in it is fitted rather than filled: a supplier's landscape shot of a
   lock was being blown up until only its middle was left, and a card that crops
   the product out of the photo is not a smaller picture of the product, it is a
   different one. Garment photography is shot 4:5, so for every card that looked
   right before this changes nothing — contain and cover agree when the ratios
   agree. A wider or taller shot now sits inside the sunken background instead of
   being cut to fit it. */
.product-card__image {
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* Slower than the card lift, so the photo settles *after* the card arrives.
     Matching durations makes the whole thing move as one flat plane. */
  transition: transform var(--ds-duration-slower) var(--ds-ease);
}

.product-card:hover .product-card__image {
  transform: scale(1.045);
}

.product-card__image--placeholder {
  display: grid;
  place-items: center;
  background: var(--ds-surface-sunken);
  color: color-mix(in srgb, var(--color-text-muted) 45%, transparent);
}

.product-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
  padding: var(--ds-space-4) var(--ds-space-4) var(--ds-space-5);
  flex: 1;
}

.product-card__brand {
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.product-card__title {
  font-size: var(--ds-text-md);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-snug);
  letter-spacing: var(--ds-tracking-tight);
  color: var(--color-text);
  margin: 0;
}

.product-card__title a {
  color: inherit;
  text-decoration: none;
  transition: color var(--ds-duration-fast) var(--ds-ease);
}

.product-card:hover .product-card__title a {
  color: var(--color-primary);
}

.product-card__price {
  margin-top: auto;
  padding-top: var(--ds-space-1);
}

/* Availability line: dot + label, printed on every card so the grid reads as a
   column of comparable states. The label carries the meaning on its own — the
   colour only reinforces it, which keeps the signal for a colour-blind buyer
   and in a printed page. */
.product-card__stock {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-medium);
}

.product-card__stock-dot {
  flex: none;
  width: 0.4rem;
  height: 0.4rem;
  border-radius: var(--ds-radius-full);
  background: currentColor;
}

.product-card__stock--in-stock {
  color: var(--color-success);
}

/* The raw warning amber (#f59e0b by default) is ~2:1 on a white surface — fine
   as a badge fill behind dark ink, too weak as 12px text. Mixed toward the text
   colour it keeps reading as amber and clears AA. */
.product-card__stock--preorderable {
  color: color-mix(in srgb, var(--color-warning) 70%, var(--color-text));
}

.product-card__stock--out-of-stock {
  color: var(--color-danger);
}

.product-card__actions {
  position: relative;
  /* Above the stretched title link, or the button would never receive the click. */
  z-index: 2;
  margin-top: var(--ds-space-3);
}

.product-card__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-2);
  width: 100%;
  min-height: 2.75rem;
  padding-inline: var(--ds-space-4);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--button-radius, var(--ds-radius-sm));
  background: var(--color-background);
  color: var(--color-text);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  text-decoration: none;
  transition:
    background-color var(--ds-duration-fast) var(--ds-ease),
    border-color var(--ds-duration-fast) var(--ds-ease),
    color var(--ds-duration-fast) var(--ds-ease);
}

.product-card__cta:hover {
  border-color: var(--color-primary);
  background: var(--ds-tint-primary);
  color: var(--color-primary);
}

.product-card__cta .hi {
  transition: transform var(--ds-duration-base) var(--ds-ease);
}

.product-card__cta:hover .hi {
  transform: translateX(2px);
}

/* ── Badges ──────────────────────────────────────────────────────────────── */

.product-card__badges {
  position: absolute;
  inset-block-start: var(--ds-space-3);
  inset-inline-start: var(--ds-space-3);
  /* Stop short of the wishlist heart in the opposite corner, and wrap rather
     than run off the card: "Disponibile in preordine" is longer than the card is
     wide, and it was being clipped mid-word by the media box. */
  inset-inline-end: 3.5rem;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ds-space-2);
  pointer-events: none;
}

.product-card__badges .product-card__badge {
  max-width: 100%;
  text-align: start;
  white-space: normal;
}

/* Not colour alone: a discount reads as a badge with a word in it, so it still
   says "sale" to someone who cannot see the red. */
.price__badge,
.product-card__badge {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  padding: 0.3rem 0.6rem;
  border-radius: var(--ds-radius-full);
  background: var(--color-danger);
  color: var(--color-danger-contrast, #fff);
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-bold);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  line-height: var(--ds-leading-none);
  box-shadow: var(--ds-shadow-sm);
  backdrop-filter: blur(8px);
}

.product-card__badge--preorderable {
  background: color-mix(in srgb, var(--color-warning) 92%, transparent);
  color: var(--color-warning-contrast, #111827);
}

.product-card__badge--out-of-stock {
  background: color-mix(in srgb, var(--color-secondary) 88%, transparent);
  color: var(--color-secondary-contrast, #fff);
}

/* ── Wishlist toggle ─────────────────────────────────────────────────────── */

.wishlist-button {
  position: absolute;
  inset-block-start: var(--ds-space-3);
  inset-inline-end: var(--ds-space-3);
  z-index: 3;
}

.wishlist-button__toggle {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-full);
  /* Frosted rather than opaque: it sits on the photo and should not punch a
     white hole in it. */
  background: color-mix(in srgb, var(--color-background) 78%, transparent);
  backdrop-filter: blur(10px) saturate(1.4);
  color: var(--color-text);
  cursor: pointer;
  padding: 0;
  box-shadow: var(--ds-shadow-sm);
  transition:
    transform var(--ds-duration-fast) var(--ds-ease-spring),
    background-color var(--ds-duration-fast) var(--ds-ease),
    color var(--ds-duration-fast) var(--ds-ease);
}

.wishlist-button__toggle:hover {
  transform: scale(1.08);
  background: var(--color-background);
  color: var(--color-danger);
}

.wishlist-button__toggle:active {
  transform: scale(0.94);
}

.wishlist-button--active .wishlist-button__toggle {
  color: var(--color-danger);
}

.wishlist-button__icon {
  width: 1.25rem;
  height: 1.25rem;
}

/* The label is for screen readers; the heart carries the meaning visually. */
.wishlist-button__label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* button_to wraps the button in a <form>; strip its layout so the toggle is not
   pushed off its corner. */
.wishlist-button form {
  margin: 0;
  display: contents;
}

/* On the detail page the toggle is a quiet icon-only ghost beside the title:
   saving for later is secondary to buying and sharing, so it no longer gets a
   labelled full-width row of its own. The label stays sr-only (base rule). */
.product-detail__wishlist .wishlist-button {
  position: static;
}

.product-detail__wishlist .wishlist-button__toggle {
  background: transparent;
  backdrop-filter: none;
  box-shadow: none;
  color: var(--color-text-muted);
}

.product-detail__wishlist .wishlist-button__toggle:hover {
  transform: none;
  background: var(--ds-tint-hover);
  color: var(--color-danger);
}

/* Title row: h1 takes the space, the heart sits top-right of it. */
.product-detail__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--ds-space-3);
}

.product-detail__header .product-detail__title {
  flex: 1;
  min-width: 0;
}

.product-detail__header .product-detail__wishlist {
  flex: none;
}

/* ── Price ───────────────────────────────────────────────────────────────── */

.price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--ds-space-2);
  font-variant-numeric: tabular-nums;
}

.price__final {
  font-size: var(--ds-text-xl);
  font-weight: var(--ds-weight-bold);
  line-height: var(--ds-leading-none);
  letter-spacing: var(--ds-tracking-tighter);
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

.price__original {
  font-size: var(--ds-text-sm);
  color: var(--color-text-muted);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  font-variant-numeric: tabular-nums;
}

.price__vat,
.price__from {
  font-size: var(--ds-text-xs);
  color: var(--color-text-muted);
}

.price__from {
  font-weight: var(--ds-weight-medium);
}

/* On the detail page the price is the headline, not a label. */
.product-detail__price .price__final {
  font-size: var(--ds-text-2xl);
}

/* ── Product detail ──────────────────────────────────────────────────────── */

/* The detail page carries no container, breadcrumb or page-margin CSS of its
   own: it renders inside the shared page shell (page_shell.css), same as the
   catalog and the CMS pages. */

.product-detail__layout {
  display: grid;
  gap: var(--ds-space-10);
  align-items: start;
}

@media (min-width: 1024px) {
  .product-detail__layout {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    gap: var(--ds-space-16);
  }

  /* The buy column follows on long pages: the decision is made at the top and
     the button should still be there when the description ends. */
  .product-detail__info {
    position: sticky;
    top: var(--ds-space-8);
  }
}

.product-detail__info {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-5);
  min-width: 0;
}

.product-detail__brand {
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  color: var(--color-primary);
  margin: 0;
  order: -1;
}

.product-detail__title {
  font-size: var(--ds-text-3xl);
  font-weight: var(--ds-weight-bold);
  line-height: var(--ds-leading-tight);
  letter-spacing: var(--ds-tracking-tightest);
  color: var(--color-text);
  text-wrap: balance;
  margin: 0;
}

.product-detail__price {
  display: flex;
  align-items: baseline;
  gap: var(--ds-space-3);
  padding-bottom: var(--ds-space-5);
  border-bottom: 1px solid var(--ds-hairline);
}

.product-detail__stock {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  align-self: flex-start;
  padding: 0.35rem 0.7rem;
  border-radius: var(--ds-radius-full);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-medium);
  margin: 0;
}

.product-detail__stock--preorder {
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  color: color-mix(in srgb, var(--color-warning) 70%, var(--color-text));
}

.product-detail__stock--out {
  background: var(--ds-surface-sunken);
  color: var(--color-text-muted);
}

.product-detail__stock--in {
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
  color: color-mix(in srgb, var(--color-success) 70%, var(--color-text));
}

/* The three availability states of a variant product live side by side and the
   option_picker shows one at a time. `display: inline-flex` on the badge would
   beat the hidden attribute, so restate it. */
.product-detail__stock-states > [hidden] {
  display: none;
}

/* Units of the resolved combination, riding inside the in-stock badge as a
   quieter second line of information. */
.product-detail__stock-units {
  font-weight: var(--ds-weight-normal);
  opacity: 0.75;
}

.product-detail__stock-units[hidden] {
  display: none;
}

.product-detail__description {
  padding-top: var(--ds-space-5);
  border-top: 1px solid var(--ds-hairline);
}

.product-detail__description-title {
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 var(--ds-space-3);
}

.product-detail__description-body {
  color: var(--color-text);
  font-size: var(--ds-text-md);
  line-height: var(--ds-leading-relaxed);
  /* Long-form measure: past ~75 characters the eye loses the line. */
  max-width: 65ch;
}

.product-detail__description-body > * + * {
  margin-top: var(--ds-space-4);
}

.product-detail__description-body a {
  color: var(--color-primary);
  text-underline-offset: 0.2em;
}

/* Technical sheet of a typed product [product_typologies] */
.product-detail__specs {
  margin-top: var(--ds-space-6);
}

.product-specs {
  margin: 0;
  max-width: 65ch;
}

.product-specs__row {
  display: flex;
  justify-content: space-between;
  gap: var(--ds-space-4);
  padding: var(--ds-space-2) 0;
  border-bottom: 1px solid var(--color-border, var(--color-secondary));
  font-size: var(--ds-text-sm);
}

.product-specs__label {
  color: var(--color-text-muted);
}

.product-specs__value {
  margin: 0;
  color: var(--color-text);
  font-weight: var(--ds-weight-medium);
  text-align: right;
}

.product-detail__sku {
  display: flex;
  gap: var(--ds-space-2);
  font-size: var(--ds-text-sm);
  color: var(--color-text-muted);
  margin: 0;
}

.product-detail__sku-value {
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
}

/* Reassurance under the buy button: shipping, returns, secure payment. Icons
   carry no meaning on their own here — each has its label beside it. */
/* No frame: the band is reassurance, not a card, and the box competed with the
   buy button right above it. */
.product-detail__trust {
  display: grid;
  gap: var(--ds-space-3);
  padding: var(--ds-space-4);
  border-radius: var(--ds-radius-md);
  background: var(--ds-gradient-surface);
}

.product-detail__trust-item {
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
  font-size: var(--ds-text-sm);
  color: var(--color-text-muted);
}

.product-detail__trust-item .hi {
  width: 1.125rem;
  height: 1.125rem;
  color: var(--color-primary);
}

/* ── Product gallery ─────────────────────────────────────────────────────── */

/* ONE box, whatever is in it. The stage states its own height and every slide
   fills it, so a 4:5 supplier portrait, a wide banner and the "no photo"
   placeholder all occupy the same rectangle: the page does not resize under the
   buyer as they click through colourways, and the thumbnail strip never moves.

   The photo is fitted with `contain`, not `cover`: a catalogue fed by suppliers
   never ships one ratio (the B&C shots are 4:5), and cropping cut the garment
   top and bottom. Letterboxing inside a fixed box costs some background and
   crops nothing. */
.product-gallery__stage {
  position: relative;
  height: min(70vh, 34rem);
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-xl);
  overflow: hidden;
  background: var(--ds-surface-sunken);
  box-shadow: var(--ds-shadow-sm);
}

/* The slide is a <button> (it opens the lightbox), so it inherits the button
   default `display: inline-block` and SHRINK-WRAPS to the photo. The image
   inside asks for width/height 100% — of a box that was sized by the image
   itself — so it stayed at its natural size and left the square stage empty
   around it on any viewport wider than the photo. The slide has to fill the
   stage first; then `cover` on the image means something. */
.product-gallery__slide {
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}

/* Inactive slides are hidden with the `hidden` ATTRIBUTE, whose UA rule a class
   selector of equal specificity would otherwise beat. */
.product-gallery__slide[hidden] {
  display: none;
}

.product-gallery__slide {
  padding: var(--ds-space-4);
  background: var(--color-background);
}

/* The stage owns the box; the media fits inside it. Letting the photo set the
   height instead put a 2000px supplier portrait past the fold, and made every
   colourway a different page length.

   <picture> is in the chain (optimize_image_tag wraps every photo) and is an
   INLINE box by default, so `height: 100%` on the img resolved against a box of
   automatic height and did nothing. It has to carry the height down. */
.product-gallery__slide picture {
  display: block;
  width: 100%;
  height: 100%;
}

.product-gallery__slide img,
.product-gallery__slide video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  margin-inline: auto;
}

/* No photo to fit, same box to fill: the placeholder takes the stage the photos
   take and the note sits under it, so a colourway nobody shot changes the
   CONTENT of the box and never its size. `aspect-ratio` here sized the slide
   itself — a square as wide as the column, which on a laptop was taller than
   the stage and pushed the note and the thumbnails down the page. */
.product-gallery__slide--empty,
.product-gallery__slide--missing {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: var(--ds-space-3);
  color: color-mix(in srgb, var(--color-text-muted) 55%, transparent);
}

/* The colour has no shot of its own. Said in words under the placeholder — the
   grey box alone is indistinguishable from a slow download. */
.product-gallery__missing-note {
  margin: 0;
  font-size: var(--ds-text-sm);
  color: var(--color-text-muted);
  text-align: center;
}

/* A fixed thumb count per row, per the owner's spec: the strip is a colour
   chart, and a chart that reflows to a different number of columns per viewport
   is read as a different chart. Grid, not flex-wrap: the count is the rule and
   the thumb size follows from it. */
.product-gallery__thumbs {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--ds-space-3);
  margin-top: var(--ds-space-4);
}

/* Tablet */
@media (min-width: 640px) {
  .product-gallery__thumbs { grid-template-columns: repeat(10, minmax(0, 1fr)); }
}

/* Laptop: the gallery is half the row from here, so ten thumbs would be stamps. */
@media (min-width: 1024px) {
  .product-gallery__thumbs { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

/* Laptop L / desktop */
@media (min-width: 1440px) {
  .product-gallery__thumbs { grid-template-columns: repeat(8, minmax(0, 1fr)); }
}

.product-gallery__thumb {
  position: relative;
  /* The column sets the width; the square keeps the row even. */
  width: 100%;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-md);
  overflow: hidden;
  /* Transparent, not sunken: the shots are cut-outs on white, so a grey plate
     behind them reads as a grey box AROUND each shirt instead of a background. */
  background: transparent;
  cursor: pointer;
  padding: 0;
  transition:
    border-color var(--ds-duration-fast) var(--ds-ease),
    box-shadow var(--ds-duration-fast) var(--ds-ease),
    transform var(--ds-duration-fast) var(--ds-ease);
}

/* Same rule as the stage: a cropped thumbnail of a t-shirt is a rectangle of
   colour with no sleeves, and a row of them is unreadable. */
.product-gallery__thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: var(--ds-space-1);
}

.product-gallery__thumb:hover {
  transform: translateY(-2px);
  border-color: var(--ds-hairline-strong);
  box-shadow: var(--ds-shadow-sm);
}

/* The selected thumb gets a ring drawn inside its own box, so selecting one does
   not shift the row by a border width. */
.product-gallery__thumb[aria-current="true"] {
  border-color: var(--color-primary);
  box-shadow: inset 0 0 0 2px var(--color-primary);
}

/* ── Variant selector ────────────────────────────────────────────────────── */

.variant-selector {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
}

.variant-selector__label,
.product-designer__title {
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0;
}

/* Class names here follow _variant_selector.html.erb exactly: __swatches /
   __swatch / __swatch-image / __swatch-label. An earlier pass styled invented
   names (__options / __option), which matched nothing — so the swatch anchors
   were unstyled and each one rendered its variant photo at full width, pushing
   the buy button off the screen. */
.variant-selector__swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-2);
  list-style: none;
  padding: 0;
  margin: 0;
}

.variant-selector__swatch {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  overflow: hidden;
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-sm);
  background: var(--ds-surface-sunken);
  color: var(--color-text);
  cursor: pointer;
  text-decoration: none;
  transition:
    border-color var(--ds-duration-fast) var(--ds-ease),
    box-shadow var(--ds-duration-fast) var(--ds-ease),
    transform var(--ds-duration-fast) var(--ds-ease);
}

.variant-selector__swatch:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--color-primary) 45%, var(--ds-hairline-strong));
  box-shadow: var(--ds-shadow-sm);
}

/* Ring drawn inside the box so selecting a swatch does not shift the row. */
.variant-selector__swatch.is-active,
.variant-selector__swatch[aria-pressed="true"] {
  border-color: var(--color-primary);
  box-shadow: inset 0 0 0 2px var(--color-primary);
}

.variant-selector__swatch-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Colour chip when the product declares a color_web custom field: the chip
   fills the swatch box; the hex itself is data, painted inline by the partial.
   The inner hairline keeps a white colourway visible on a white surface. */
.variant-selector__swatch-color {
  width: 100%;
  height: 100%;
  display: block;
  box-shadow: inset 0 0 0 1px var(--ds-hairline);
}
/* Fallback when a colourway has no photo: initials rather than an empty box. */
.variant-selector__swatch-label {
  padding: var(--ds-space-1);
  font-size: var(--ds-text-2xs);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-tight);
  text-align: center;
  overflow: hidden;
}

.variant-selector__select {
  width: 100%;
  min-height: 2.75rem;
  padding: var(--ds-space-2) var(--ds-space-3);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-sm);
  background: var(--color-background);
  color: var(--color-text);
  font-size: 1rem;
  font-family: inherit;
}

/* ── Size selector ───────────────────────────────────────────────────────── */
/* Radio pills: one tap, the whole row visible, the choice obvious. The native
   radio is visually hidden but stays the accessible control (keyboard + screen
   reader); the label is the target. */
.add-to-cart__size {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
}

.add-to-cart__size-label {
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.add-to-cart__size-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-2);
}

.add-to-cart__size-option {
  cursor: pointer;
}

.add-to-cart__size-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.add-to-cart__size-option > span {
  display: grid;
  place-items: center;
  min-width: 3rem;
  min-height: 2.75rem;
  padding-inline: var(--ds-space-3);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-sm);
  background: var(--color-background);
  color: var(--color-text);
  font-size: var(--ds-text-base);
  font-weight: var(--ds-weight-medium);
  font-variant-numeric: tabular-nums;
  transition:
    border-color var(--ds-duration-fast) var(--ds-ease),
    background-color var(--ds-duration-fast) var(--ds-ease),
    box-shadow var(--ds-duration-fast) var(--ds-ease);
}

.add-to-cart__size-option:hover > span {
  border-color: color-mix(in srgb, var(--color-primary) 45%, var(--ds-hairline-strong));
}

.add-to-cart__size-option input:checked + span {
  border-color: var(--color-primary);
  background: var(--ds-tint-primary);
  color: var(--color-primary);
  box-shadow: inset 0 0 0 1px var(--color-primary);
}

.add-to-cart__size-option input:focus-visible + span {
  box-shadow: var(--ds-focus-ring);
}

/* ── Option picker (real variants) ───────────────────────────────────────── */
/* Size/colour picker for a product sold through ProductVariant rows. The pills
   reuse the size-option rules above; only the colour chips, the disabled state
   and the availability note are picker-specific. */
.option-picker {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
}

/* A combination that cannot be bought: still visible (the range is part of the
   story) and still CLICKABLE, so picking it answers with words instead of doing
   nothing. WHY it cannot be bought is two different facts and the buyer is
   entitled to both — option_picker#applyStates writes the reason onto the label
   as data-state, the title attribute says it on hover, and the availability
   badge says it in full. */
.add-to-cart__size-option[data-state] > span {
  opacity: 0.45;
  background: var(--color-background);
}

/* Sold out: we sell this one, there is none left. The diagonal strike reads as
   "crossed off the list". */
.add-to-cart__size-option[data-state="sold-out"] > span {
  background:
    linear-gradient(to top right,
      transparent calc(50% - 1px),
      var(--ds-hairline-strong) calc(50% - 1px),
      var(--ds-hairline-strong) calc(50% + 1px),
      transparent calc(50% + 1px)),
    var(--color-background);
}

/* Not available: this combination is not made at all (black comes in S only).
   No strike — nothing was crossed off, it was never on the list. The dashed
   border says "outside the range" without competing with the sold-out mark. */
.add-to-cart__size-option[data-state="unavailable"] > span {
  border-style: dashed;
}

.add-to-cart__size-option[data-state]:hover > span {
  border-color: var(--ds-hairline-strong);
}

/* Swatch AND name inside the shared pill: the admin-entered hex on a small
   square, the colour's own name beside it. Twenty greens are indistinguishable
   as bare chips, and the sold-out strike needs something to cross out. The pill
   frame, the checked state and the two unavailable states all come from the
   .add-to-cart__size-option rules above — this only lays out the contents. */
/* Selector carries the label class on purpose: the shared pill rule is
   `.add-to-cart__size-option > span { display: grid }`, which outranks a lone
   class and stacks the swatch ON TOP of the name. Same weight plus one, so the
   row wins. */
.option-picker__option > .option-picker__color {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  justify-content: flex-start;
  /* One line, always: "BOTTLE GREEN" wrapping under its own swatch turns a row
     of pills into a ragged grid of different heights. */
  flex-wrap: nowrap;
  white-space: nowrap;
}

.option-picker__color-dot {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-xs);
}

.option-picker__color-name {
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: var(--ds-text-sm);
  white-space: nowrap;
}

/* The picked colour is filled, not tinted: on a wall of thirty pills a light
   tint reads as "slightly warmer", and the buyer has to hunt for their choice.
   Hover previews the same fill so the target is obvious before the click. */
/* :not([data-state]) on purpose — an unbuyable combination must keep its strike
   and its dashed border under the pointer, so the fill is only ever offered for
   something the buyer can actually have. */
.option-picker__option:not([data-state]):hover > span,
.option-picker__option:not([data-state]) input:checked + span {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  box-shadow: none;
}

/* The swatch keeps its own hex against the filled pill: a white shirt on a dark
   fill still needs an edge. */
.option-picker__option:not([data-state]):hover .option-picker__color-dot,
.option-picker__option:not([data-state]) input:checked + span .option-picker__color-dot {
  border-color: var(--color-primary-contrast);
}

.option-picker__availability {
  font-size: var(--ds-text-sm);
  color: var(--color-text-muted);
}

.option-picker__availability--out {
  color: var(--color-danger);
  font-weight: var(--ds-weight-medium);
}

/* ── Size grid (b2b multi-line buy) ──────────────────────────────────────── */
/* The colour x size quantity matrix that replaces the picker on the b2b
   checkout profile [T-07]. A wholesale reorder is a spreadsheet gesture, so it
   gets a table: one axis per variant axis, one box per purchasable row, one
   submit. Wide size ranges scroll INSIDE the grid — the product page must never
   scroll sideways. */
.size-grid {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
}

.size-grid__hint {
  font-size: var(--ds-text-sm);
  color: var(--color-text-muted);
}

.size-grid__scroll {
  overflow-x: auto;
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-sm);
}

.size-grid__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--ds-text-sm);
}

.size-grid__table th,
.size-grid__table td {
  padding: var(--ds-space-2);
  border-bottom: 1px solid var(--ds-hairline);
  text-align: center;
  white-space: nowrap;
}

.size-grid__table tbody tr:last-child th,
.size-grid__table tbody tr:last-child td {
  border-bottom: 0;
}

.size-grid__table thead th {
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* The axis column stays readable while the sizes scroll past it. */
.size-grid__axis {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--color-background);
  text-align: left;
  font-weight: var(--ds-weight-medium);
}

.size-grid__cell {
  vertical-align: top;
}

.size-grid__input {
  width: 4rem;
  min-height: 2.75rem;
  padding: var(--ds-space-1) var(--ds-space-2);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-sm);
  background: var(--color-background);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--ds-text-base);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.size-grid__input:focus-visible {
  border-color: var(--color-primary);
  box-shadow: var(--ds-focus-ring);
}

.size-grid__stock,
.size-grid__price {
  display: block;
  margin-top: var(--ds-space-1);
  font-size: var(--ds-text-xs);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* A combination that cannot be bought stays visible: the range is part of what
   the buyer needs to see. Same "sold out" diagonal the picker uses. */
.size-grid__empty {
  display: grid;
  place-items: center;
  width: 4rem;
  min-height: 2.75rem;
  margin-inline: auto;
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-sm);
  background:
    linear-gradient(to top right,
      transparent calc(50% - 1px),
      var(--ds-hairline-strong) calc(50% - 1px),
      var(--ds-hairline-strong) calc(50% + 1px),
      transparent calc(50% + 1px)),
    var(--color-background);
}

.size-grid__empty-text {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.size-grid__total {
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-medium);
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

/* ── Add to cart ─────────────────────────────────────────────────────────── */

/* The form owns the rhythm between its own parts (variants, designer, stepper,
   buttons). Without this they butted straight into each other — the stepper sat
   flush on top of the buy button, reading as one welded control. */
.add-to-cart {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--ds-space-4);
}

/* Grid-card mode: one button, nothing to space. It also runs at card width
   (~16rem at four columns), where the detail page's 1rem label and 1.5rem
   padding wrap "Add to cart" onto two lines. */
.add-to-cart--compact {
  gap: 0;
}

.add-to-cart--compact .add-to-cart__submit {
  min-height: 2.75rem;
  padding-inline: var(--ds-space-3);
  font-size: var(--ds-text-base);
}

.add-to-cart__quantity {
  align-self: flex-start;
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-sm);
  overflow: hidden;
  background: var(--color-background);
}

.add-to-cart__step {
  /* 44px: the smallest a finger reliably hits. */
  min-width: 2.75rem;
  min-height: 2.75rem;
  display: grid;
  place-items: center;
  background: transparent;
  border: 0;
  color: var(--color-text-muted);
  cursor: pointer;
  transition:
    background-color var(--ds-duration-fast) var(--ds-ease),
    color var(--ds-duration-fast) var(--ds-ease);
}

.add-to-cart__step:hover {
  background: var(--ds-tint-hover);
  color: var(--color-text);
}

.add-to-cart__step:active {
  background: var(--ds-tint-active);
}

.add-to-cart__step .hi {
  width: 1rem;
  height: 1rem;
}

.add-to-cart__quantity-input {
  /* Wide enough for a typed three-digit reorder, not just the stepper's 1-9. */
  width: 5rem;
  min-height: 2.75rem;
  border: 0;
  border-inline: 1px solid var(--ds-hairline);
  background: transparent;
  text-align: center;
  color: var(--color-text);
  /* 16px: anything smaller and iOS zooms the page on focus. */
  font-size: 1rem;
  font-weight: var(--ds-weight-semibold);
  font-family: inherit;
  font-variant-numeric: tabular-nums;
}

.add-to-cart__quantity-input::-webkit-outer-spin-button,
.add-to-cart__quantity-input::-webkit-inner-spin-button {
  appearance: none;
  margin: 0;
}

.add-to-cart__actions {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
}

.add-to-cart__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-2);
  width: 100%;
  min-height: 3rem;
  padding-inline: var(--ds-space-6);
  border: 1px solid transparent;
  border-radius: var(--button-radius, var(--ds-radius-sm));
  font-size: var(--ds-text-md);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: var(--ds-tracking-tight);
  cursor: pointer;
  font-family: inherit;
  transition:
    transform var(--ds-duration-fast) var(--ds-ease),
    box-shadow var(--ds-duration-base) var(--ds-ease),
    background-color var(--ds-duration-fast) var(--ds-ease),
    border-color var(--ds-duration-fast) var(--ds-ease),
    color var(--ds-duration-fast) var(--ds-ease);
}

.add-to-cart__submit--primary {
  /* Solid primary, matching .btn-primary (the hero's "Scopri la collezione") so
   * every primary button on the store reads as the same button. It used to be a
   * gradient with a sheen, which set it visibly apart from the rest. */
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  box-shadow: var(--ds-shadow-sm);
}

.add-to-cart__submit--primary:hover {
  transform: translateY(-1px);
  background: var(--color-primary-hover);
  box-shadow: var(--ds-shadow-md);
}

.add-to-cart__submit--primary:active {
  transform: translateY(0);
  box-shadow: var(--ds-shadow-xs);
}

.add-to-cart__submit--secondary {
  background: var(--color-background);
  border-color: var(--ds-hairline-strong);
  color: var(--color-text);
}

.add-to-cart__submit--secondary:hover {
  border-color: var(--color-primary);
  background: var(--ds-tint-primary);
  color: var(--color-primary);
}

.add-to-cart__submit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.add-to-cart__flash {
  font-size: var(--ds-text-sm);
  color: var(--color-text-muted);
}

.add-to-cart__flash:empty {
  display: none;
}

/* ── Product designer ────────────────────────────────────────────────────── */

.product-designer {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-4);
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-md);
  padding: var(--ds-space-5);
  background: var(--ds-gradient-surface);
}

.product-designer:empty {
  display: none;
}

.product-designer__field {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
}

.product-designer__field--inline {
  flex-direction: row;
  align-items: center;
  gap: var(--ds-space-3);
  cursor: pointer;
}

.product-designer__label {
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-medium);
  color: var(--color-text);
}

.product-designer__input,
.product-designer__select,
.product-designer__file {
  width: 100%;
  min-height: 2.75rem;
  padding: var(--ds-space-2) var(--ds-space-3);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-sm);
  background: var(--color-background);
  color: var(--color-text);
  font-size: 1rem;
  font-family: inherit;
  transition:
    border-color var(--ds-duration-fast) var(--ds-ease),
    box-shadow var(--ds-duration-fast) var(--ds-ease);
}

.product-designer__input:focus,
.product-designer__select:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--ds-tint-primary-strong);
}

.product-designer__file::file-selector-button {
  margin-inline-end: var(--ds-space-3);
  padding: var(--ds-space-2) var(--ds-space-3);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-radius-xs);
  background: var(--color-background);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-medium);
  cursor: pointer;
}

.product-designer__checkbox {
  width: 1.15rem;
  height: 1.15rem;
  accent-color: var(--color-primary);
  flex-shrink: 0;
}

.product-designer__preview {
  padding: var(--ds-space-4);
  border: 1px dashed var(--ds-hairline-strong);
  border-radius: var(--ds-radius-sm);
  min-height: 4rem;
}

.product-designer__preview:empty,
.product-designer__preview[aria-hidden="true"]:not(:has(*:not(:empty))) {
  display: none;
}

.product-designer__preview-images {
  display: flex;
  gap: var(--ds-space-2);
  flex-wrap: wrap;
}

.product-designer__preview-images img {
  width: 3.5rem;
  height: 3.5rem;
  object-fit: cover;
  border-radius: var(--ds-radius-xs);
}

/* ── Focus ───────────────────────────────────────────────────────────────── */

/* Never remove the ring without giving one back: keyboard users navigate by it.
   Drawn as a box-shadow so it follows each control's own border-radius. */
.product-card__title a:focus-visible,
.product-card__cta:focus-visible,
.product-listing__sort select:focus-visible,
.product-gallery__thumb:focus-visible,
.variant-selector__option:focus-visible,
.add-to-cart__step:focus-visible,
.add-to-cart__quantity-input:focus-visible,
.add-to-cart__submit:focus-visible,
.product-designer__checkbox:focus-visible,
.product-designer__file:focus-visible,
.wishlist-button__toggle:focus-visible,
.filter-sidebar__submit:focus-visible,
.filter-sidebar__clear:focus-visible,
/* The radio is the focusable node, but the row is what a keyboard user sees
   move, so the ring is drawn on the row. */
.filter-sidebar__option:has(.filter-sidebar__radio:focus-visible),
.product-grid__pagination a:focus-visible,
.cart-addons input:focus-visible {
  outline: none;
  box-shadow: var(--ds-focus-ring);
}

/* The stretched link's ring is drawn on the card, so hide its own. */
.product-card__title a:focus-visible {
  box-shadow: none;
  outline: none;
}

/* ── Reduced motion ──────────────────────────────────────────────────────
   _design_system.css already collapses every duration globally. What remains is
   the transforms, which are instant rather than absent under that rule and so
   must be suppressed by hand. */
@media (prefers-reduced-motion: reduce) {
  .product-card:hover,
  .product-card:hover .product-card__image,
  .product-gallery__thumb:hover,
  .wishlist-button__toggle:hover,
  .add-to-cart__submit--primary:hover,
  .product-card__cta:hover .hi {
    transform: none;
  }
}

/* ── Blank media placeholder ─────────────────────────────────────────────────
 * The intentional empty state for a missing image (Frontend::AttachmentHelper
 * #blank_media_placeholder). Replaces the grey "NO IMAGE" JPEG that used to fill
 * every card and hero on a catalogue without photography. A soft token-tinted
 * surface with a faint centred icon: reads as "no photo yet", not as broken.
 * Fills whatever slot the caller sized. */
.blank-media {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-height: 100%;
  background:
    linear-gradient(135deg,
      color-mix(in srgb, var(--color-secondary) 6%, var(--color-surface)) 0%,
      color-mix(in srgb, var(--color-secondary) 12%, var(--color-surface)) 100%);
  color: color-mix(in srgb, var(--color-text) 22%, transparent);
}

.blank-media__icon {
  width: clamp(1.75rem, 6%, 3rem);
  height: auto;
}

/* When the slot has no intrinsic height (a bare grid cell), give the
 * placeholder a sensible square so it does not collapse to the icon. */
.blank-media:only-child {
  aspect-ratio: 1 / 1;
}

/* ── Cart count badge ────────────────────────────────────────────────────────
 * The header cart icon's item counter. The class existed with no rule, so the
 * count rendered as a bare number floating beside the bag — hard to read as a
 * cart badge. A small filled disc in the primary colour, pinned to the top-right
 * of the icon, the way every storefront shows it. */
.frontend-cart-badge {
  position: absolute;
  top: -0.5rem;
  right: -0.6rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.15rem;
  height: 1.15rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  /* Visible on the header whatever its colour: the badge takes the menu's own
   * text colour as its fill (white on a dark header, dark on a light one) and
   * inverts the number against it, so a dark primary on a dark menu can never
   * make it disappear. */
  background: var(--frontend-menu-text, var(--color-primary));
  color: var(--frontend-menu-surface, var(--color-on-primary));
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--frontend-menu-dark-background, var(--color-background));
}

.frontend-cart-badge[hidden] { display: none; }

/* ── Product trust band + share ──────────────────────────────────────────── */
.product-detail__trust {
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 1rem 0;
  display: grid;
  gap: 0.75rem;
}
.product-detail__trust-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.875rem;
  color: var(--color-text);
}
.product-detail__trust-item svg { color: var(--color-primary); flex: none; }

/* Share block, right under the buy buttons: WhatsApp is the loud option (the
   owner's customers share by chat), email and copy-link are the quiet ones. */
.product-detail__share {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--ds-space-3);
}
.product-detail__share-whatsapp {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-2);
  flex: 1;
  min-height: 2.75rem;
  padding-inline: var(--ds-space-5);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--button-radius, var(--ds-radius-sm));
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  color: var(--color-text);
  text-decoration: none;
  transition: background-color var(--ds-duration-fast) var(--ds-ease),
              border-color var(--ds-duration-fast) var(--ds-ease);
}
/* WhatsApp brand green on the glyph only, never as a theme token. */
.product-detail__share-whatsapp .bi--whatsapp {
  color: #25d366;
  flex: none;
}
.product-detail__share-whatsapp:hover {
  background: var(--ds-tint-hover);
  border-color: color-mix(in srgb, var(--color-primary) 45%, var(--ds-hairline-strong));
}
.product-detail__share-channels {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
}
.product-detail__share-label {
  font-size: 0.8rem;
  color: var(--color-text-muted);
  margin-right: 0.25rem;
}
.product-detail__share-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border-radius: 999px;
  border: 1px solid var(--ds-hairline-strong);
  background: transparent;
  cursor: pointer;
  color: var(--color-text);
  transition: background-color var(--ds-duration-fast) var(--ds-ease),
              color var(--ds-duration-fast) var(--ds-ease);
}
.product-detail__share-link:hover {
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  border-color: var(--color-primary);
}
/* Copy-link feedback: share_copy_controller flips is-copied for a moment. */
.product-detail__share-link .product-detail__share-idle,
.product-detail__share-link .product-detail__share-copied {
  display: inline-flex;
}
.product-detail__share-link .product-detail__share-copied {
  display: none;
}
.product-detail__share-link.is-copied .product-detail__share-idle {
  display: none;
}
.product-detail__share-link.is-copied .product-detail__share-copied {
  display: inline-flex;
  color: var(--color-success, currentColor);
}

/* ── Floating cart (menu-less pages) ─────────────────────────────────────────
 * Landing pages hide the header, which is where the cart lives. The same
 * mini_cart component is pinned top-right instead, so a chrome-less page still
 * has the badge, the drawer, and a way into the cart. Sits under the drawer
 * (z-index 60) and clear of the sticky buy bar (z-index 40). */
.frontend-floating-cart {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: 9999px;
  background: var(--color-surface, #fff);
  box-shadow: 0 6px 20px color-mix(in srgb, var(--color-text) 18%, transparent);
}

/* The button inherits the header's menu-link colours, which no longer exist on
   a page without a header: repaint it against the floating surface. */
.frontend-floating-cart button {
  color: var(--color-text);
}

/* ── Mini-cart drawer ────────────────────────────────────────────────────────
 * The add-to-cart feedback: a right-side slide-over, not the old top-right
 * dropdown that was easy to miss. Opens on a successful add (mini-cart
 * controller toggles `hidden`); the panel slides in from the edge each time it
 * is shown, over a dimmed page. */
.mini-cart-drawer {
  position: fixed;
  inset: 0;
  z-index: 60;
}
.mini-cart-drawer.hidden { display: none; }

.mini-cart-drawer__backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--color-text) 45%, transparent);
  animation: mini-cart-fade var(--ds-duration-base, 0.25s) var(--ds-ease, ease);
}

/* One deliberate geometry: a full-height drawer pinned to the right edge. The
   panel is the only surface (the body inside it is transparent), so the drawer
   never reads as a floating card sitting on a white slab. */
.mini-cart-drawer__panel {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: min(24rem, 100vw);
  display: flex;
  flex-direction: column;
  background: var(--color-background, #fff);
  box-shadow: var(--ds-shadow-xl, 0 20px 40px rgba(0,0,0,.2));
  animation: mini-cart-slide var(--ds-duration-base, 0.25s) var(--ds-ease, ease);
}

.mini-cart-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex: 0 0 auto;
  padding: 1rem;
  border-bottom: 1px solid var(--ds-hairline, rgba(0, 0, 0, 0.1));
}

.mini-cart-drawer__title {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text);
}

.mini-cart-drawer__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 9999px;
  color: var(--color-text);
  background: transparent;
  cursor: pointer;
  transition: background-color var(--ds-duration-fast, 0.15s) var(--ds-ease, ease);
}
.mini-cart-drawer__close:hover {
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
}
.mini-cart-drawer__close:focus-visible {
  outline: 2px solid var(--color-primary, currentColor);
  outline-offset: 2px;
}

/* The Turbo Stream target. It fills what is left under the header, and its item
   list is the part that scrolls, so the total and the actions stay in view. */
.mini-cart-drawer__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  padding: 1rem;
}

.mini-cart-drawer__items {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

@keyframes mini-cart-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes mini-cart-slide { from { transform: translateX(100%) } to { transform: translateX(0) } }

/* ── Checkout ────────────────────────────────────────────────────────────── */

/* Turbo frames are custom elements and browsers lay unknown elements out
   inline, where vertical margins do not apply. The one-page checkout stacks
   its contact and address blocks as frames inside a space-y column, so the
   rhythm utilities silently skipped them and the blocks crowded whatever
   followed. Scoped to the checkout column: frames used inline elsewhere
   (buttons, badges) stay inline. */
.checkout-sections > turbo-frame {
  display: block;
}

/* The new-shipping-address panel on the checkout. An overlay rather than a
   native <dialog>: showModal() paints in the top layer, above everything, and
   searchable-select renders its option lists into document.body — inside a
   dialog every picker in the panel (country, province, city, dialling code)
   would open behind it. At --ds-z-modal the panel sits over the page and under
   those lists, so they stay usable.

   The scrim is mixed from the text colour, like the mini-cart drawer's, so it
   dims with the theme instead of stating a colour of its own. */
.checkout-address-modal {
  position: fixed;
  inset: 0;
  z-index: var(--ds-z-modal);
  display: none;
  align-items: flex-start;
  justify-content: center;
  overflow-y: auto;
  padding: 1rem;
  background: color-mix(in srgb, var(--color-text) 45%, transparent);
}

.checkout-address-modal:not(.hidden) {
  display: flex;
}

@media (min-width: 640px) {
  .checkout-address-modal {
    align-items: center;
    padding: 1.5rem;
  }
}

turbo-frame.subpages-preview-page-frame[src]:not([complete]) {
  display: block;
  grid-column: 1 / -1;
  min-height: 4rem;
  width: 100%;
}

turbo-frame.subpages-preview-page-frame[complete],
turbo-frame.subpages-preview-page-frame:not([src]) {
  display: contents;
}

/* Testimonials module — social proof cards, carousel and spotlight.
 *
 * Composes the storefront design system (--ds-* tokens, _design_system.css):
 * hairlines, surfaces, radii, type scale and shadows all come from there, so
 * a client palette re-skins the module without a rule here changing.
 *
 * Every rule is scoped with [data-module="testimonials"] (see
 * docs/guides/MODULE_CSS_GUIDE.md). Fluid spacing uses clamp() calibrated at
 * ~690px and ~1440px like the hero module.
 */

/* Header-to-quotes gap grows with the viewport. */
[data-module="testimonials"] .testimonials-header {
  margin-bottom: clamp(2rem, 1.5rem + 2vw, 3.5rem);
}

[data-module="testimonials"] .testimonials-grid {
  gap: clamp(1rem, 0.75rem + 1vw, 1.75rem);
}

/* ── Card ─────────────────────────────────────────────────────────────────
   Explicit surface + text pair: the card stays legible when the shared
   backgrounds group paints the section dark, because it never inherits the
   section's inverted text colour. */
[data-module="testimonials"] .testimonial-card {
  padding: clamp(1.25rem, 1rem + 1vw, 2rem);
  color: var(--color-text);
  background: var(--ds-surface);
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-shadow-xs);
  transition: box-shadow var(--ds-duration-base) var(--ds-ease);
}

[data-module="testimonials"] .testimonial-card:hover {
  box-shadow: var(--ds-shadow-sm);
}

[data-module="testimonials"] .testimonial-quote {
  margin-top: clamp(0.75rem, 0.6rem + 0.5vw, 1.125rem);
  font-size: var(--ds-text-base);
  line-height: var(--ds-leading-relaxed);
}

/* Opening quote glyph — drawn, not typed, so it never reaches the i18n copy. */
[data-module="testimonials"] .testimonial-quote::before {
  content: "\201C";
  display: block;
  height: 0.6em;
  font-size: 2.25em;
  line-height: 1;
  color: var(--color-primary);
  opacity: 0.35;
}

/* ── Author row ──────────────────────────────────────────────────────────── */
[data-module="testimonials"] .testimonial-author {
  gap: 0.75rem;
  margin-top: clamp(1.25rem, 1rem + 0.75vw, 1.75rem);
}

[data-module="testimonials"] .testimonial-avatar {
  width: 3rem;
  height: 3rem;
  object-fit: cover;
  border-radius: var(--ds-radius-full);
  border: 1px solid var(--ds-hairline-strong);
  flex-shrink: 0;
}

[data-module="testimonials"] .testimonial-author-name {
  font-size: var(--ds-text-base);
  font-style: normal; /* cite defaults to italic */
  font-weight: var(--ds-weight-semibold);
}

/* Opacity instead of a fixed grey: stays readable over dark sections too. */
[data-module="testimonials"] .testimonial-author-role {
  font-size: var(--ds-text-sm);
  opacity: 0.7;
}

/* ── Rating stars ────────────────────────────────────────────────────────── */
[data-module="testimonials"] .testimonial-rating {
  display: inline-flex;
  gap: 0.125rem;
}

[data-module="testimonials"] .testimonial-star {
  color: var(--ds-hairline-strong);
}

[data-module="testimonials"] .testimonial-star--filled {
  color: var(--color-primary);
}

/* ── Spotlight ───────────────────────────────────────────────────────────
   No card surface: the quote sits directly on the section background and
   inherits its text colour, so it is dark-background friendly by default. */
[data-module="testimonials"] .testimonial-spotlight-item {
  padding: clamp(0.5rem, 0.25rem + 1vw, 1.5rem) clamp(2.5rem, 2rem + 2vw, 4rem);
}

[data-module="testimonials"] .testimonial-spotlight-quote {
  max-width: 46ch;
  margin-top: clamp(1rem, 0.75rem + 1vw, 1.75rem);
  font-size: var(--ds-text-xl);
  line-height: var(--ds-leading-snug);
  letter-spacing: var(--ds-tracking-tight);
  font-weight: var(--ds-weight-medium);
  text-wrap: balance;
}

[data-module="testimonials"] .testimonial-author--spotlight {
  gap: 0.5rem;
  margin-top: clamp(1.5rem, 1.125rem + 1.5vw, 2.5rem);
}

[data-module="testimonials"] .testimonial-avatar--spotlight {
  width: 4rem;
  height: 4rem;
}

/* The spotlight arrows sit inside the side padding reserved above; keep them
   visible without hover on touch-less precision (the shared .carousel__arrow
   fades in on hover — a lone big quote gives no affordance otherwise). */
[data-module="testimonials"] .testimonials-spotlight .carousel__arrow {
  opacity: 1;
  transform: translateY(-50%) scale(1);
}

/* ───────────────────────────────────────────────────────────────────────────
   fantasia — custom frontend CSS (thin override)

   This file is loaded AFTER all of themes/default/assets/stylesheets/*.css, so
   any rule here cascade-wins over the base theme. Put ONLY fantasia-specific
   tweaks here (backgrounds, spacing, bespoke component styling). The base
   button/carousel/hero/etc. styles come from default automatically.

   The dev working on fantasia edits files under themes/fantasia/ ONLY — never
   touches themes/default or themes/pasciuti, so two devs never collide.
   ─────────────────────────────────────────────────────────────────────────── */
.frontend-main-menu {
  --frontend-menu-logo-height: 3rem;
}

.frontend-main-menu .frontend-main-menu__topbar,
.frontend-main-menu--transparent-fixed.frontend-main-menu--scrolled .frontend-main-menu__topbar {
  background: var(--color-primary);
}

@media (min-width: 1024px) {
  .frontend-main-menu--center-logo {
    --frontend-menu-logo-height: 3.75rem;
  }

  .frontend-main-menu--center-logo .frontend-main-menu__logo-image {
    height: var(--frontend-menu-logo-height);
    max-height: var(--frontend-menu-logo-height);
    width: auto;
  }
}

@media (max-width: 1023px) {
  .frontend-main-menu .frontend-main-menu__mobile-top-nav {
    border-color: color-mix(in srgb, var(--frontend-menu-text) 20%, transparent);
  }

  .frontend-main-menu__mobile-item--open .frontend-main-menu__mobile-submenu {
    max-height: 80rem;
  }

  .frontend-main-menu__mobile-submenu .btn-outline-light,
  .frontend-main-menu__mobile-submenu .btn-outline-light:visited {
    color: #ffffff;
  }

  .frontend-main-menu__mobile-submenu .btn-outline-light:hover {
    color: #111827;
  }
}

/* Footer text/links at full opacity (the default theme dims them to 70%).
   Theme-side replacement for a former edit to the core footer style map. */
footer[aria-labelledby="footer-heading"] .opacity-70 {
  opacity: 1;
}

.fantasia-footer-logo,
.fantasia-footer-logo > a,
.fantasia-footer-logo .optimize-image,
.fantasia-footer-logo .optimize-image img {
  width: 100%;
}

.fantasia-footer-logo > a,
.fantasia-footer-logo .optimize-image {
  display: block;
}

.fantasia-footer-logo .optimize-image,
.fantasia-footer-logo .optimize-image img {
  height: auto;
}

/* Single Banner — mobile layout
   Keep the image as a full-frame background while allowing the text overlay
   to define the banner height, so wrapped content is never clipped. */
@media (max-width: 639px) {
  [data-module="single-banner"] .single-banner__frame {
    min-height: 24rem;
  }

  [data-module="single-banner"] .single-banner__frame > [class~="z-0"] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
  }

  [data-module="single-banner"] .single-banner__frame > [class~="z-0"] > picture,
  [data-module="single-banner"] .single-banner__frame > [class~="z-0"] img {
    display: block;
    width: 100%;
    height: 100%;
  }

  [data-module="single-banner"] .single-banner__frame > [class~="z-0"] img {
    object-fit: cover;
  }

  [data-module="single-banner"] .single-banner__frame > [class~="z-20"] {
    position: relative;
    min-height: 24rem;
    padding: 2rem 1.5rem;
  }

  [data-module="single-banner"] .single-banner__frame > [class~="z-20"] > div {
    width: 100%;
    padding: 1.25rem;
  }
}

/* standard_page - gap system*/

.fantasia-page-stack--flush-first-gap > .main-standard-page {
  margin-top: calc(var(--mobile-gap) * -1);
}

@media (min-width: 768px) {
  .fantasia-page-stack--flush-first-gap > .main-standard-page {
    margin-top: calc(var(--desktop-gap) * -1);
  }
}
