/* ========= design tokens ========= */
:root {
  --border-radius: 8px;
  --border-radius-sm: 4px;
  --transition: 200ms ease;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --reveal-shift: 26px;
}

/* Users who ask for less motion get a calm, static experience. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1 !important; transform: none !important; }
  .announcement-track, .promo-marquee-track { animation: none !important; }
  /* Announcer (option B): reduced motion = show only the first message, no
     cross-fade cycling, all messages stacked visibly would be chaos. */
  .announcer-msg { transition: none !important; }
  .announcer-msg.is-active { opacity: 1 !important; visibility: visible !important; }
  .announcer-msg:not(.is-active) { display: none !important; }
}

:root {
  /* Warm quiet-luxury palette (ouqla/Aesop): cream grounds, obsidian ink, stone lines.
     Light-only since 2026-09-26 — the dark theme was retired. */
  --bg: #FBFBF9;
  --bg-alt: #F5F4F0;
  --bg-elevated: #ffffff;
  --bg-card: #F5F4F0;  /* product card frame fill — soft stone for the "square in square" look */
  --text: #111111;
  --text-alt: #55524c;
  --text-muted: #8a867e;
  --border: #E5E5E0;
  --accent: #b8935e;
  --accent-hover: #a07a46;
  --sale: #c43d3d;
  --success: #3d9944;
  --chip-bg: #f0efeb;
  --shadow: 0 2px 8px rgba(17,17,17,0.05);
  --shadow-lg: 0 8px 32px rgba(17,17,17,0.09);
}

/* ========= base ========= */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--text);
  font-size: 16px;
  line-height: 1.55;
  transition: background var(--transition), color var(--transition);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, .logo, .section-title, .hero h1, .pdp-title, .product-card-title, .announcement-item { font-family: var(--font-display);
  font-optical-sizing: auto; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; height: auto; display: block; }
button, input, select, textarea {
  font: inherit;
  color: inherit;
}
button { cursor: pointer; background: none; border: none; }
main { min-height: 60vh; }

.container { max-width: 1280px; margin: 0 auto; padding: 0 24px; }

/* ========= announcement bar ========= */
.announcement-bar {
  background: #171310;        /* warm near-black, not #000: pairs with the gold instead
                                 of fighting it */
  color: #f3ece2;
  overflow: hidden;
  font-size: 11px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  padding: 8px 0;
  font-weight: 600;
}
/* ---- OPTION A (active): refined scroll marquee ----
   Slow drift, gold ✦ separators between messages, pause on hover. The JS
   (initMarquee) tops up copies so the -50% loop has no gap on wide screens. */
.announcement-item { padding: 0 34px; flex-shrink: 0; display: inline-flex; align-items: center; gap: 68px; }
.announcement-sep { color: #d4af37; font-size: 9px; opacity: 0.9; letter-spacing: 0; }
/* Chat photo attachments (admin → customer). Tappable to open full-size. */
.chat-attachment {
  display: block; max-width: 220px; max-height: 220px;
  width: auto; height: auto; object-fit: contain;
  border-radius: 10px; margin: 6px 0; cursor: zoom-in;
}
.announcement-track {
  display: flex;
  /* CRITICAL: width:max-content makes the track as wide as ALL its content.
     Without it the track is only as wide as the bar, and translateX(-50%)
     would snap visibly instead of looping seamlessly. */
  width: max-content;
  white-space: nowrap;
  will-change: transform;
  animation: marquee 42s linear infinite; /* slower than before: 22s → 42s */
}
.announcement-bar:hover .announcement-track { animation-play-state: paused; }
@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
/* ========= sale countdown banner ========= */
.sale-countdown {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 14px;
  background: linear-gradient(90deg, #b45309, #92400e);
  color: #fff;
  padding: 10px 16px;
  font-size: 13px;
  letter-spacing: 0.06em;
}
.sc-label {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 12px;
}
.sc-timer { display: inline-flex; align-items: center; gap: 6px; }
.sc-unit {
  display: inline-flex; align-items: baseline; gap: 4px;
  background: rgba(0,0,0,0.22);
  padding: 4px 9px; border-radius: 6px;
}
.sc-num {
  font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: 16px; line-height: 1;
  min-width: 22px; text-align: center;
}
.sc-cap { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.85; }
.sc-sep { font-weight: 700; font-size: 15px; opacity: 0.75; }
@media (max-width: 600px) {
  .sale-countdown { gap: 8px; padding: 9px 12px; }
  .sc-label { width: 100%; text-align: center; }
  .sc-num { font-size: 15px; min-width: 20px; }
  .sc-unit { padding: 3px 7px; }
}
/* ---- OPTION B (rotating cross-fade) — kept dormant for easy switch-back.
   Restore the announcer markup in layout.php + initAnnouncer in site.js. ---- */
.announcer-stack {
  display: grid;
  justify-items: center;
  align-items: center;
  min-height: 1.4em;
}
.announcer-msg {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  transition: opacity 640ms ease, visibility 0s linear 640ms;
  will-change: opacity;
}
.announcer-msg.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity 640ms ease;
}

/* ========= header ========= */
.site-header {
  position: sticky; top: 0; z-index: 100;
  /* Fully opaque: a translucent header showed card edges through it, which read
     as a hard "cut" band across product images while scrolling. */
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  transition: padding var(--transition), box-shadow var(--transition), background var(--transition);
}
.header-inner {
  max-width: 1280px; margin: 0 auto; padding: 14px 24px;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
  transition: padding var(--transition);
}
/* Condensed state once the page is scrolled (JS toggles .is-scrolled). */
.site-header.is-scrolled {
  box-shadow: 0 4px 20px rgba(0,0,0,0.08);
}
.site-header.is-scrolled .header-inner { padding-top: 7px; padding-bottom: 7px; }
.site-header.is-scrolled .logo { transform: scale(0.94); }
/* Brand wordmark: Fraunces serif, uppercase, wide quiet tracking. */
.logo {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 500; font-size: 19px; letter-spacing: 0.18em; text-transform: uppercase;
  justify-self: center;
  text-decoration: none;
}
.main-nav { justify-self: center; display: none; gap: 30px; }
/* Quiet underline-draw hover (ouqla pattern): a 1px line grows from the left. */
.main-nav a {
  position: relative;
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 500;
  color: var(--text-alt);
  text-decoration: none;
  padding-bottom: 4px;
  transition: color var(--transition);
}
.main-nav a::after {
  content: ''; position: absolute; left: 0; bottom: 0;
  width: 0; height: 1px; background: currentColor;
  transition: width 300ms cubic-bezier(0.22, 1, 0.36, 1);
}
.main-nav a:hover { color: var(--text); }
.main-nav a:hover::after { width: 100%; }
.nav-sale { color: var(--sale) !important; }
.header-actions { justify-self: end; display: flex; gap: 4px; }
.header-icon {
  width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--border-radius);
  color: var(--text-alt);
  transition: background var(--transition), color var(--transition);
  position: relative;
}
.header-icon:hover { background: var(--bg-alt); color: var(--text); }
.cart-icon .cart-count {
  position: absolute; top: 4px; right: 2px;
  min-width: 16px; height: 16px; border-radius: 999px;
  background: var(--accent); color: #fff;
  font-size: 10px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  padding: 0 4px;
}
/* Brief bounce when the cart count changes. */
.cart-count.pulse { animation: cartPulse 420ms var(--ease-out); }
@keyframes cartPulse {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.45); }
  70%  { transform: scale(0.92); }
  100% { transform: scale(1); }
}
.cart-count[data-count="0"], .cart-count:empty { display: none; }
.hamburger { justify-self: start; }

@media (min-width: 860px) {
  .main-nav { display: flex; }
  .hamburger { display: none; }
  /* Three-zone header: logo left, nav dead-center, utilities right. */
  .logo { justify-self: start; }
}

/* mobile nav */
.mobile-nav {
  position: fixed; inset: 0 30% 0 0; background: var(--bg); z-index: 200;
  transform: translateX(-100%);
  transition: transform var(--transition);
  padding: 32px;
  display: flex; flex-direction: column; gap: 20px;
  box-shadow: var(--shadow-lg);
}
.mobile-nav.open { transform: translateX(0); }
.mobile-nav a {
  font-size: 18px; font-weight: 600; padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.mobile-nav-close {
  align-self: flex-end; font-size: 32px; line-height: 1;
  color: var(--text-muted);
}

/* ========= search overlay ========= */
.search-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 300;
  display: none;
  align-items: flex-start; justify-content: center; padding: 20vh 24px 0;
  backdrop-filter: blur(4px);
}
.search-overlay.open { display: flex; }
.search-form {
  display: flex; gap: 12px; width: 100%; max-width: 600px;
  background: var(--bg); padding: 8px; border-radius: var(--border-radius);
  box-shadow: var(--shadow-lg);
}
.search-form input {
  flex: 1; padding: 14px 16px; border: none; outline: none; background: transparent;
  font-size: 18px;
}
.search-form button {
  padding: 14px 28px; background: var(--text); color: var(--bg);
  border-radius: var(--border-radius-sm); font-weight: 600;
}
.search-close {
  position: absolute; top: 24px; right: 24px; font-size: 36px; color: #fff;
}

/* ========= cart drawer ========= */
.cart-drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 100vw);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-lg);
  z-index: 300;
  transform: translateX(100%);
  transition: transform var(--transition);
  display: flex; flex-direction: column;
}
.cart-drawer.open { transform: translateX(0); }
.cart-drawer-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 299;
  opacity: 0; pointer-events: none;
  transition: opacity var(--transition);
}
.cart-drawer-backdrop.open { opacity: 1; pointer-events: auto; }

.cart-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 20px 24px; border-bottom: 1px solid var(--border);
}
.cart-header h2 { margin: 0; font-size: 18px; }
.cart-close { font-size: 28px; color: var(--text-muted); line-height: 1; }
.cart-body { flex: 1; overflow-y: auto; padding: 24px; }
.cart-loading { text-align: center; color: var(--text-muted); padding: 40px 0; }
.cart-empty { text-align: center; padding: 60px 24px; color: var(--text-muted); }

.cart-item {
  display: grid; grid-template-columns: 72px 1fr auto;
  gap: 16px; padding: 16px 0;
  border-bottom: 1px solid var(--border);
}
.cart-item img { width: 72px; height: 90px; object-fit: cover; border-radius: var(--border-radius-sm); }
.cart-item-title { font-weight: 600; }
.cart-item-variant { color: var(--text-muted); font-size: 14px; margin-top: 2px; }
.cart-item-price { font-size: 14px; margin-top: 4px; }
.cart-item-controls { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.cart-item-controls button {
  width: 28px; height: 28px; border: 1px solid var(--border);
  border-radius: var(--border-radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
}
.cart-item-controls button:hover { background: var(--bg-alt); }
.cart-item-controls input {
  width: 44px; height: 28px; text-align: center;
  border: 1px solid var(--border); border-radius: var(--border-radius-sm);
  background: var(--bg);
}
.cart-item-remove {
  align-self: start;
  color: var(--text-muted); font-size: 18px;
  width: 28px; height: 28px;
}
.cart-item-remove:hover { color: var(--sale); }

.cart-footer { border-top: 1px solid var(--border); padding: 20px 24px; }
.cart-row {
  display: flex; justify-content: space-between; align-items: center;
  padding: 6px 0; font-size: 15px;
}
.cart-row-small { font-size: 12px; color: var(--text-muted); }
.discount-row { display: flex; gap: 8px; margin-bottom: 12px; }
.discount-row input {
  flex: 1; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--border-radius-sm);
  background: var(--bg);
}
.discount-row button {
  padding: 10px 16px; background: var(--bg-alt); border-radius: var(--border-radius-sm);
}
.discount-row button:hover { background: var(--border); }
.discount-applied {
  display: flex; justify-content: space-between; align-items: center;
  padding: 8px 12px; background: var(--bg-alt); border-radius: var(--border-radius-sm);
  font-size: 13px; margin-bottom: 12px;
}
.discount-applied button { color: var(--sale); font-size: 16px; }

/* discount + total rows in the drawer */
.cart-row-discount strong { color: var(--accent, #b8935e); font-weight: 600; }
.cart-row-discount[hidden], .cart-row-total[hidden] { display: none; }
.cart-row-total {
  border-top: 1px solid var(--border);
  margin-top: 8px; padding-top: 12px;
  font-size: 17px; font-weight: 700;
}
.cart-row-total span { letter-spacing: 0.04em; text-transform: uppercase; font-size: 12px; font-weight: 600; color: var(--text-muted); }

/* the "code applied" moment: shimmer sweep + counting number + gold sparkles */
.cart-row-total { position: relative; overflow: visible; }
.cart-row-total strong { position: relative; transition: color 240ms ease; }
.cart-row-total.is-updating strong { color: var(--accent, #b8935e); }
.cart-row-total.is-updating::after {
  content: '';
  position: absolute; inset: 6px 0; border-radius: 6px;
  background: linear-gradient(100deg,
    transparent 20%, rgba(184,147,94,0.16) 50%, transparent 80%);
  transform: translateX(-100%);
  animation: cartShimmer 900ms ease-out;
  pointer-events: none;
}
@keyframes cartShimmer {
  to { transform: translateX(100%); }
}
.total-sparkle-layer {
  position: absolute; top: 50%; right: 0; width: 1px; height: 1px;
  pointer-events: none; z-index: 5;
}
.total-sparkle-layer .spark {
  position: absolute; top: 0; left: 0;
  width: var(--sz, 4px); height: var(--sz, 4px);
  margin: calc(var(--sz, 4px) / -2);
  background: var(--clr, #b8935e);
  border-radius: 50%;
  opacity: 0;
  animation: cartSpark 900ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes cartSpark {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.4); }
  18%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx, 0), var(--dy, -40px)) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  
.cart-row-total.is-updating::after, .total-sparkle-layer .spark { animation: none; }
}

/* ========= buttons ========= */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 14px 32px;
  font-weight: 600; font-size: 14px; letter-spacing: 0.06em; text-transform: uppercase;
  border-radius: var(--border-radius-sm);
  border: 1px solid transparent;
  transition: background var(--transition), color var(--transition),
              transform 180ms var(--ease-out), box-shadow 180ms var(--ease-out);
  text-decoration: none;
  will-change: transform;
}
.btn-primary { background: var(--text); color: var(--bg); }
.btn-primary:hover { background: var(--accent); color: #fff; }
.btn-secondary { background: transparent; color: var(--text); border-color: var(--text); }
.btn-secondary:hover { background: var(--text); color: var(--bg); }
.btn-ghost { background: transparent; color: var(--text-alt); }
.btn-ghost:hover { color: var(--text); }
.btn-block { width: 100%; margin-bottom: 8px; }
.btn-cta {
  padding: 18px 48px; font-size: 15px;
  background: var(--accent); color: #fff;
  border-radius: var(--border-radius-sm);
}
.btn-cta:hover { background: var(--accent-hover); }
/* Gentle lift on hover for the primary CTAs (transform/shadow only = GPU-cheap). */
.btn-primary:hover, .btn-cta:hover, .btn-secondary:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 22px rgba(0,0,0,0.16);
}
.btn:active { transform: translateY(0) scale(0.985); }
.btn[disabled], .btn[disabled]:hover { opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none; }

/* ========= hero — editorial split grid (ouqla pattern) =========
   Left: quiet copy stack (eyebrow / rating badge / serif headline / sub / CTA).
   Right: flagship product spotlight carousel with dots.
   Strict overflow boundary: the carousel clips inside its column. */
.hero-editorial {
  /* Soft warm sand ground behind the whole hero (Ken: "maybe the background there
     like not white"). Flat gradient only - no radial, no hotspot, so nothing can
     read as a stain behind the product.
     NOTE: this must come AFTER the old `background: var(--bg)` that used to sit
     here; when it was placed above it, the cream silently won and the hero looked
     pure white. */
  background: linear-gradient(180deg, #F8F4ED 0%, #F2ECE1 100%);
  color: var(--text);
  overflow: hidden;
}
.hero-grid {
  max-width: 1280px; margin: 0 auto;
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  align-items: center;
  gap: clamp(48px, 8vw, 96px);
  padding: clamp(48px, 8vh, 96px) 24px;
  min-height: 78vh;
}
.hero-copy { max-width: 520px; }
.hero-eyebrow {
  text-transform: uppercase; letter-spacing: 0.32em; font-size: 11px;
  color: var(--accent);              /* was --text-muted: the hero opened in grey on
                                        cream, which is why it read flat */
  font-weight: 600;
  margin-bottom: 22px;
}
.hero-rating-badge {
  display: inline-flex; align-items: center; gap: 10px;
  margin-bottom: clamp(20px, 3vh, 30px);
}
.hero-rating-badge .stars { color: var(--accent); font-size: 14px; letter-spacing: 2.5px; }
.hero-rating-text {
  font-size: 10.5px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--text-alt); font-weight: 500;
}
.hero-copy h1 {
  font-family: var(--font-display);
  font-size: clamp(44px, 6vw, 76px);
  line-height: 1.02; font-weight: 400;
  letter-spacing: -0.02em;
  margin: 0 0 clamp(18px, 2.5vh, 26px);
  color: var(--text);
}
.hero-tagline {
  font-size: clamp(15px, 1.5vw, 17px);
  color: var(--text-alt); font-weight: 300; line-height: 1.65;
  margin: 0 0 clamp(30px, 4.5vh, 44px);
  max-width: 46ch;
}
.hero-cta-btn {
  background: linear-gradient(180deg, var(--accent) 0%, var(--accent-hover) 100%) !important;
  color: #141414 !important;
  border-color: transparent !important;
  box-shadow: 0 10px 24px -12px color-mix(in srgb, var(--accent) 70%, transparent);
  display: inline-flex; align-items: center; gap: 12px;
  background: #111; color: #FBFBF9;
  text-decoration: none;
  font-size: 12px; letter-spacing: 0.22em; text-transform: uppercase; font-weight: 600;
  padding: 18px 30px;
  border-radius: 2px;
  box-shadow: 0 1px 2px rgba(17,17,17,0.2);
  transition: transform 260ms var(--ease-out), box-shadow 260ms var(--ease-out), background 260ms ease;
}
.hero-cta-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 28px rgba(17,17,17,0.22);
  background: #000;
}
.hero-cta-btn span { transition: transform 300ms var(--ease-out); }
.hero-cta-btn:hover span { transform: translateX(5px); }

/* ---- spotlight carousel — STRICT overflow boundary ---- */
.hero-spotlight { position: relative; }

/* No halo, no blob, no glow behind the hero product (Ken, 2026-10-30: "remove that
   like gold spot that looks like poop"). The strap sits directly on the page. */
.hero-spotlight { position: relative; }
/* .hero-spot-clip is the hard boundary. Slides translating inside never bleed
   into the text column. overflow + width:100% is the key overlap fix. */
.hero-spot-clip {
  overflow: hidden;
  border-radius: 14px;
  position: relative;
  width: 100%;
  /* Swipe support on touch: pan-y lets the PAGE keep vertical scrolling while
     horizontal drags are delivered to the carousel (without it, iOS fires
     pointercancel and swipes are swallowed). Matches .product-card-media. */
  touch-action: pan-y;
}
.hero-spot-track {
  display: flex;
  transition: transform 480ms var(--ease-out);
  will-change: transform;
}
.hero-spot-slide {
  flex: 0 0 100%;
  min-width: 100%;
}
.hero-spot-media {
  aspect-ratio: 1/1; overflow: hidden;
  /* Was background: var(--bg-alt) — a light stone plate inside the padded frame.
     THAT is the pale rectangle at the top of the hero image Ken kept seeing: the
     photo is transparent, so the plate showed through everywhere the strap wasn't.
     Transparent now; the second .hero-spot-media rule below already said so, but the
     plate was painted here first and this rule set no padding, so the two frames
     never lined up. */
  background: transparent;
  border-radius: 14px;
  box-shadow: none;
}
.hero-spot-media img {
  width: 100%; height: 100%; object-fit: contain;
  user-select: none; -webkit-user-drag: none;
  transition: transform 700ms var(--ease-out);
}
.hero-spot-slide:hover .hero-spot-media img { transform: scale(1.03); }

/* Ouqla-style dots navigation */
.hero-dots {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 22px;
}
.hero-dot {
  width: 8px; height: 8px; border-radius: 50%;
  border: none; padding: 0; cursor: pointer;
  background: color-mix(in srgb, var(--accent) 55%, transparent);
  transition: background 220ms ease, transform 220ms ease;
}
.hero-dot:hover { background: var(--accent); }
.hero-dot.active {
  background: var(--accent);          /* gold, not black - the carousel marker is
                                         the one piece of UI that lives right next to
                                         the product, so it carries the accent */
  transform: scale(1.35);
}

/* rise-in, staggered */
.hero-copy > *, .hero-spotlight {
  animation: hero-rise 900ms var(--ease-out) both;
}
.hero-copy h1 { animation-delay: 110ms; }
.hero-tagline { animation-delay: 240ms; }
.hero-cta-btn { animation-delay: 360ms; }
.hero-spotlight { animation-delay: 200ms; }
@keyframes hero-rise {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; min-height: 0; gap: 40px; padding-top: 56px; padding-bottom: 64px; }
  .hero-copy { max-width: none; }
}

/* ========= promo marquee (infinite ticker) ========= */
.promo-marquee {
  background: #111; color: #FBFBF9;
  overflow: hidden;
  padding: 11px 0;
  font-size: 10.5px; letter-spacing: 0.24em; text-transform: uppercase; font-weight: 500;
}
.promo-marquee-track {
  display: flex; width: max-content; white-space: nowrap;
  will-change: transform;
  transform: translate3d(0, 0, 0); /* force GPU layer — zero jank on iOS Safari */
  animation: marquee 30s linear infinite;
}
.promo-marquee:hover .promo-marquee-track { animation-play-state: paused; }
.promo-marquee-item { flex-shrink: 0; }

/* ========= best-sellers horizontal slider (ouqla full-bleed pattern) ========= */
.section-slider { padding-bottom: 0; }
.slider-controls { display: flex; align-items: center; gap: 14px; }
.slider-arrow {
  width: 42px; height: 42px; border-radius: 50%;
  border: 1px solid var(--border); background: transparent;
  color: var(--text); font-size: 16px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 220ms ease, border-color 220ms ease, opacity 220ms ease;
}
.slider-arrow:hover { background: var(--bg-alt); border-color: var(--text-muted); }
.slider-arrow:disabled { opacity: 0.3; cursor: default; }

/* Full-bleed wrapper: breaks out of the container so the last card peeks
   off the right edge, giving users a visual cue to swipe. */
.product-slider-bleed {
  overflow: hidden;
  position: relative;
  /* Pad left to align with the container, let right bleed off-screen. */
  padding-left: max(24px, calc((100vw - 1280px) / 2 + 24px));
  padding-right: 0;
}
/* Soft fade on the right edge hints there is more to scroll. */
.product-slider-bleed::after {
  content: ''; position: absolute; top: 0; right: 0; bottom: 0; width: 90px;
  background: linear-gradient(to right, transparent, var(--bg) 85%);
  pointer-events: none; z-index: 2;
}
.product-slider {
  display: flex; gap: 24px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; /* Firefox */
  padding-bottom: 8px;
  cursor: grab;
  /* Right padding inside the scroll container so the last card isn't flush */
  padding-right: 10vw;
}
.product-slider:active { cursor: grabbing; }
/* While dragging, disable scroll-snap so the drag feels smooth. */
.product-slider.is-dragging { scroll-snap-type: none; scroll-behavior: auto; cursor: grabbing; }
.product-slider.is-dragging a { pointer-events: none; }
.product-slider::-webkit-scrollbar { display: none; }
.product-slider-item {
  /* Slightly narrower so the next card peeks — signals "scroll me". */
  flex: 0 0 clamp(230px, 24vw, 300px);
  scroll-snap-align: start;
}
/* On mobile the RAIL owns horizontal swipes. The inner product-image filmstrip
   must not steal the gesture — BUT vertical page scrolling must still work when
   the finger starts on the image, so we allow both axes here (pan-x pan-y).
   The JS guard stops the inner filmstrip from capturing the drag. */
@media (hover: none), (pointer: coarse) {
  .product-slider { touch-action: pan-x pan-y; -webkit-overflow-scrolling: touch; }
  .product-slider .product-card-media { touch-action: pan-x pan-y; }
  .product-slider .card-carousel { touch-action: pan-x pan-y; }
  .product-slider .card-dots { display: none; }
}
/* "Drag / scroll to explore" hint under the rail. */
.slider-hint {
  display: flex; align-items: center; gap: 8px;
  padding: 14px 0 0 max(24px, calc((100vw - 1280px) / 2 + 24px));
  font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-muted);
}
.slider-hint .hint-line { flex: 0 0 46px; height: 1px; background: var(--border); }
@media (max-width: 700px) {
  .product-slider-bleed { padding-left: 16px; }
  /* One generous card + a clear peek of the next — feels curated, not cramped. */
  .product-slider { gap: 14px; padding-right: 20px; scroll-snap-type: x mandatory; }
  .product-slider-item { flex-basis: 70vw; scroll-snap-align: center; }
  .slider-hint { padding-left: 16px; font-size: 11px; }
  /* Swipe is the gesture on mobile — drop the redundant, cramped arrows. */
  .slider-controls .slider-arrow { display: none; }
  .section-header-row { align-items: flex-start; }
  .section-viewall { margin-top: 4px; }
}

/* ========= testimonial cards ========= */
.reviews-rating-line {
  margin-top: 12px; font-size: 13px; color: var(--text-muted);
  letter-spacing: 0.04em;
}
.reviews-rating-line .stars { color: var(--accent); letter-spacing: 2px; margin-right: 4px; }
.review-title {
  font-family: var(--font-display);
  font-size: 19px; font-weight: 500; letter-spacing: -0.01em;
  line-height: 1.25;
  margin: 4px 0 8px;
  color: var(--text);
}
.review-foot {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.review-verified {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10.5px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent); font-weight: 600;
}

/* ========= promo strip (static — the scrolling bar is the announcement bar) ========= */
.promo-strip {
  background: var(--accent); color: #fff;
  padding: 13px 24px; text-align: center;
  font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase;
  font-size: 14px;
}

/* ========= product cards & rails ========= */
.section { padding: 88px 0; }
.section-alt { background: var(--bg-alt); }
.section-flush { padding-top: 0; }
.section-header { text-align: center; margin-bottom: 48px; }
.section-header-row {
  display: flex; align-items: flex-end; justify-content: space-between;
  text-align: left; gap: 16px;
}
.section-viewall {
  font-size: 14px; font-weight: 600; color: var(--text);
  white-space: nowrap; padding-bottom: 6px;
}
.section-viewall:hover { color: var(--accent); }
@media (max-width: 600px) { .section-header-row { flex-direction: column; align-items: flex-start; } }
.section-eyebrow {
  text-transform: uppercase; letter-spacing: 0.15em; font-size: 12px;
  color: var(--accent);          /* every section label on the page was grey */
  font-weight: 600;
  margin-bottom: 8px;
}
.section-title { font-size: clamp(28px, 4vw, 42px); margin: 0; letter-spacing: -0.02em; }
.section-sub { color: var(--text-muted); margin-top: 12px; font-size: 16px; }

.rail {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 64px 36px; /* luxury = big tiles, wide air: ~1.5 rows per viewport */
}
.rail-2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 1100px) { .rail { grid-template-columns: repeat(2, 1fr); gap: 44px 24px; } }
@media (max-width: 520px)  { .rail { gap: 34px 18px; } }

/* ========= product cards =========
   Aesop/ouqla pattern: flat (no card chrome, no hover-lift), the IMAGE is the
   card. Multi-image cards are swipeable filmstrips with position dots. Meta
   is quiet — small title, lighter price, generous air. */
.product-card {
  position: relative;
  background: transparent;
  border-radius: 0;
  overflow: visible;
}
/* Product card media: the product photo (a transparent cut-out) floats on the
   page with a soft shadow; there is intentionally no visible panel/box behind
   it. overflow:hidden is still required so the translated carousel track can't
   bleed neighbouring slides outside the card — but with a transparent
   background it clips nothing visible. */
.product-card-media {
  display: block;
  position: relative;
  aspect-ratio: 1/1;
  overflow: hidden;
  /* Ken, 2026-10-31: no painted backdrop on product images. Tiles are
     transparent so cut-outs sit directly on the page background. The old warm
     stone fill + gold hairline is kept one switch away: re-add
     background: var(--bg-card) and the border line below. */
  background: transparent;
  border: none;
  border-radius: 16px;
  padding: 12px;
  transition: border-color 220ms ease, box-shadow 220ms ease;
  touch-action: pan-y;
  cursor: pointer;
}
.product-card:hover .product-card-media {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
  box-shadow: 0 14px 34px -22px color-mix(in srgb, var(--accent) 55%, transparent);
}
/* Inner image wrapper.
   IMPORTANT: overflow must stay VISIBLE here. The track is translated to
   reveal each slide, and the outer .product-card-media already clips. Putting
   overflow:hidden on the TRANSLATED track makes Chromium clip relative to the
   track's own (moved) box — which hid slides 2/3 entirely ("blank on dot 2/3"). */
.card-track {
  display: flex; height: 100%;
  border-radius: 6px;
  overflow: visible;
  transition: transform 340ms cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}
.card-slide { flex: 0 0 100%; height: 100%; overflow: visible; }
.card-slide img {
  width: 100%; height: 100%; object-fit: contain;
  user-select: none; -webkit-user-drag: none;
}
/* Single-image cards keep the quiet hover */
.product-card-media:not(.card-carousel) .card-slide img {
  transition: transform 500ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* Position dots — BARE, no pill background (Ken: the highlight reads cheap).
   White ring keeps them legible over dark photos without a container. */
.card-dots {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 12px; z-index: 4;
  display: flex; gap: 7px;
}
.card-dot {
  /* Small visible dot (as designed) with a LARGER invisible hit area supplied
     by ::before. Using a border/padding for the hit area made the dot 43px tall
     and it covered the product photo — an absolutely-positioned element cannot
     grow its box here without overlapping the image. */
  position: relative;
  width: 7px; height: 7px; box-sizing: border-box;
  border: none; padding: 0; cursor: pointer;
  border-radius: 50%;
  background: rgba(255,255,255,0.55);
  box-shadow: 0 0 0 1px rgba(0,0,0,0.18);
  transition: background 220ms ease, transform 220ms ease;
  touch-action: manipulation;
}
/* Invisible 44×44 thumb target centred on the dot — does not affect layout. */
.card-dot::before {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
}
.card-dot:hover { background: rgba(255,255,255,0.85); }
.card-dot.active {
  background: #fff;
  transform: scale(1.3);
  box-shadow: 0 0 0 1px rgba(0,0,0,0.28);
}
/* Desktop arrows — invisible until the media is hovered (ouqla pattern),
   circular, thin-stroke chevrons. Touch devices never see them. */
.card-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  z-index: 4;
  width: 38px; height: 38px; border-radius: 50%;
  border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.92);
  color: #111;
  box-shadow: 0 1px 6px rgba(0,0,0,0.18);
  opacity: 0; pointer-events: none;
  transition: opacity 220ms ease;
}
.card-arrow svg { width: 18px; height: 18px; }
.card-arrow-prev { left: 10px; }
.card-arrow-next { right: 10px; }
.card-arrow:hover { background: #fff; }
.card-arrow:disabled { opacity: 0 !important; pointer-events: none !important; }
@media (hover: hover) {
  .product-card-media:hover .card-arrow { opacity: 1; pointer-events: auto; }
}
@media (hover: none) {
  .card-arrow { display: none; }
}
.product-card-badge {
  position: absolute; top: 12px; left: 12px; z-index: 4;
  padding: 4px 10px; font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.14em;
  background: var(--sale); color: #fff;
  border-radius: var(--border-radius-sm);
}
/* Meta: quiet typography, left-aligned, air between blocks. */
.product-card-body { padding: 14px 2px 0; }
.product-card-title {
  font-size: 14px; font-weight: 500; letter-spacing: 0.01em;
  margin: 0 0 5px; display: block; color: var(--text);
  text-decoration: none;
}
.product-card-title:hover { color: var(--accent); }
.product-card-price { display: flex; align-items: center; gap: 8px; }
.price-current { font-weight: 500; font-size: 14px; }
.price-compare { color: var(--text-muted); text-decoration: line-through; font-size: 12.5px; }
.price-sale { color: var(--sale); }
.stock-low { color: #d97706; font-size: 12px; margin-top: 4px; }
.stock-out { color: var(--sale); font-size: 12px; margin-top: 4px; }

/* ========= grid sections ========= */
.our-selection {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 48px 28px; /* tighter, smaller cards — 4 across instead of 3 big ones */
}
@media (max-width: 1100px) { .our-selection { grid-template-columns: repeat(3, 1fr); gap: 40px 22px; } }
@media (max-width: 820px)  { .our-selection { grid-template-columns: repeat(2, 1fr); gap: 36px 18px; } }
@media (max-width: 520px)  { .our-selection { gap: 30px 14px; } }

.split-banner {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 0; background: var(--bg-alt);
  overflow: hidden; border-radius: var(--border-radius);
}
.split-banner-media { min-height: 400px; overflow: hidden; }
.split-banner-media img { width: 100%; height: 100%; object-fit: cover; }
.split-banner-body {
  padding: 60px; display: flex; flex-direction: column; justify-content: center;
}
.split-banner-body h3 { font-size: clamp(24px, 3vw, 36px); margin: 0 0 16px; letter-spacing: -0.02em; }
.split-banner-body p { color: var(--text-alt); margin-bottom: 24px; }
@media (max-width: 800px) {
  .split-banner { grid-template-columns: 1fr; }
  .split-banner-body { padding: 40px 24px; }
}

/* ========= reviews (luxury-elegant editorial cards) ========= */
.reviews-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px;
}
@media (max-width: 800px) { .reviews-grid { grid-template-columns: 1fr; } }
.review-card {
  position: relative;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 30px 28px 26px;
  box-shadow: var(--shadow);
  transition: transform 320ms var(--ease-out), box-shadow 320ms var(--ease-out), border-color 320ms var(--ease-out);
}
.review-card::before {
  content: "";
  position: absolute; top: 0; left: 28px; right: 28px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent) 20%, var(--accent) 80%, transparent);
  opacity: 0.55;
}
.review-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: var(--accent);
}
.review-stars {
  color: var(--accent);
  font-size: 15px; letter-spacing: 3px;
  margin-bottom: 14px;
  text-shadow: 0 1px 0 rgba(184,147,94,0.18);
}
.review-body {
  color: var(--text-alt);
  margin: 0 0 4px;
  font-size: 15px; line-height: 1.68;
}
.review-author {
  font-size: 13.5px; font-weight: 600;
  letter-spacing: 0.02em;
  display: inline-flex; align-items: center; gap: 9px;
}
.review-author::before {
  content: "";
  width: 22px; height: 1px;
  background: var(--accent);
}
.review-product { color: var(--text-muted); font-size: 13px; }
.review-product:hover { color: var(--accent); }

/* ========= reviews slider (homepage testimonials) ========= */
.reviews-slider {
  display: flex; gap: 22px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 4px 4px 12px;
  cursor: grab;
  user-select: none; -webkit-user-select: none;
}
.reviews-slider::-webkit-scrollbar { display: none; }
.reviews-slider.is-dragging { scroll-snap-type: none; scroll-behavior: auto; cursor: grabbing; }
.reviews-slider.is-dragging a { pointer-events: none; }
.review-slide {
  flex: 0 0 clamp(280px, 30vw, 380px);
  scroll-snap-align: start;
  text-decoration: none;
  color: inherit;
  display: flex;
  flex-direction: column;
  cursor: pointer;
}
.review-slide .review-body {
  flex: 1;
}
@media (hover: none), (pointer: coarse) {
  .reviews-slider { touch-action: pan-x pan-y; }
}

/* ========= newsletter ========= */
.newsletter {
  text-align: center; padding: 80px 24px;
  background: var(--bg-alt);
  border-radius: var(--border-radius);
}
.newsletter h3 { font-size: 28px; margin: 0 0 12px; }
.newsletter p { color: var(--text-alt); margin-bottom: 24px; }
.newsletter form { display: flex; gap: 8px; max-width: 480px; margin: 0 auto; }
.newsletter input {
  flex: 1; padding: 14px 18px;
  border: 1px solid var(--border); border-radius: var(--border-radius-sm);
  background: var(--bg);
}
.newsletter button { padding: 14px 28px; white-space: nowrap; }
@media (max-width: 480px) {
  .newsletter form { flex-direction: column; }
  .newsletter button { width: 100%; }
}

/* ========= footer — expanded editorial ========= */
.site-footer {
  background: #111111;
  color: #f5f5f5;
  padding: 5rem 0 2rem;
  margin-top: 80px;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 56px;
  margin-bottom: 48px;
}
@media (max-width: 900px) {
  .footer-grid { grid-template-columns: 1fr 1fr; gap: 40px; }
}
@media (max-width: 600px) {
  .footer-grid { grid-template-columns: 1fr; gap: 32px; }
}
/* Brand column */
.footer-logo {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #FBFBF9;
  margin-bottom: 12px;
}
.footer-tagline {
  font-size: 14px;
  color: rgba(255,255,255,0.55);
  line-height: 1.6;
  max-width: 32ch;
}
/* Newsletter column */
.footer-news-sub {
  font-size: 13px;
  color: rgba(255,255,255,0.5);
  margin: 0 0 16px;
  line-height: 1.5;
}
.footer-news-form {
  display: flex;
  align-items: center;
  gap: 0;
  border-bottom: 1px solid rgba(255,255,255,0.2);
  max-width: 280px;
  transition: border-color 220ms ease;
}
.footer-news-form:focus-within {
  border-color: rgba(255,255,255,0.5);
}
.footer-news-form input {
  flex: 1;
  background: transparent;
  border: none;
  color: #f5f5f5;
  font-size: 14px;
  padding: 10px 0;
  outline: none;
}
.footer-news-form input::placeholder { color: rgba(255,255,255,0.35); }
.footer-news-form button {
  background: transparent;
  border: none;
  color: rgba(255,255,255,0.6);
  cursor: pointer;
  padding: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 200ms ease, transform 200ms var(--ease-out);
}
.footer-news-form button:hover {
  color: #fff;
  transform: translateX(3px);
}
/* Link columns */
.site-footer h4 {
  margin: 0 0 18px;
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.4);
  font-weight: 500;
}

.site-footer .footer-col a, .site-footer .footer-newsletter + .footer-col a { display: block;
  color: rgba(255,255,255,0.7);
  margin-bottom: 10px;
  font-size: 14px;
  text-decoration: none;
  letter-spacing: 0.01em;
  line-height: 1.4;
  transition: color var(--transition); }
.site-footer a:hover { color: #fff !important; }
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  padding-top: 28px;
  border-top: 1px solid rgba(255,255,255,0.1);
  font-size: 12px;
  color: rgba(255,255,255,0.4);
  gap: 16px;
  letter-spacing: 0.04em;
}
.footer-payments { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.footer-pay-label { font-size: 11px; color: rgba(255,255,255,0.35); text-transform: uppercase; letter-spacing: 0.1em; }
.qris-badge {
  display: inline-block; height: 26px; width: auto;
  background: #fff; border-radius: 4px; padding: 1px 2px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}

/* ========= PDP ========= */
/* Comfortable, ouqla-style product layout: a media column (vertical thumbs)
   beside a restrained main image. Previously the image felt oversized because
   the gallery column was wide (1.2fr) AND the box was a tall 4/5. */
.pdp-layout {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 56px;
  margin: 40px 0;
  align-items: start;
}
@media (max-width: 900px) { .pdp-layout { grid-template-columns: 1fr; gap: 28px; } }

/* Gallery = media column (thumbs) + main stage */
.pdp-gallery { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 16px; align-items: start; }
.pdp-gallery--single { grid-template-columns: 1fr; }

/* Ken, 2026-10-31: no painted stage behind the product image. */
.pdp-gallery-main {
  aspect-ratio: 1/1; background: transparent;
  border-radius: var(--border-radius);
  overflow: hidden;
  position: relative;
  touch-action: pan-y;
  max-height: 62vh; /* keep it from dominating tall viewports */
}
.pdp-gallery-main img { width: 100%; height: 100%; object-fit: contain; }

/* Vertical media strip (left of the main image) */
.pdp-gallery-thumbs {
  display: flex; flex-direction: column; gap: 10px;
  overflow-y: auto; max-height: 62vh;
  scrollbar-width: thin;
}
.pdp-gallery-thumbs button {
  flex: 0 0 auto; width: 76px; height: 76px;
  border: 1px solid var(--border); border-radius: var(--border-radius-sm);
  overflow: hidden; opacity: 0.55; padding: 0; background: var(--bg-alt);
  transition: opacity var(--transition), border-color var(--transition);
}
.pdp-gallery-thumbs button.active { opacity: 1; border-color: var(--text); }
.pdp-gallery-thumbs img { width: 100%; height: 100%; object-fit: contain; }

/* Mobile: main image on top, thumbnails as a horizontal strip underneath.
   (Must come AFTER the base rules above so it actually wins.) */
@media (max-width: 900px) {
  .pdp-gallery { grid-template-columns: 1fr; }
  .pdp-gallery-thumbs {
    order: 2; flex-direction: row;
    max-height: none; overflow-y: visible; overflow-x: auto;
    margin-top: 4px;
  }
  .pdp-gallery-thumbs button { flex: 0 0 auto; width: 64px; height: 64px; }
}

/* "1 / 3" media counter, top-left of the main image (ouqla-style label). */
.pdp-media-count {
  position: absolute; top: 12px; left: 12px; z-index: 3;
  font-size: 12px; letter-spacing: .06em; color: var(--text-alt);
  background: color-mix(in srgb, var(--bg-elevated) 78%, transparent);
  border: 1px solid var(--border);
  border-radius: 999px; padding: 3px 10px;
  backdrop-filter: blur(6px);
}
.pdp-media-count b { font-weight: 600; color: var(--text); }

.pdp-info .breadcrumb, .breadcrumb {
  font-size: 13px; color: var(--text-muted); margin: 32px 0 12px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
}
.breadcrumb a { color: var(--text-muted); }
.breadcrumb a:hover { color: var(--text); }
.breadcrumb .crumb-sep { color: var(--text-muted); opacity: 0.6; }
.breadcrumb .crumb-current { color: var(--text); font-weight: 600; }
.pdp-title { font-size: clamp(22px, 2.4vw, 28px); margin: 0 0 10px; letter-spacing: -0.01em; font-weight: 500; }
.pdp-info h1 { font-size: clamp(22px, 2.4vw, 28px); margin: 0 0 10px; letter-spacing: -0.01em; font-weight: 500; }
.pdp-rating { display: inline-flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; color: var(--text-alt); }
.pdp-rating .stars { color: #fbbf24; letter-spacing: 2px; }
.pdp-price { font-size: 24px; font-weight: 700; margin-bottom: 14px; display: flex; align-items: baseline; gap: 10px; }
.pdp-price .price-compare { font-size: 16px; font-weight: 400; }

/* Colour cross-link ("Available in: ⬤ Onyx · ⬤ Cognac") — links sibling
   products that share the same family title. Read-only; never mutates cart. */
.pdp-colors {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 10px; margin: 2px 0 18px;
  font-size: 13.5px;
}
.pdp-colors-label { color: var(--text-muted); letter-spacing: 0.04em; }
.pdp-colors-links { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.pdp-color-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px 6px 7px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--chip-bg); color: var(--text);
  text-decoration: none; line-height: 1;
  transition: border-color 200ms ease, background 200ms ease, transform 200ms ease;
}
a.pdp-color-chip:hover { border-color: var(--text); transform: translateY(-1px); }
.pdp-color-chip img { width: 22px; height: 22px; border-radius: 50%; object-fit: contain; background: var(--bg-alt); }

/* Wear-format switch (Wrist ⇄ Arm band) — a clear, premium segmented control. */
.pdp-wear { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 2px 0 18px; font-size: 13.5px; }
.pdp-wear-label { color: var(--text-muted); letter-spacing: 0.04em; }
.pdp-wear-chips { display: inline-flex; align-items: center; gap: 6px; padding: 3px; background: var(--bg-alt); border-radius: 999px; border: 1px solid var(--border); }
.pdp-wear-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 16px; border-radius: 999px; text-decoration: none;
  color: var(--text); line-height: 1; font-weight: 500;
  transition: background 200ms ease, color 200ms ease;
}
.pdp-wear-chip.is-current { background: var(--text); color: var(--bg); }
a.pdp-wear-chip:hover { background: rgba(0,0,0,.06); }
.pdp-wear-oos { font-size: 11px; color: var(--text-muted); font-style: normal; }
@media (max-width: 480px) { .pdp-wear-chip { padding: 7px 13px; font-size: 13px; } }

/* Collection wear-format filter chips (Wrist / Arm / All). */
.wear-filter { display: flex; gap: 8px; flex-wrap: wrap; margin: 4px 0 22px; }
.wear-chip {
  padding: 7px 16px; border-radius: 999px; border: 1px solid var(--border);
  text-decoration: none; color: var(--text); font-size: 13.5px; font-weight: 500;
  transition: background 180ms ease, border-color 180ms ease, color 180ms ease;
}
.wear-chip:hover { border-color: var(--text); }
.wear-chip.active { background: var(--text); color: var(--bg); border-color: var(--text); }
.pdp-color-chip.is-current { border-color: var(--text); background: var(--bg-alt); cursor: default; }
/* The current product has no image chip — draw a small onyx/cognac dot instead. */
.pdp-color-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--text); opacity: 0.75; margin-left: 5px; }
.pdp-color-oos { font-style: normal; font-size: 11px; color: var(--text-muted); }

/* feature badges row (editable free text per product) */
.pdp-badges { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
.pdp-badge {
  padding: 5px 12px; font-size: 12px; font-weight: 600;
  background: var(--chip-bg); color: var(--text-alt);
  border-radius: 999px; letter-spacing: 0.02em;
}

/* stock status line + social proof */
.stock-line { margin-bottom: 8px; font-size: 14px; }
.stock-line .stock-in { color: var(--success); }
.pdp-sold { font-size: 13px; color: var(--accent); font-weight: 600; margin-bottom: 18px; }
.pdp-oos-note { font-size: 13px; color: var(--sale); margin: 8px 0 0; }
.pdp-section { margin-top: 80px; }

/* full-screen lightbox for the gallery */
.pdp-gallery-main { cursor: default; }
.pdp-lightbox {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0,0,0,0.92);
  display: none; align-items: center; justify-content: center;
  padding: 40px;
}
.pdp-lightbox.open { display: flex; }
.pdp-lightbox img {
  max-width: 92vw; max-height: 92vh; object-fit: contain;
  border-radius: 6px; cursor: zoom-out;
}
.pdp-lightbox-close {
  position: absolute; top: 20px; right: 26px;
  width: 44px; height: 44px; border-radius: 50%;
  background: rgba(255,255,255,0.12); color: #fff;
  border: none; font-size: 26px; line-height: 1; cursor: pointer;
}
.pdp-lightbox-close:hover { background: rgba(255,255,255,0.25); }
.pdp-lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 46px; height: 46px; border-radius: 50%;
  background: rgba(255,255,255,0.1); color: #fff; cursor: pointer;
  border: none; display: flex; align-items: center; justify-content: center;
}
.pdp-lightbox-nav:hover { background: rgba(255,255,255,0.22); }
.pdp-lightbox-nav svg { width: 22px; height: 22px; }
.pdp-lightbox-prev { left: 22px; }
.pdp-lightbox-next { right: 22px; }

/* Gallery carousel arrows (inside the main image stage).
   Always visible — hover-reveal made them undiscoverable (ouqla-style). */
.gallery-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 38px; height: 38px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--bg-elevated) 88%, transparent);
  color: var(--text); border: 1px solid var(--border);
  cursor: pointer; z-index: 3;
  transition: background var(--transition);
  backdrop-filter: blur(4px);
}
.gallery-arrow svg { width: 18px; height: 18px; }
.gallery-arrow:hover { background: var(--bg-elevated); }
.gallery-arrow-prev { left: 14px; }
.gallery-arrow-next { right: 14px; }
@media (max-width: 480px) {
  .gallery-arrow { width: 32px; height: 32px; }
  .gallery-arrow svg { width: 15px; height: 15px; }
  .gallery-arrow-prev { left: 8px; }
  .gallery-arrow-next { right: 8px; }
}

.qty-selector {
  display: inline-flex; border: 1px solid var(--border); border-radius: var(--border-radius-sm);
  margin-bottom: 24px;
}
.qty-selector button, .qty-selector input {
  width: 44px; height: 48px; border: none; background: transparent;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 18px;
}
.qty-selector input { width: 60px; text-align: center; font-size: 16px; }

.add-to-cart-btn {
  width: 100%; padding: 18px; font-size: 15px;
  margin-bottom: 20px;
}
/* Trust badges — premium treatment: hairline rule, thin single-weight line
   SVG icons in the accent color, uppercase micro-labels with letterspacing.
   Restrained on purpose (no boxes, no color chips, no emoji). */
.trust-badges {
  display: grid; grid-template-columns: repeat(3,1fr);
  padding: 18px 0; margin: 24px 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.trust-badge {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  text-align: center; padding: 0 8px;
}
.trust-badge + .trust-badge { border-left: 1px solid var(--border); }
.trust-icon {
  width: 22px; height: 22px; flex: 0 0 auto;
  color: var(--accent); /* thin line icon picks up the site accent */
}
.trust-badge strong {
  font-weight: 600; font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-alt); line-height: 1.4;
}
@media (max-width: 480px) {
  .trust-badges { grid-template-columns: 1fr; gap: 14px; }
  .trust-badge { flex-direction: row; justify-content: flex-start; text-align: left; gap: 12px; }
  .trust-badge + .trust-badge { border-left: none; border-top: 1px solid var(--border); padding-top: 14px; }
}

.accordion { border-top: 1px solid var(--border); margin-top: 32px; }
.accordion-item { border-bottom: 1px solid var(--border); }
.accordion-trigger {
  width: 100%; padding: 18px 0;
  display: flex; justify-content: space-between; align-items: center;
  font-weight: 600; text-align: left; background: transparent;
  border: none;
}
.accordion-trigger::after {
  content: '+'; font-size: 20px; font-weight: 400; color: var(--text-muted);
  transition: transform var(--transition);
}
.accordion-item.open .accordion-trigger::after { transform: rotate(45deg); }
.accordion-body { padding: 0 0 18px; color: var(--text-alt); display: none; }
.accordion-item.open .accordion-body { display: block; }

/* ========= collection pages ========= */
.collection-banner {
  text-align: center; padding: 80px 24px;
  background: var(--bg-alt);
  margin-bottom: 40px;
}
.collection-banner h1 { font-size: clamp(32px, 5vw, 52px); margin: 0 0 12px; }
.collection-banner p { color: var(--text-alt); max-width: 600px; margin: 0 auto; }

.collection-toolbar {
  display: flex; justify-content: space-between; align-items: center;
  padding: 16px 0; border-bottom: 1px solid var(--border);
  margin-bottom: 32px; flex-wrap: wrap; gap: 12px;
}
.collection-filters { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.collection-filters select {
  padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--border-radius-sm);
  background: var(--bg); font-size: 14px;
}

/* ========= policy / pages ========= */
.page-body {
  max-width: 720px; margin: 0 auto; padding: 60px 24px;
}
.page-body h1 { margin-bottom: 32px; }
.page-body h2 { margin-top: 32px; }
.page-body p { color: var(--text-alt); line-height: 1.7; }

/* ========= forms ========= */
/* PDP dual CTA: add-to-cart + buy-now side by side */
.pdp-cta-row { display: flex; gap: 10px; align-items: stretch; }
.pdp-cta-row .add-to-cart-btn { flex: 1.2; }
.pdp-cta-row .buy-now-btn { flex: 1; white-space: nowrap; }
@media (max-width: 480px) {
  .pdp-cta-row { flex-wrap: wrap; }
  .pdp-cta-row .add-to-cart-btn, .pdp-cta-row .buy-now-btn { flex: 1 1 100%; }
}
/* courier options skeleton (checkout) - shimmer while live rates load */
.skeleton-card {
  height: 58px; border: 1px solid var(--border); border-radius: var(--border-radius-sm);
  background: linear-gradient(100deg, rgba(0,0,0,.04) 40%, rgba(0,0,0,.08) 50%, rgba(0,0,0,.04) 60%);
  background-size: 200% 100%;
  animation: skel 1.2s linear infinite;
  margin-bottom: 8px;
}
@keyframes skel { to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton-card { animation: none; } }
/* searchable location picker (checkout) */
.loc-wrap { position: relative; }
.loc-search {
  margin-bottom: 8px;
  background: var(--bg-elev, #fff);
}
.loc-search:focus { border-color: var(--accent); }
.loc-empty {
  padding: 8px 12px; font-size: 13px; color: var(--text-muted);
}
.form-input, .form-select, .form-textarea {
  width: 100%; padding: 12px 14px;
  border: 1px solid var(--border);
  background: var(--bg); border-radius: var(--border-radius-sm);
  font-size: 15px;
  transition: border-color var(--transition);
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
  outline: none; border-color: var(--accent);
}
.form-label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.form-field { margin-bottom: 16px; }

/* ========= misc helpers ========= */
.notice {
  padding: 14px 18px; border-radius: var(--border-radius-sm);
  background: var(--bg-alt); margin: 12px 0;
  border-left: 3px solid var(--accent);
}
.notice.success { border-left-color: var(--success); }
.notice.error { border-left-color: var(--sale); }

.empty-state {
  text-align: center; padding: 80px 24px;
}
.empty-state h2 { margin-bottom: 12px; }
.empty-state p { color: var(--text-muted); margin-bottom: 24px; }

/* Toast */
.toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  background: var(--text); color: var(--bg);
  padding: 14px 24px; border-radius: var(--border-radius-sm);
  font-size: 14px; z-index: 400;
  box-shadow: var(--shadow-lg);
  animation: toastIn 300ms ease;
}
@keyframes toastIn {
  from { opacity: 0; transform: translate(-50%, 10px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

/* ========= payment method picker ========= */
.pay-methods { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
.pay-method {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--border-radius-sm);
  cursor: pointer; transition: border-color 150ms, background 150ms;
}
.pay-method:hover { border-color: var(--accent); }
.pay-method-active { border-color: var(--accent); background: var(--bg-alt); }
.pay-method input[type=radio] { margin-top: 3px; accent-color: var(--accent); width: 16px; height: 16px; }
.pay-method-body { display: flex; flex-direction: column; }
.pay-method-name { font-weight: 600; }
.pay-method-desc { color: var(--text-muted); font-size: 13px; }


/* ============================================================
   Part 3 — motion & polish (all GPU-cheap: transform/opacity only)
   ============================================================ */

/* ---------- scroll-reveal ---------- */
.reveal {
  opacity: 0;
  transform: translateY(var(--reveal-shift));
  transition: opacity 560ms var(--ease-out), transform 560ms var(--ease-out);
  will-change: opacity, transform;
}
.reveal.in { opacity: 1; transform: none; }
/* Stagger children of grids/rails so they don't all pop at once. */
.reveal-stagger > * { transition-delay: calc(var(--i, 0) * 70ms); }

/* ---------- image skeleton (no blank flash / layout shift) ---------- */
.img-skeleton {
  position: relative;
  background: linear-gradient(100deg,
     var(--bg-alt) 30%, color-mix(in srgb, var(--bg-alt) 60%, var(--border)) 50%, var(--bg-alt) 70%);
  background-size: 220% 100%;
  animation: skeleton 1.3s ease-in-out infinite;
}
@keyframes skeleton { from { background-position: 120% 0; } to { background-position: -120% 0; } }
.img-skeleton img { opacity: 0; transition: opacity 400ms ease; }
/* loaded = placeholder's job done; transparent so backdrop-less cut-outs sit
   on the page (Ken, 2026-10-31). */
.img-skeleton.loaded { animation: none; background: transparent; }
.img-skeleton.loaded img { opacity: 1; }

/* ---------- add-to-cart micro-interaction ---------- */
.add-to-cart-btn { position: relative; overflow: hidden; }
.add-to-cart-btn.did-add { animation: atcPop 480ms var(--ease-out); }
@keyframes atcPop {
  0%   { transform: scale(1); }
  30%  { transform: scale(0.955); }
  60%  { transform: scale(1.02); }
  100% { transform: scale(1); }
}
.add-to-cart-btn .atc-check {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--success); color: #fff;
  opacity: 0; transform: translateY(6px);
  transition: opacity 180ms ease, transform 180ms ease;
  pointer-events: none;
}
.add-to-cart-btn.show-check .atc-check { opacity: 1; transform: none; }

/* ---------- PDP gallery: slide-and-follow carousel ---------- */
/* The track is a filmstrip of slides; JS translates it to the active slide.
   Drag moves it 1:1 with the cursor; release snaps to a neighbour or back. */
.pdp-track {
  position: absolute; inset: 0;
  display: flex;
  height: 100%;
  transition: transform 320ms var(--ease-out);
  will-change: transform;
  cursor: grab;
}
.pdp-track:active { cursor: grabbing; }
.pdp-slide {
  flex: 0 0 100%;
  height: 100%;
}
.pdp-slide img {
  width: 100%; height: 100%; object-fit: contain;
  user-select: none; -webkit-user-drag: none;
}

/* Position dots (which image am I on). Carried on a soft light pill so they
   stay visible over dark placeholder art and light product photos. */
.pdp-dots {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 12px;
  display: flex; gap: 7px;
  padding: 6px 10px; border-radius: 999px;
  background: color-mix(in srgb, var(--bg-elevated) 72%, transparent);
  backdrop-filter: blur(6px);
  border: 1px solid var(--border);
  z-index: 3;
}
.pdp-dot {
  width: 8px; height: 8px; border-radius: 50%;
  border: none; padding: 0; cursor: pointer;
  background: color-mix(in srgb, var(--text) 26%, transparent);
  transition: background var(--transition), transform var(--transition);
}
.pdp-dot:hover { background: color-mix(in srgb, var(--text) 55%, transparent); }
.pdp-dot.active {
  background: var(--text);
  transform: scale(1.25);
}

/* ---------- mobile sticky add-to-cart bar (PDP) ---------- */
.mobile-atc-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
  display: none;
  align-items: center; gap: 12px;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
  background: color-mix(in srgb, var(--bg) 96%, transparent);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--border);
  transform: translateY(110%);
  transition: transform 260ms var(--ease-out);
}
.mobile-atc-bar.show { transform: none; }
.mobile-atc-bar .mab-info { flex: 1; min-width: 0; }
.mobile-atc-bar .mab-name {
  font-family: var(--font-display); font-weight: 600; font-size: 14px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mobile-atc-bar .mab-price { font-size: 13px; color: var(--text-alt); }
.mobile-atc-bar .btn { padding: 12px 20px; font-size: 13px; }
@media (max-width: 900px) {
  .mobile-atc-bar { display: flex; }
}
/* When the sticky bar is visible it would cover the page bottom (footer copy
   was unreachable on mobile). Reserve exactly its height on PDPs. */
@media (max-width: 900px) {
  body.pdp { padding-bottom: 96px; }
  /* Ensure the inline buy box / trust notes aren't hidden behind the sticky bar. */
  .pdp-info { scroll-margin-bottom: 96px; }
}

/* ---------- misc motion ---------- */
.footer-payments .qris-badge { transition: transform 200ms var(--ease-out); }
.footer-payments .qris-badge:hover { transform: scale(1.05); }

/* ---------- language switcher (EN/ID) ---------- */
.lang-switch {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-right: 4px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
  border: 1px solid var(--border, rgba(128,128,128,.3));
  border-radius: 999px;
  padding: 2px;
}
.lang-switch a {
  padding: 3px 8px;
  border-radius: 999px;
  color: var(--text-muted, #888);
  text-decoration: none;
  transition: background 160ms ease, color 160ms ease;
}
.lang-switch a.on { background: var(--text, #111); color: var(--bg, #fff); }
.lang-switch a:hover { color: var(--text, #111); }
/* On small screens keep the switcher available in the header rather than
   hiding it (Ken uses ID/EN on mobile). Slightly tighter so it fits beside
   the header icons. */
@media (max-width: 640px) {
  .lang-switch { display: inline-flex; margin-right: 2px; font-size: 11px; padding: 1px; }
  .lang-switch a { padding: 3px 6px; }
}
@media (max-width: 380px) {
  .lang-switch { display: none; } /* ultra-narrow: rely on the drawer's switcher */
}

/* Header can't overflow on phones: lang switch (57px) + 5 icons used to total
   ~262px and stick 19px past a 375px viewport, giving the whole page a
   horizontal scrollbar. Icons shrink slightly and the header switcher yields
   to the drawer's copy on narrow screens (drawer always has it). */
@media (max-width: 480px) {
  .header-icon, .hamburger { width: 38px; height: 38px; }
  .header-actions { gap: 0; }
}
@media (max-width: 430px) {
  .lang-switch { display: none; } /* rely on the drawer's switcher */
}

/* Language switcher inside the mobile nav drawer. */
.mobile-nav-lang {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border, rgba(128,128,128,.25));
  font-size: 14px;
}
.mobile-nav-lang .mnl-label { color: var(--text-muted, #888); margin-right: auto; font-size: 13px; }
.mobile-nav-lang a {
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--border, rgba(128,128,128,.3));
  color: var(--text-muted, #888);
  text-decoration: none;
  font-weight: 600;
  letter-spacing: .04em;
}
.mobile-nav-lang a.on { background: var(--text, #111); color: var(--bg, #fff); border-color: var(--text, #111); }

/* ---------- customer support chat widget ---------- */
.chat-fab {
  position: fixed; right: 20px; bottom: 20px; z-index: 900;
  width: 56px; height: 56px; border-radius: 50%;
  background: var(--text, #111); color: var(--bg, #fff);
  border: none; cursor: pointer; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 24px rgba(0,0,0,.28);
  transition: transform 200ms var(--ease-out, ease), box-shadow 200ms ease;
}
.chat-fab:hover { transform: translateY(-3px) scale(1.04); }
.chat-dot { position: absolute; top: 8px; right: 8px; width: 11px; height: 11px; border-radius: 50%; background: #e11d48; border: 2px solid var(--bg, #fff); }
.chat-panel {
  position: fixed; right: 20px; bottom: 88px; z-index: 901;
  width: 340px; max-width: calc(100vw - 32px); height: 440px; max-height: calc(100vh - 140px);
  background: var(--bg, #fff); color: var(--text, #111);
  border: 1px solid var(--border, rgba(128,128,128,.25)); border-radius: 16px;
  box-shadow: 0 18px 50px rgba(0,0,0,.28);
  display: flex; flex-direction: column; overflow: hidden;
  opacity: 0; transform: translateY(12px) scale(.98); pointer-events: none;
  transition: opacity 220ms ease, transform 220ms var(--ease-out, ease);
}
.chat-panel.open { opacity: 1; transform: none; pointer-events: auto; }
.chat-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--border, rgba(128,128,128,.2)); font-size: 14px; }
.chat-head button { background: none; border: none; font-size: 22px; line-height: 1; cursor: pointer; color: inherit; }
.chat-body { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
.chat-msg { max-width: 82%; padding: 9px 12px; border-radius: 14px; font-size: 14px; line-height: 1.45; white-space: pre-wrap; word-wrap: break-word; }
.chat-msg.customer { align-self: flex-end; background: var(--text, #111); color: var(--bg, #fff); border-bottom-right-radius: 4px; }
.chat-msg.admin { align-self: flex-start; background: var(--bg-alt, #f2f2f2); color: var(--text, #111); border-bottom-left-radius: 4px; }
.chat-msg .chat-time { display: block; font-size: 10.5px; opacity: .6; margin-top: 3px; }
.chat-form { display: flex; gap: 8px; padding: 10px; border-top: 1px solid var(--border, rgba(128,128,128,.2)); }
.chat-form input { flex: 1; padding: 10px 12px; border: 1px solid var(--border, rgba(128,128,128,.3)); border-radius: 999px; background: var(--bg, #fff); color: inherit; font-size: 14px; }
.chat-form button { width: 40px; height: 40px; border-radius: 50%; border: none; background: var(--text, #111); color: var(--bg, #fff); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.chat-signin, .chat-loading { padding: 22px 16px; font-size: 14px; color: var(--text-muted, #888); text-align: center; line-height: 1.6; }
.chat-signin a { color: inherit; font-weight: 600; text-underline-offset: 2px; }
@media (max-width: 480px) { .chat-panel { right: 10px; bottom: 80px; } .chat-fab { right: 14px; bottom: 14px; } }
/* Keep the floating chat button from covering text on phones: give the page a
   little breathing room at the bottom and shrink the fab slightly. */
@media (max-width: 700px) {
  body { padding-bottom: 8px; }
  /* Lift the chat fab above the mobile tab bar so it doesn't cover a tab. */
  .chat-fab { width: 48px; height: 48px; right: 14px; bottom: 76px; }
  .chat-panel { bottom: 140px; }
  .value-strip, .site-footer { scroll-margin-bottom: 64px; }
}

/* ---------- otofly-style motion: page fade, scroll reveal, nav ---------- */
/* Page fade-in on load */
/* Page fade-in on load — OPACITY ONLY. A transform here would make <body> the
   containing block and break position:fixed (chat button, sticky header). */
body { animation: pageFade 420ms ease both; }
@keyframes pageFade { from { opacity: 0; } to { opacity: 1; } }

/* Scroll reveal — elements with .reveal fade/slide in when scrolled into view */
.reveal { opacity: 0; transform: translateY(26px); transition: opacity 640ms var(--ease-out, cubic-bezier(.16,1,.3,1)), transform 640ms var(--ease-out, cubic-bezier(.16,1,.3,1)); will-change: opacity, transform; }
.reveal.in { opacity: 1; transform: none; }
.reveal-delay-1 { transition-delay: 80ms; }
.reveal-delay-2 { transition-delay: 160ms; }
.reveal-delay-3 { transition-delay: 240ms; }
.reveal-delay-4 { transition-delay: 320ms; }

/* Category cards keep a soft lift. Product cards are handled by the
   borderless product-media treatment further down — putting a rectangular
   box-shadow on .product-card here created a hard-edged box that clashed with
   the product's soft shadow ("two boxes"). */
.category-card { transition: transform 260ms var(--ease-out, ease), box-shadow 260ms ease; }
.category-card:hover { transform: translateY(-5px); box-shadow: 0 16px 38px rgba(0,0,0,.14); }

/* Buttons: subtle press + hover */
.btn { transition: transform 160ms ease, background 200ms ease, color 200ms ease, box-shadow 200ms ease; }
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0) scale(.985); }

/* Nav links: animated underline */
.main-nav a { position: relative; }
.main-nav a::after { content: ''; position: absolute; left: 0; right: 100%; bottom: -3px; height: 1.5px; background: currentColor; transition: right 260ms var(--ease-out, ease); }
.main-nav a:hover::after { right: 0; }

/* Back/forward ("faded reverse") — applied by JS on pageshow from bfcache */
body.page-unload { opacity: 0; transition: opacity 220ms ease; }

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  body, .reveal, .product-card, .product-card img, .btn, .main-nav a::after { animation: none !important; transition: none !important; }
  .reveal { opacity: 1; transform: none; }
}

/* =====================================================================
   ELIOS — VISUAL REFINEMENT LAYER (Tier 1 + Tier 2)
   Appended overrides; safe to tweak/remove without touching the base sheet.
   ===================================================================== */

/* ---------- T1.1 Fail-safe reveal: only hide content when JS is on ---------- */
html:not(.js-enabled) .reveal { opacity: 1 !important; transform: none !important; }

/* ---------- T1.2 Hero upgrade ---------- */
/* Product photos have a light/white studio background. Rather than fight them,
   give the frame an INTENTIONAL soft "studio card" look: generous radius, a
   warm inner light, and a lifted shadow so the white reads as designed. */
.hero-spot-media {
  /* NO plate at all (Ken, 2026-10-30: "pls dont add the white bg on the hero image").
     The product photos are transparent cut-outs, so anything behind them - a white
     card, a gold gradient, a blurred glow - reads as a box or a haze around the
     product. Transparent + the page's own background is the only clean answer. */
  background: transparent;
  border-radius: 18px;
  padding: clamp(18px, 3vw, 34px);
  box-shadow: 0 18px 40px -28px rgba(0,0,0,.35);   /* soft, no hairline edge */
  overflow: hidden;
}
/* Inner "studio backdrop" glow REMOVED. It was a 55% white radial over the top of
   the frame, which is exactly the pale band across the top of slide 1. With a
   transparent product photo there is nothing for it to light. */
.hero-spot-media::after { content: none; }
/* Slightly larger, smoother image inside the frame */
.hero-spot-media img { border-radius: 14px; }
/* Product photos are background-free cut-outs, so they sit directly on the
   warm studio card — no blend needed (multiply would tint them). */
.hero-spot-media img { mix-blend-mode: normal; }
/* Glow behind the hero carousel REMOVED (Ken, 2026-10-30: "the hero image ...
   theres like a light? whats that remove"). It was a blurred gold radial sitting
   behind a lightened panel, which lifted the photo's surroundings and made the
   whole hero read washed out. No pseudo-element, no blur, no tint - the photo now
   sits on the page's own background. */
.hero-copy h1 { letter-spacing: -0.015em; }

/* ---------- T1.3 Warm gold accent + calmer sale treatment ---------- */
/* Sale badge: warm amber, not alarm red. Softer, smaller, more editorial. */
.product-card-badge {
  background: var(--accent);
  color: #1a1a1a;
  font-weight: 700; letter-spacing: .16em;
  box-shadow: 0 4px 14px -6px rgba(0,0,0,.5);
}
/* Sale price reads as the LIVE price, not a warning. */
.price-sale { color: var(--accent); }
.price-compare { font-size: 12px; opacity: .75; }
/* Out-of-stock stays red (it IS a warning) but muted. */
.stock-out { color: var(--sale); }

/* ---------- T1.4 (revised) Slightly tighter grid for a 4-up rhythm ---------- */
.our-selection { gap: 48px 28px; }
@media (max-width: 1100px) { .our-selection { gap: 40px 22px; } }
/* (product-card-body / -title / price-current are finalised in the consolidated
   product-card section further down — not re-declared here.) */
.price-compare { font-size: 12.5px; }

/* ---------- T2.5 Hero parallax ---------- */
.hero-spotlight { will-change: transform; }
@media (prefers-reduced-motion: no-preference) {
  .hero-grid { --px: 0px; }
}

/* ---------- T2.7 Micro-interactions ---------- */
/* Magnetic buttons (JS sets transform inline) */
.btn, .hero-cta-btn { will-change: transform; }
/* Cursor-follow glow on the hero REMOVED (Ken, 2026-10-30: "the cursor animation
   thingy just remove"). No ::after, no --gx/--gy, no transition. The JS that set
   those variables is gone too. */
.hero-grid { position: relative; }

/* ---------- Product card image: no hard box, soft spotlight ----------
   The product photos are transparent cut-outs, so they float directly on the
   page. We deliberately do NOT draw a light panel behind them: a solid panel
   ended in a hard edge against the dark page and read cheap. Instead a soft,
   edge-less radial glow gives depth, and the product keeps its own diffuse
   shadow. Result: the item sits *on* the page, luxury-style. */
.product-card-media {
  background: transparent !important;
  border-radius: 16px;
  padding: 12px;
  box-shadow: none !important;
}
/* Soft, feathered spotlight behind the product — fades to nothing (no border).
   A subtle pool of light under the item gives the card depth. */
.product-card-media::before {
  content: '';
  position: absolute; inset: 4%;
  border-radius: 50%;
  background: radial-gradient(closest-side,
    color-mix(in srgb, #fff 10%, transparent), transparent 72%);
  opacity: 0.55;
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out);
  pointer-events: none;
  z-index: 0;
}
.product-card-media .card-track { position: relative; z-index: 1; }
/* Images are transparent cut-outs — no blend needed (multiply would tint them). */

.product-card-media .card-slide img, .product-card-media .card-track img { mix-blend-mode: normal; }
/* Hover: gentle lift + a brighter, slightly larger pool of light. No box, no
   tilt, no hard edge — the item simply "lifts" off the surface. */
.product-card:hover .product-card-media { transform: translateY(-4px); }
.product-card:hover .product-card-media::before { opacity: 0.85; transform: scale(1.05); }
.product-card-media { transition: transform 340ms var(--ease-out); }
/* Image zoom stays very subtle */
/* Ken: hover zoom off */ .product-card:hover .card-slide img { transform: none; }
.product-card-media .card-slide img {
  filter: drop-shadow(0 10px 14px rgba(0,0,0,.22));
  transition: transform 460ms var(--ease-out);
}
.product-card:hover .product-card-media .card-slide img { filter: drop-shadow(0 14px 18px rgba(0,0,0,.3)); }



/* ---------- T2.8 Category tiles (luxury rebuild) ----------
   Products are shot on plain white with lots of negative space, so a raw
   `object-fit: cover` produced a mostly-white tile and invisible white labels.
   Instead each tile gets a warm stone frame, the product FLOATS (contained,
   centred), and the label sits in a solid bar so it is always legible. */
.cat-tiles {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px;
  max-width: var(--page-max, 1240px); margin: 0 auto;
}
/* Adapt to how many categories exist so they never leave a lopsided gap. */
.cat-tiles[data-count="3"] { grid-template-columns: repeat(3, 1fr); max-width: 1000px; }
.cat-tiles[data-count="2"] { grid-template-columns: repeat(2, 1fr); max-width: 680px; }
.cat-tiles[data-count="1"] { grid-template-columns: 1fr; max-width: 360px; }
.cat-tile {
  position: relative; display: flex; flex-direction: column;
  overflow: hidden; border-radius: 16px; aspect-ratio: 4/5;
  text-decoration: none; isolation: isolate;
  background: #F0EBE1; /* single soft warm stone — light, distinct from the page white */
  border: 1px solid #E7E0D3;
  box-shadow: 0 2px 8px rgba(90,70,38,.05);
  transition: transform 420ms var(--ease-out), box-shadow 420ms ease, border-color 300ms ease;
}
/* Deepen slightly on hover so the panel responds without changing hue. */
.cat-tile:hover {
  transform: translateY(-5px);
  box-shadow: 0 18px 32px rgba(90,70,38,.14);
  border-color: #DED4C2;
}
/* Product floats, contained (never cropped, never fills the whole tile). */
/* Each rotating image lives inside its own <picture> (WebP source + <img>).
   The <picture> must be the absolutely-positioned, cross-faded layer — if only
   the inner <img> is positioned the <picture> boxes stack in normal flow and
   every photo paints on top of the others ("two Fitbits at once"). */
.cat-tile picture {
  position: absolute; inset: 8% 6% 22% 6%;
  width: 88%; height: 70%;
  opacity: 0; z-index: 1;
  transition: opacity 600ms var(--ease-out);
}
.cat-tile picture.is-active { opacity: 1; }
.cat-tile picture img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 14px 18px rgba(17,17,17,.14));
  transition: transform 620ms var(--ease-out), filter 420ms ease;
}
/* Tile with no <picture> (plain <img> child) keeps working too. */
.cat-tile > img {
  position: absolute; inset: 8% 6% 22% 6%;
  width: 88%; height: 70%;
  opacity: 0; z-index: 1;
  object-fit: contain;
  transition: opacity 600ms var(--ease-out);
}
.cat-tile > img.is-active { opacity: 1; }
.cat-tile:hover img { transform: scale(1.06) translateY(-3px); filter: drop-shadow(0 20px 22px rgba(17,17,17,.2)); }
/* Auto-rotation: multiple variant images stack and crossfade. */
/* .cat-tile-img is the class picture() puts on the <picture>/<img>. The wrapper
   handles cross-fade, so the inner image must stay fully opaque. */
.cat-tile-img { opacity: 1; }
/* A bare <img> (no <picture> wrapper) CARRIES .cat-tile-img itself, so the rule
   above would paint it over every other layer. When it sits inside a rotating
   tile it must follow the wrapper's fade like any inner image. */
.cat-tile[data-rotate="1"] > img.cat-tile-img { opacity: 0; }
.cat-tile[data-rotate="1"] > img.cat-tile-img.is-active { opacity: 1; }
/* A very soft warm halo behind the product (kept subtle on the flat panel). */
.cat-tile::before {
  content: ''; position: absolute; left: 50%; top: 44%; width: 80%; height: 60%;
  transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, rgba(255,255,255,.7), transparent 74%);
  z-index: 0; pointer-events: none;
}
/* Label: dark text on a soft fade so it stays legible on the flat light panel. */
.cat-tile-label {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 26px 18px 16px;
  font-family: var(--font-display);
  font-size: 15px; font-weight: 500; letter-spacing: .04em;
  color: #2a2115;
  background: linear-gradient(to top, rgba(240,235,225,.96) 0%, rgba(240,235,225,.7) 60%, rgba(240,235,225,0) 100%);
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
/* A small gold arrow that nudges on hover. */
.cat-tile-label::after {
  content: '→'; font-family: var(--font-body, inherit);
  color: var(--accent); font-size: 16px; opacity: 0; transform: translateX(-4px);
  transition: opacity 260ms ease, transform 260ms var(--ease-out);
}
.cat-tile:hover .cat-tile-label::after { opacity: 1; transform: translateX(0); }
@media (max-width: 820px) {
  /* Category tiles become a swipeable rail on mobile — no awkward orphan tile,
     and it reads as a curated, elegant strip instead of a lopsided stack. */
  
.cat-tiles, .cat-tiles[data-count="3"], .cat-tiles[data-count="4"] { display: flex; flex-wrap: nowrap; overflow-x: auto;
    gap: 12px; max-width: none; padding: 2px 16px 8px;
    scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; }
  .cat-tiles::-webkit-scrollbar { display: none; }
  .cat-tiles .cat-tile { flex: 0 0 62vw; scroll-snap-align: center; }
  .cat-tiles[data-count="1"] .cat-tile { flex-basis: 100%; }
  .cat-tiles[data-count="2"] .cat-tile { flex-basis: 74vw; }
}
@media (max-width: 480px) {
  .cat-tiles { gap: 10px; }
  .cat-tiles .cat-tile { flex-basis: 68vw; }
  .cat-tile-label { font-size: 14px; padding: 14px 12px 12px; }
}

/* ---------- Brand marquee (lightweight luxe ticker) ----------
   Two identical .marquee-group blocks side by side; the track shifts left by
   50% (exactly one group) so the loop is mathematically seamless — no gap, no
   stutter. Only `transform` animates (GPU-cheap). Respects reduced motion. */
.brand-marquee {
  overflow: hidden; white-space: nowrap;
  background: #141210;
  border-top: 1px solid rgba(185,147,94,.25);
  border-bottom: 1px solid rgba(185,147,94,.25);
  padding: 14px 0;
}
.marquee-track {
  display: flex; width: max-content; will-change: transform;
  /* 46s = NO-JS fallback only. initBrandMarquee() (site.js) clones groups so
     one half always covers the bar, then overrides the duration via a
     `.marquee-track.marquee-track--js` rule (higher specificity). Do NOT
     hardcode a smaller duration here — it reintroduces the seam gap. */
  animation: marquee-scroll 46s linear infinite;
}
.marquee-group { display: inline-flex; align-items: center; flex: 0 0 auto; }
.marquee-item {
  display: inline-block; padding: 0 22px;
  font-family: var(--font-display); font-size: 14px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase; color: #e7dcc6;
}
.marquee-sep { color: var(--accent); font-size: 11px; opacity: .85; flex: 0 0 auto; }
@keyframes marquee-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); } /* = exactly one .marquee-group */
}
@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; }
  .brand-marquee { text-align: center; }
  .marquee-group:nth-child(2) { display: none; }
}

/* ---------- Editorial "Our Selection" (homepage only) ----------
   Breaks the "same grid twice" monotony: the first product is a large feature
   tile (2x2), the rest fill a compact grid beside/below it — a magazine layout
   instead of a uniform catalogue. Distinct from .our-selection used elsewhere. */
.selection-editorial {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: auto;
  gap: 28px 26px;
}
.sel-cell { min-width: 0; }
.sel-cell .product-card { height: 100%; }
/* Feature tile spans two columns and two rows, anchored top-left. */
.selection-editorial .sel-feature { grid-column: 1 / span 2; grid-row: 1 / span 2; }
.selection-editorial .sel-feature .product-card-media { aspect-ratio: 1/1; }
.selection-editorial .sel-feature .product-card-title { font-size: 22px; }
.selection-editorial .sel-feature .product-card { padding: 0; }
/* A small "Featured" tag on the big tile so the hierarchy reads as deliberate. */
.selection-editorial .sel-feature { position: relative; }
.selection-editorial .sel-feature .sel-badge {
  /* Sits just to the RIGHT of the product card's own collection tag (HERITAGE),
     which is pinned at top-left (12px). Anchoring to the left edge with a fixed
     offset keeps the two side by side with a small, consistent gap instead of
     pushing "Featured pick" to the far edge of the tile. */
  position: absolute; top: 12px; left: 108px; z-index: 5;
  background: var(--text); color: #fff; font-size: 11px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; padding: 6px 12px; border-radius: 999px;
  white-space: nowrap;
}
@media (max-width: 560px) {
  /* On narrow screens the two pills can collide — let the tag wrap to the next
     line under the collection badge rather than overlap it. */
  .selection-editorial .sel-feature .sel-badge { top: 44px; left: 12px; }
}
@media (max-width: 1100px) {
  .selection-editorial { grid-template-columns: repeat(3, 1fr); }
  .selection-editorial .sel-feature { grid-column: 1 / span 2; grid-row: auto; }
}
@media (max-width: 820px) {
  .selection-editorial { grid-template-columns: repeat(2, 1fr); gap: 22px 16px; }
  .selection-editorial .sel-feature { grid-column: 1 / span 2; grid-row: auto; }
}
/*
 * Phones: TWO columns, not one.
 *
 * This used to be `grid-template-columns: 1fr`, so all nine cards (1 feature + 8)
 * stacked as full-width tiles: measured 4,559px tall on a 390x844 phone = 5.4
 * full screens of scrolling just for this block. Ken, 2026-10-28: "i kinda get
 * tired scrolling too in my phone". Two columns roughly halves it, and the
 * product images are still large enough to read at ~165px.
 *
 * The feature tile keeps spanning the full width (it is the hero of the block),
 * and only the smaller cells pair up.
 */
@media (max-width: 480px) {
  .selection-editorial { grid-template-columns: repeat(2, 1fr); gap: 16px 12px; }
  .selection-editorial .sel-feature { grid-column: 1 / span 2; }
  /* Tighten the cards so two fit comfortably without feeling cramped. */
  .selection-editorial .sel-cell .product-card { padding: 10px; }
  .selection-editorial .sel-cell .product-card-title { font-size: 13px; line-height: 1.3; }
  .selection-editorial .sel-cell .product-card-price { font-size: 13px; }
  /* A 2-up grid can't show a big quick-add button on every tile — hide it on
     phones; the whole card already links to the product. The real class is
     `card-quickadd` (emitted by inc/product_card.php), not `quick-add`. */
  .selection-editorial .sel-cell .card-quickadd { display: none; }
}

/* ---------- Quick add button on product cards ----------
   Slides up on hover for a fast add-to-cart without opening the PDP. Always
   visible on touch devices (no hover). */
.product-card { position: relative; }
.card-quickadd {
  position: absolute; left: 10px; right: 10px; bottom: 10px; z-index: 6;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 11px 14px; border: 0; border-radius: 999px; cursor: pointer;
  background: rgba(20,18,16,.92); color: #fff;
  font-family: var(--font-body); font-size: 13px; font-weight: 600; letter-spacing: .04em;
  opacity: 0; transform: translateY(10px);
  transition: opacity 220ms ease, transform 260ms var(--ease-out), background 200ms ease;
  backdrop-filter: blur(2px);
}

.product-card:hover .card-quickadd, .card-quickadd:focus-visible { opacity: 1; transform: translateY(0); }
.card-quickadd:hover { background: var(--accent); color: #1a1a1a; }
.card-quickadd.is-added { background: #2f7d4f; color: #fff; }
.card-quickadd svg { flex: 0 0 auto; }
@media (hover: none) {
  /* Touch devices: keep it visible, compact. */
  .card-quickadd { opacity: 1; transform: none; left: auto; right: 10px; padding: 9px 12px; }
  .card-quickadd span { display: none; }
}

/* ---- Wishlist heart (top-right of the product card media) ---- */
.card-wishlist {
  position: absolute; top: 10px; right: 10px; z-index: 6;
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(255,255,255,.92); color: #1a1a1a;
  box-shadow: 0 1px 6px rgba(0,0,0,.12);
  transition: transform 180ms var(--ease-out), background 180ms ease, color 180ms ease;
}
.card-wishlist:hover { transform: scale(1.08); background: #fff; }
.card-wishlist svg { transition: fill 180ms ease; }
.card-wishlist.is-saved { background: #1a1a1a; color: #fff; }
.card-wishlist.is-saved svg { fill: #e05252; stroke: #e05252; }
.card-wishlist.is-saved:hover svg { fill: #e05252; stroke: #e05252; }

/* ---------- Mobile enhancements (app-like tab bar, rails, lightbox) ---------- */

/* --- Mobile bottom tab bar --- */
.mtab-bar { display: none; }
@media (max-width: 820px) {
  .mtab-bar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 88;
    background: color-mix(in srgb, var(--bg) 94%, transparent);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid var(--border);
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
    transform: translateY(0); transition: transform 300ms var(--ease-out);
  }
  .mtab-bar.is-hidden { transform: translateY(110%); }
  .mtab {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
    background: none; border: 0; cursor: pointer; position: relative;
    padding: 6px 2px; color: var(--text-muted); text-decoration: none;
    font-size: 10.5px; letter-spacing: .04em; transition: color 180ms ease;
  }
  .mtab svg { width: 22px; height: 22px; }
  .mtab.is-active, .mtab:active { color: var(--text); }
  .mtab-badge {
    position: absolute; top: 2px; right: 50%; transform: translateX(16px);
    min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
    background: var(--accent); color: #1a1a1a; font-size: 10px; font-weight: 700;
    font-style: normal; line-height: 16px; text-align: center;
  }
  .mtab-badge:empty, .mtab-badge[data-cart-count="0"] { display: none; }
  /* Reserve space so the tab bar never covers content/footer. */
  body { padding-bottom: 62px; }
}

/* --- Related products as a swipe rail on mobile --- */
@media (max-width: 820px) {
  .related-rail {
    display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 14px;
    margin: 0 -16px; padding: 2px 16px 10px;
    scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; touch-action: pan-x;
  }
  .related-rail::-webkit-scrollbar { display: none; }
  .related-rail > * { flex: 0 0 62vw; scroll-snap-align: start; }
  .related-rail .product-card-media { touch-action: pan-x; }
}

/* --- PDP photo lightbox (full-screen swipeable gallery) --- */
.lightbox {
  position: fixed; inset: 0; z-index: 200; display: none;
  background: rgba(12,11,10,.96); touch-action: none;
}
.lightbox.open { display: block; }
.lightbox-track {
  display: flex; height: 100%; transition: transform 320ms var(--ease-out);
  will-change: transform;
}
.lightbox-slide {
  flex: 0 0 100%; height: 100%; display: flex; align-items: center; justify-content: center;
  padding: 40px 16px;
}
.lightbox-slide img { max-width: 100%; max-height: 100%; object-fit: contain; user-select: none; -webkit-user-drag: none; }
.lightbox-close {
  position: absolute; top: 14px; right: 14px; z-index: 3;
  width: 42px; height: 42px; border-radius: 50%; border: 0; cursor: pointer;
  background: rgba(255,255,255,.14); color: #fff; font-size: 22px; line-height: 1;
}
.lightbox-count {
  position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
  color: rgba(255,255,255,.8); font-size: 12px; letter-spacing: .1em;
}
.lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 44px; height: 44px; border-radius: 50%; border: 0; cursor: pointer;
  background: rgba(255,255,255,.14); color: #fff; font-size: 24px;
}
.lightbox-nav.prev { left: 10px; } .lightbox-nav.next { right: 10px; }
.lightbox-dots { position: absolute; bottom: 46px; left: 50%; transform: translateX(-50%); display: flex; gap: 8px; }
.lightbox-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.35); border: 0; padding: 0; }
.lightbox-dot.active { background: #fff; }
.pdp-gallery-main { cursor: default; }

/* --- Blur-up progressive image loading --- */
.img-blur { opacity: 0; filter: blur(12px); transition: opacity 500ms ease, filter 600ms ease; }
.img-blur.loaded { opacity: 1; filter: none; }

/* While the photo lightbox is open, hide the floating chat + mobile tab bar so
   the gallery is completely clean. */

body.lb-open .chat-fab, body.lb-open .chat-panel, body.lb-open .mtab-bar, body.lb-open .mobile-atc-bar { display: none !important; }

/* ---------- Per-product size guide (slim single line) ---------- */
.pdp-size {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  margin: 10px 0 2px; font-size: 13.5px; line-height: 1.5;
}
.pdp-size-label {
  font-family: var(--font-display); font-weight: 600; color: var(--text);
}
.pdp-size-body { color: var(--text-alt); display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 7px; }
.pdp-size-fit { color: var(--text-alt); }
.pdp-size-dot { color: var(--border); }
.pdp-size-pair { display: inline-flex; gap: 5px; }
.pdp-size-pair dt { margin: 0; color: var(--text-muted); }
.pdp-size-pair dd { margin: 0; font-weight: 600; color: var(--text); }

/* Review photos (customer images) */
.review-photos { display: flex; flex-wrap: wrap; gap: 10px; margin: 16px 0 6px; }
.review-photo { padding: 0; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; cursor: default; background: none; line-height: 0; transition: border-color 260ms var(--ease-out); }
/* Customer review photo. Was 72×72 — read as a tiny thumbnail and the product
   was hard to see. Now 150×150: big enough to actually show what the buyer
   received, while a review card is ~390px wide on desktop so it still leaves
   room for the name/verified line beside a wrapped row of photos. Ken,
   2026-10-28: "make the review image abit bigger".
   (A full-width portrait was considered and rejected: the reviews are a
   3-column grid, so one tall card would unbalance the whole row.) */
.review-photo img { width: 150px; height: 150px; object-fit: cover; display: block; transition: transform 320ms var(--ease-out); }
.review-photo:hover { border-color: var(--accent); }
.review-photo:hover img { transform: scale(1.05); }
/* The homepage testimonial slider reuses .review-photo inside much narrower
   slides (the container that actually wraps it is .reviews-slider /
   .review-slide — NOT .testimonial-card, which does not exist here), where
   150px would swamp the card. Keep those at 84px. */
.reviews-slider .review-photo img,
.review-slide .review-photo img { width: 84px; height: 84px; }
/* Keep the photo from dominating very narrow phones. */
@media (max-width: 480px) {
  .review-photo img { width: 120px; height: 120px; }
  .reviews-slider .review-photo img,
  .review-slide .review-photo img { width: 72px; height: 72px; }
}

/* ==========================================================================
   TURNSTILE - hidden but still working
   The widget protects the newsletter popup, contact, login, register and
   checkout. Visually it cluttered the newsletter popup (a checkbox box in
   the middle of a clean modal). Cloudflare's own recommended pattern for
   hiding it while keeping it functional: keep the widget in the layout
   flow but scale it down and fade it out. It still renders (so the
   challenge still runs and still fills the hidden token input), it just no
   longer reads as a visual element. NEVER display:none / visibility:hidden:
   the challenge needs the widget to be laid out, or the token never
   materialises and every protected form breaks.
   ========================================================================== */
.elios-turnstile {
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
  opacity: 0.01;
  position: absolute;
  pointer-events: none;
}
/* keep the iframe inside at full size so the challenge still completes */
.elios-turnstile iframe { min-width: 300px; min-height: 65px; }
#review-photo-modal { position: fixed; inset: 0; z-index: 300; display: none; align-items: center; justify-content: center; background: rgba(12,11,10,.94); }
#review-photo-modal.open { display: flex; }
#review-photo-modal img { max-width: 92vw; max-height: 88vh; object-fit: contain; border-radius: 8px; }
#review-photo-modal button { position: absolute; top: 16px; right: 16px; width: 42px; height: 42px; border-radius: 50%; border: 0; background: rgba(255,255,255,.16); color: #fff; font-size: 22px; cursor: pointer; }

/* ---------- Per-product FAQ ---------- */
.pdp-faq { margin-top: 40px; }
.pdp-faq .faq-item summary h3 { margin: 0; font: inherit; display: inline; }
.pdp-faq .faq-list { border-top: 1px solid var(--border); }
.pdp-faq .faq-item { border-bottom: 1px solid var(--border); }
.faq-hero { text-align: center; margin-bottom: 40px; }
.faq-hero h1 { font-family: var(--font-display); font-size: clamp(30px, 5vw, 46px); margin: 8px 0 14px; letter-spacing: -0.02em; }
.faq-intro { color: var(--text-muted); font-size: 16px; line-height: 1.7; max-width: 560px; margin: 0 auto; }
.faq-list { border-top: 1px solid var(--border); }
.faq-item { border-bottom: 1px solid var(--border); }
.faq-item summary {
  list-style: none; cursor: pointer; padding: 20px 44px 20px 0; position: relative;
  font-family: var(--font-display); font-size: 18px; font-weight: 500; color: var(--text);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary h2 { margin: 0; font: inherit; display: inline; }
.faq-item summary::after {
  content: '+'; position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  font-size: 24px; font-weight: 300; color: var(--accent); transition: transform 240ms ease;
}
.faq-item[open] summary::after { content: '–'; }
.faq-answer { padding: 0 44px 22px 0; color: var(--text-alt); font-size: 15.5px; line-height: 1.75; }
.faq-answer p { margin: 0; }
.faq-foot { margin-top: 34px; text-align: center; color: var(--text-muted); font-size: 15px; }
.faq-foot a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 700px) {
  .faq-page { padding: 28px 16px 70px; }
  .faq-item summary { font-size: 16px; padding: 16px 36px 16px 0; }
  .faq-answer { padding: 0 30px 18px 0; font-size: 15px; }
}

/* ---------- Sticky header auto-hide (reveals on scroll-up) ---------- */
.site-header { transition: transform 320ms var(--ease-out), box-shadow 320ms ease; }
.site-header.header-hidden { transform: translateY(-102%); }
/* Anchor jumps shouldn't hide under the sticky header */
:target { scroll-margin-top: 90px; }
section[id], [id^="section-"] { scroll-margin-top: 90px; }

/* =====================================================================
   LUXURY CARD REFINEMENT (round 4) — kill the "rectangle" feel
   Luxury stores (Aesop, ouqla, Apple) never box the text: the image floats,
   the meta is quiet and airy, and NOTHING shows an outline/border on hover.
   ===================================================================== */

/* No focus rectangle around the card or its title. Keyboard users still get a
   tasteful gold underline on the title (see :focus-visible below). */
.product-card, .product-card-body, .product-card-title { outline: none !important; }
.product-card-media { outline: none; }
/* Elegant keyboard-only focus: a thin gold underline, never a browser box. */
.product-card-title:focus-visible {
  outline: none;
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 4px;
}
.product-card-media:focus-visible { outline: 1px solid var(--accent); outline-offset: 3px; border-radius: 14px; }

/* Quiet, editorial meta block: no padding box, generous air, thin divider. */
.product-card-body {
  background: none;
  padding: 18px 2px 0;
  border: 0;
}
/* Optional hairline that fades out — a luxe detail instead of a box. */
.product-card-body::before { content: none; }

/* Title: airy serif, muted until hovered. */
.product-card-title {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 400;
  letter-spacing: .005em;
  color: var(--text);
  transition: color 200ms ease, opacity 200ms ease;
}
/* Price row: quiet, letter-spaced. */
.product-card-price {
  gap: 10px;
  font-variant-numeric: tabular-nums;
}
.price-current { font-size: 14px; font-weight: 500; color: var(--text-alt); }
.product-card:hover .price-current { color: var(--text); }

/* The product image is the card. Hover is handled once, higher up, with a
   borderless spotlight + silhouette shadow. Nothing here adds a rectangle. */
.product-card-media { box-shadow: none !important; border: 0 !important; }
/* Clear breathing room between the image and its meta so nothing sits under
   the name/price. (Consolidates earlier .product-card-body rules.) */
.product-card-body { padding: 0 2px; margin-top: 22px; }
/* Kill the reveal compositing hint once revealed (avoids a faint layer box). */
.product-card.reveal.in { will-change: auto; }

/* ---- Full-bleed "banner" section with a real image + overlay text ---- */
.editorial-banner {
  position: relative; width: 100%;
  min-height: clamp(320px, 46vw, 520px);
  display: flex; align-items: flex-end; /* cinematic: copy sits low-left, not dead-center */
  overflow: hidden; isolation: isolate;
  margin: 72px 0;
}
.editorial-banner img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0;
}
.editorial-banner::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  /* Heavier at the BOTTOM: the copy sits low-left now, so the scrim must
     protect that zone (was a flat left-bias gradient for centered copy). */
  background: linear-gradient(180deg, rgba(0,0,0,.18) 0%, rgba(0,0,0,.34) 55%, rgba(0,0,0,.68) 100%);
}
.editorial-banner-inner {
  position: relative; z-index: 2; max-width: 620px;
  text-align: left; padding: 56px clamp(24px, 6vw, 80px) 64px; color: #fff;
}
.editorial-banner .eb-eyebrow {
  font-size: 11px; letter-spacing: .28em; text-transform: uppercase;
  color: rgba(255,255,255,.85); margin-bottom: 16px;
}
.editorial-banner h2 {
  font-family: var(--font-display);
  font-size: clamp(30px, 4.4vw, 54px); font-weight: 400; line-height: 1.08;
  margin: 0 0 16px; color: #fff;
}
.editorial-banner p { color: rgba(255,255,255,.9); font-size: clamp(14px,1.5vw,17px); line-height: 1.6; margin: 0 0 26px; }
.editorial-banner .eb-cta {
  display: inline-block; padding: 15px 30px; border: 1px solid rgba(255,255,255,.85);
  color: #fff; text-decoration: none; font-size: 12px; letter-spacing: .2em;
  text-transform: uppercase; font-weight: 600; border-radius: 2px;
  transition: background 220ms ease, color 220ms ease, transform 220ms ease;
}
.editorial-banner .eb-cta:hover { background: #fff; color: #111; transform: translateY(-2px); }

/* ---- "Value props" strip: breaks the monotony with a lighter band ---- */
.value-strip {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
  background: var(--bg-alt); margin: 0;
}
.value-strip .vp {
  padding: 34px 22px; text-align: center; border-left: 1px solid var(--border);
}
.value-strip .vp:first-child { border-left: 0; }
.value-strip .vp-title { font-size: 13px; font-weight: 600; letter-spacing: .04em; margin-bottom: 6px; }
.value-strip .vp-sub { font-size: 12.5px; color: var(--text-muted); line-height: 1.5; }
@media (max-width: 820px) {
  .value-strip { grid-template-columns: repeat(2, 1fr); }
  .value-strip .vp { padding: 20px 14px; }
  .value-strip .vp:nth-child(odd) { border-left: 0; }
  .value-strip .vp:nth-child(n+3) { border-top: 1px solid var(--border); }
  .value-strip .vp-title { font-size: 12.5px; margin-bottom: 3px; }
  .value-strip .vp-sub { font-size: 11.5px; line-height: 1.4; }
}
@media (max-width: 460px) {
  /* Compact 2-up chip style — keeps all four promises without eating the screen. */
  .value-strip { grid-template-columns: repeat(2, 1fr); }
  .value-strip .vp { padding: 16px 12px; }
  .value-strip .vp-title { font-size: 12px; }
  .value-strip .vp-sub { font-size: 11px; }
}

/* ---- Section rhythm: more contrast between bands (less monotony) ---- */
.section-alt { background: var(--bg-alt); }
.section-tight { padding-top: 30px; padding-bottom: 30px; }

/* Second banner variant: SPLIT editorial CAROUSEL (image | solid panel).
   A different shape from banner 1's full-bleed — this kills the "repetitive
   banner" look, and the arrows let Ken cycle category banners ("edits") as
   the catalogue grows. Same luxury panel language as the exotic split-banner. */
.eb-split-wrap { position: relative; margin: 72px 0; }
.eb-split {
  overflow: hidden;
  border-radius: var(--border-radius);
  background: var(--bg-alt);
}
.eb-split-track {
  display: flex;
  transition: transform 520ms var(--ease-out);
  will-change: transform;
  touch-action: pan-y;
}
.eb-slide { flex: 0 0 100%; min-width: 100%; display: grid; grid-template-columns: 1.15fr 1fr; }
.eb-split-media { height: clamp(300px, 34vw, 400px); overflow: hidden; }
/* contain: the banner image is square while the media frame is ~1.6:1 -
   cover cropped the product top/bottom so it read as "cut off" (the text
   shrank in the same pass, making the crop more obvious). contain shows the
   whole product; the leftover frame fills with the card background so there
   is no harsh letterbox. */
.eb-split-media img { width: 100%; height: 100%; object-fit: contain; transition: transform 900ms var(--ease-out); }
.eb-split:hover .eb-split-media img { transform: scale(1.04); }
.eb-split-body {
  padding: clamp(36px, 5vw, 72px);
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start;
}
.eb-split-body .eb-eyebrow { font-size: 11px; letter-spacing: .28em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 16px; }
.eb-split-body h2 {
  font-family: var(--font-display); font-weight: 400; line-height: 1.1;
  font-size: clamp(26px, 3.2vw, 42px); letter-spacing: -0.015em;
  margin: 0 0 16px; color: var(--text);
}
.eb-split-body p { color: var(--text-alt); font-size: 15.5px; line-height: 1.65; margin: 0 0 28px; max-width: 40ch; }
.eb-cta-dark {
  display: inline-block; padding: 14px 28px; border: 1px solid var(--text);
  color: var(--text); text-decoration: none; font-size: 12px; letter-spacing: .2em;
  text-transform: uppercase; font-weight: 600; border-radius: 2px;
  transition: background 220ms ease, color 220ms ease, transform 220ms ease;
}
.eb-cta-dark:hover { background: var(--text); color: var(--bg); transform: translateY(-2px); }

/* Carousel arrows — circular, half-overlapping the card like the PDP gallery */
.eb-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  z-index: 3; width: 46px; height: 46px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--bg-elevated, #fff);
  color: var(--text); font-size: 22px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 20px -8px rgba(0,0,0,.25);
  transition: background 200ms ease, color 200ms ease, border-color 200ms ease;
}
.eb-arrow:hover { background: var(--text); color: var(--bg); border-color: var(--text); }
.eb-arrow-prev { left: -23px; }
.eb-arrow-next { right: -23px; }

/* Carousel dots */
.eb-dots { display: flex; justify-content: center; gap: 9px; margin-top: 18px; }
.eb-dot {
  width: 8px; height: 8px; border-radius: 50%; border: 0; padding: 0;
  background: var(--border); cursor: pointer; transition: background 200ms ease, transform 200ms ease;
}
.eb-dot.active { background: var(--text); transform: scale(1.15); }

@media (max-width: 800px) {
  .eb-split-wrap { margin: 44px 0; }
  .eb-slide { grid-template-columns: 1fr; }
  .eb-split-media { min-height: 260px; }
  .eb-split-body { padding: 34px 22px; }
  .eb-arrow { width: 40px; height: 40px; font-size: 19px; }
  .eb-arrow-prev { left: 10px; }
  .eb-arrow-next { right: 10px; }
}

/* ---------- Zone-based shipping (checkout) ---------- */
.zone-box {
  border: 1px solid var(--border); border-radius: var(--border-radius);
  padding: 16px 18px; margin-bottom: 10px; background: var(--bg-alt);
}
.zone-result { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; font-size: 15px; }
.zone-result .zone-name { font-weight: 600; }
.zone-result .zone-price { margin-left: auto; font-weight: 700; color: var(--accent); }
.zone-result .zone-eta { color: var(--text-muted); font-size: 13px; }
.zone-result.is-free .zone-price { color: var(--success); }
.zone-result.is-pending { color: var(--text-muted); font-weight: 400; }
.zone-result.is-error { color: var(--sale); font-weight: 400; font-size: 14px; line-height: 1.5; }
.zone-result.is-error a { color: inherit; font-weight: 600; }
.zone-hint { font-size: 12.5px; color: var(--text-muted); margin: 0 0 6px; line-height: 1.5; }

/* =====================================================================
   MOBILE POLISH (<= 640px) — thumb-friendly targets, calmer spacing,
   a full-width hero image, and a 2-up product grid that breathes.
   ===================================================================== */
@media (max-width: 640px) {
  /* Comfortable side gutters on every section/container. */
  .container { padding-left: 18px; padding-right: 18px; }

  /* Header: taller, roomier icons/logo so nothing is cramped. */
  .header-inner { padding: 12px 16px; gap: 10px; }
  .header-actions { gap: 2px; }
  .header-icon { width: 44px; height: 44px; }
  .hamburger { width: 44px; height: 44px; }
  .logo { font-size: 18px; }

  /* Tappable buttons: meet the ~44px target and use a readable label size. */
  .btn, .hero-cta-btn, .section-viewall, .pay-method { min-height: 46px; }
  .btn, .hero-cta-btn { font-size: 13.5px; padding: 14px 22px; }

  /* Body copy: nudge up for readability (was often < 14px). */
  body { font-size: 15px; }
  .section-sub, .hero-sub, p { font-size: 14.5px; line-height: 1.65; }

  /* Hero: stack cleanly, image full-width and larger, more air around copy. */
  .hero { padding: 28px 0 10px; }
  .hero-grid { gap: 26px; }
  .hero h1 { font-size: clamp(30px, 9vw, 40px); line-height: 1.06; }
  .hero-spot-media { width: 100%; max-width: 100%; }

  /* Product grid: comfortable 2-up with a bigger gap; single column on the
     smallest phones so cards aren't tiny. */
  .our-selection { gap: 30px 16px; grid-template-columns: repeat(2, 1fr); }
  .product-card-title { font-size: 15px; }
  .price-current { font-size: 13.5px; }

  /* Editorial banners: shorter on mobile so they don't dominate. */
  .editorial-banner { min-height: 300px; margin: 44px 0; }

  /* Section rhythm: tighter on mobile. */
  .section { padding-top: 46px; padding-bottom: 46px; }

  /* Footer: comfy spacing. */
  footer .container, footer > div { padding: 30px 18px; }
}
@media (max-width: 360px) {
  /* Only the very smallest phones: single-column products for legibility. */
  .our-selection { grid-template-columns: 1fr; }
  .hero h1 { font-size: 28px; }
}

/* Sticky mobile add-to-cart bar: keep it clear of the chat bubble. */
@media (max-width: 640px) {
  .mobile-atc-bar.show { padding-bottom: max(12px, env(safe-area-inset-bottom)); }
}

/* ==========================================================================
   Welcome / discount signup popup  (site-wide, dismissible everywhere)
   --------------------------------------------------------------------------
   Shown once per visitor. Closing is deliberately easy: the × button, the
   "No thanks" link, Escape, a click on the backdrop, or the mobile swipe-down
   handle all dismiss it — and we remember the choice for a long time so it
   never nags.
   ========================================================================== */
.wp-overlay {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(17, 17, 17, 0.45);
  backdrop-filter: blur(3px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 280ms var(--ease-out), visibility 280ms var(--ease-out);
}
.wp-overlay.is-open { opacity: 1; visibility: visible; }

.wp-card {
  position: relative;
  width: 100%;
  max-width: 880px;
  background: var(--bg-elevated);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 24px 70px rgba(17, 17, 17, 0.28);
  transform: translateY(18px) scale(0.985);
  transition: transform 320ms var(--ease-out);
  display: grid;
  grid-template-columns: 1.05fr 1fr;
}
.wp-overlay.is-open .wp-card { transform: translateY(0) scale(1); }

/* No image → single tidy column (never an empty grey panel) */
.wp-card--text { grid-template-columns: 1fr; max-width: 480px; }
.wp-card--text .wp-body { padding: 44px 46px; text-align: center; }
.wp-card--text .wp-form { justify-content: center; }

/* Hero secondary buy CTA — answers "how much / how to buy" above the fold.
   Deliberately secondary to "Explore the Collection": a buyer who already
   knows they want one gets a direct path and a price in the first screen. */
.hero-buy-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  padding: 13px 24px;
  border: 1px solid var(--text);
  border-radius: var(--border-radius);
  background: transparent;
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .02em;
  transition: background var(--transition), color var(--transition), transform var(--transition);
}
.hero-buy-btn:hover { background: var(--text); color: #fff; }
.hero-buy-btn:active { transform: scale(.985); }
.hero-buy-note { margin: 10px 0 0; font-size: 12.5px; color: var(--text-muted); }
.hero-ships-from {
  margin: 6px 0 0; font-size: 12px; letter-spacing: .01em;
  color: var(--text-alt); display: inline-flex; align-items: center; gap: 6px;
}
.hero-ships-from::before { content: "📍"; font-size: 11px; }
/* Payment method chips in the footer (text, no third-party logo assets needed). */
.pay-chip {
  display: inline-flex; align-items: center;
  padding: 3px 9px; margin-left: 8px;
  border: 1px solid var(--border); border-radius: 999px;
  font-size: 11.5px; color: var(--text-alt); white-space: nowrap;
}
@media (max-width: 720px) {
  .hero-buy-btn { width: 100%; justify-content: center; }
}

/* Left: image (hidden on narrow screens so the form is what people see) */
.wp-media {
  background: var(--bg-card);
  min-height: 380px;
  background-size: cover;
  background-position: center;
}

.wp-body { padding: 40px 42px; display: flex; flex-direction: column; justify-content: center; }

.wp-eyebrow {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
  margin: 0 0 12px;
}
.wp-title {
  font-family: var(--font-display);
  font-size: 27px;
  line-height: 1.2;
  margin: 0 0 10px;
  color: var(--text);
}
.wp-sub {
  font-size: 14.5px;
  color: var(--text-alt);
  line-height: 1.6;
  margin: 0 0 22px;
}
.wp-sub strong { color: var(--text); }

.wp-form { display: flex; gap: 8px; flex-wrap: wrap; }
.wp-input {
  flex: 1 1 200px;
  padding: 13px 15px;
  border: 1px solid var(--border);
  border-radius: var(--border-radius);
  background: var(--bg);
  font-size: 15px;
  transition: border-color var(--transition), box-shadow var(--transition);
}
.wp-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(184, 147, 94, 0.16);
}
.wp-submit {
  flex: 0 0 auto;
  padding: 13px 22px;
  background: var(--text);
  color: #fff;
  border: none;
  border-radius: var(--border-radius);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  transition: background var(--transition), transform var(--transition);
}
.wp-submit:hover { background: var(--accent); }
.wp-submit:active { transform: scale(0.985); }
.wp-submit[disabled] { opacity: 0.6; cursor: wait; }

.wp-note { font-size: 12px; color: var(--text-muted); margin: 12px 0 0; }
.wp-msg { font-size: 13px; margin: 10px 0 0; min-height: 1px; }
.wp-msg.is-err { color: var(--sale); }
.wp-msg.is-ok { color: var(--success); }

/* Coupon reveal after signup */
.wp-code {
  display: none;
  margin: 14px 0 0;
  padding: 14px 16px;
  border: 1px dashed var(--accent);
  border-radius: var(--border-radius);
  background: rgba(184, 147, 94, 0.08);
  text-align: center;
}
.wp-code.is-shown { display: block; }
.wp-code-label { font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-muted); }
.wp-code-value {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--accent-hover);
  margin: 4px 0 0;
  user-select: all;
}

/* --- Easy-close affordances --- */
.wp-close {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.9);
  color: var(--text-alt);
  border: 1px solid var(--border);
  font-size: 20px;
  line-height: 1;
  transition: background var(--transition), color var(--transition), transform var(--transition);
}
.wp-close:hover { background: #fff; color: var(--text); transform: rotate(90deg); }

.wp-decline {
  display: block;
  margin: 16px auto 0;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 12.5px;
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: 4px 8px;
}
.wp-decline:hover { color: var(--text-alt); }

/* Mobile grab handle (swipe-down to close) */
.wp-handle { display: none; }

@media (max-width: 720px) {
  .wp-overlay { align-items: flex-end; padding: 0; }
  .wp-card {
    grid-template-columns: 1fr;
    max-width: 100%;
    border-radius: 16px 16px 0 0;
    transform: translateY(100%);
    max-height: 92vh;
    overflow-y: auto;
  }
  .wp-overlay.is-open .wp-card { transform: translateY(0); }
  .wp-media { display: none; }
  .wp-card--text { max-width: 100%; }
  .wp-card--text .wp-body { padding: 30px 22px calc(26px + env(safe-area-inset-bottom)); text-align: left; }
  .wp-body { padding: 30px 22px calc(26px + env(safe-area-inset-bottom)); }
  .wp-title { font-size: 23px; }
  .wp-handle {
    display: block;
    width: 44px;
    height: 5px;
    border-radius: 3px;
    background: var(--border);
    margin: 10px auto 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .wp-overlay, .wp-card, .wp-close { transition: none !important; }
  .wp-overlay { opacity: 1; visibility: hidden; }
  .wp-overlay.is-open { visibility: visible; }
}


/* ==========================================================================
   Cart: free-shipping progress bar
   The strongest AOV nudge in e-commerce — shows the buyer exactly how much
   more unlocks free nationwide shipping. Turns from "keep going" (gold) to
   "unlocked" (green) at the threshold.
   ========================================================================== */
.ship-progress { padding: 4px 0 12px; }
.ship-progress-bar {
  height: 6px; border-radius: 999px; overflow: hidden;
  background: var(--chip-bg); border: 1px solid var(--border);
}
.ship-progress-bar > span {
  display: block; height: 100%; width: 0;
  background: linear-gradient(90deg, var(--accent), var(--accent-hover));
  border-radius: 999px;
  transition: width 420ms var(--ease-out), background 300ms ease;
}
.ship-progress.is-unlocked .ship-progress-bar > span {
  background: linear-gradient(90deg, #3d9944, #2f7d36);
}
.ship-progress-msg {
  margin: 8px 0 0; font-size: 12.5px; line-height: 1.5;
  color: var(--text-alt);
}
.ship-progress.is-unlocked .ship-progress-msg { color: var(--success); font-weight: 600; }
@media (prefers-reduced-motion: reduce) {
  .ship-progress-bar > span { transition: none !important; }
}


/* ==========================================================================
   Welcome popup — "check your inbox" confirmation
   Replaces the old code-reveal box. The coupon is emailed only (an on-screen
   code could be farmed with a fake address), so we show where to find it.
   ========================================================================== */
.wp-inbox { text-align: left; }
.wp-inbox .wp-code-label { text-align: center; }
.wp-inbox-body {
  margin: 8px 0 14px; font-size: 13.5px; line-height: 1.55;
  color: var(--text-alt); text-align: center;
}
.wp-inbox-steps {
  margin: 0; padding: 0; list-style: none; counter-reset: wpstep;
  display: grid; gap: 9px;
}
.wp-inbox-steps li {
  counter-increment: wpstep;
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 13px; line-height: 1.45; color: var(--text);
}
.wp-inbox-steps li::before {
  content: counter(wpstep);
  flex: 0 0 20px; height: 20px; border-radius: 50%;
  background: var(--accent); color: #141414;
  font-size: 11px; font-weight: 700; line-height: 20px; text-align: center;
}

/* ===== Category nav with dropdowns (2026-10-05) =====
   Desktop: hover OR keyboard-focus opens the submenu (:focus-within), so it
   works without JS and stays accessible. Mobile: tap-to-expand accordion.
   Both are driven by the same markup in inc/layout.php. */

.main-nav .nav-item { position: relative; display: flex; align-items: center; }
.main-nav .nav-cat { display: inline-flex; align-items: center; gap: 5px; }
.main-nav .nav-caret { transition: transform .2s var(--ease-out, ease); opacity: .6; }

.main-nav .nav-item:hover .nav-caret, .main-nav .nav-item:focus-within .nav-caret { transform: rotate(180deg); }

.main-nav .nav-dropdown {
  position: absolute;
  top: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%) translateY(-6px);
  min-width: 200px;
  background: var(--bg, #fff);
  border: 1px solid var(--border, #e6e2da);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0,0,0,.10);
  padding: 8px;
  display: flex;
  flex-direction: column;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .18s ease, transform .18s ease, visibility .18s;
  z-index: 60;
}
/* Bridge the 10px gap so moving the mouse into the menu doesn't close it. */
.main-nav .nav-dropdown::before {
  content: ''; position: absolute; top: -12px; left: 0; right: 0; height: 12px;
}

.main-nav .nav-item:hover .nav-dropdown, .main-nav .nav-item:focus-within .nav-dropdown { opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateX(-50%) translateY(0); }
.main-nav .nav-dropdown a {
  padding: 9px 12px; border-radius: 7px; font-size: 14px; white-space: nowrap;
  color: var(--text, #1a1a1a); text-decoration: none; transition: background .15s;
}
.main-nav .nav-dropdown a::after { display: none; }   /* kill the underline effect */
.main-nav .nav-dropdown a:hover { background: var(--bg-alt, #f5f3ef); }
.main-nav .nav-dropdown .nav-dropdown-all {
  margin-top: 4px; border-top: 1px solid var(--border, #e6e2da);
  padding-top: 11px; font-weight: 600; font-size: 13px;
}

/* ---- Mobile accordion ---- */
.mobile-nav .mnav-group { border-bottom: 1px solid rgba(255,255,255,.08); }
.mobile-nav .mnav-toggle {
  width: 100%; background: none; border: 0; color: inherit; font: inherit;
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 0; cursor: pointer; text-align: left;
}
.mobile-nav .mnav-toggle svg { transition: transform .22s var(--ease-out, ease); opacity: .7; }
.mobile-nav .mnav-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
/* mnav dropdown: replaced by the max-height rules at the end of this file */
/* ==========================================================================
   COLLECTION / CATEGORY GRID  — Ouqla-inspired "catalogue" layout (2026-10-05)
   ==========================================================================
   WHAT CHANGED AND WHY
   Ouqla.com renders its collection pages as a printed-catalogue grid: hairline
   rules between cells, a soft tile behind each product, and the name/price on a
   single editorial line. We take that STRUCTURE but keep ELIOS's warm palette
   (cream + gold) rather than copying their stark black/white.

   BUG FIXED HERE: .product-grid had NO definition at all, so category pages
   rendered as a single unstyled column. It is defined now.
   ========================================================================== */

.product-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;                       /* cells touch; hairline rules do the dividing */
  border-top: 1px solid var(--border);
  border-left: 1px solid var(--border);
  margin: 8px 0 48px;
  background: var(--bg);
}
@media (max-width: 1000px) { .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Each card becomes a catalogue cell: hairline on the right + bottom, roomy
   padding, and enough left-aligned structure to read like a spread. */
.product-grid .product-card {
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  padding: 22px 22px 18px;
  border-radius: 0;             /* the catalogue look is square-cornered */
  background: transparent;
  transition: background 300ms var(--ease-out, ease);
  position: relative;
}
.product-grid .product-card:hover { background: var(--bg-alt); }

/* Ken, 2026-10-31: no tile fill — cut-outs sit on the page background.
.product-grid .product-card-media {
  background: transparent;
  border-radius: 10px;
  padding: 18px;
  aspect-ratio: 1 / 1.15;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.product-grid .product-card-media::before { display: none; }   /* no radial glow here */

.product-grid .product-card-media .card-slide img, .product-grid .product-card-track img { filter: none;                 /* the tile provides the contrast now */
  max-height: 100%;
  width: 100%;
  object-fit: contain;
  transition: transform 500ms cubic-bezier(.34,1.16,.64,1); }
.product-grid .product-card:hover .card-slide img { transform: scale(1.04); }

/* Name left, price right — a single editorial line, like Ouqla. */
.product-grid .product-card-body {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 2px 0;
}
.product-grid .product-card-title {
  font-size: 14.5px;
  font-weight: 500;
  letter-spacing: .01em;
  margin: 0;
  flex: 1 1 auto;
}
.product-grid .product-card-price {
  flex: 0 0 auto;
  font-size: 14px;
  gap: 6px;
}
/* Stock flags sit under the name line without disturbing it. */

.product-grid .stock-out, .product-grid .stock-low { grid-column: 1 / -1;
  font-size: 11.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: 6px 2px 0; }

/* Quick-add: hidden until hover, per the Ouqla "browse, don't clutter" idea.
   Always visible on touch devices, where hover does not exist. */
.product-grid .product-card .card-quickadd {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 220ms ease, transform 220ms ease;
}

.product-grid .product-card:hover .card-quickadd, .product-grid .product-card:focus-within .card-quickadd { opacity: 1;
  transform: translateY(0); }
@media (hover: none) {
  .product-grid .product-card .card-quickadd { opacity: 1; transform: none; }
}

/* Badge sits inside the tile corner. */
.product-grid .product-card-badge {
  top: 12px;
  left: 12px;
}

/* ---- Header row: title left, count + sort right (Ouqla pattern) ---- */
.collection-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding: 0 0 18px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 0;
}
.collection-head .collection-head-left { flex: 1 1 auto; min-width: 240px; }
.collection-head h1 {
  font-size: clamp(28px, 4.2vw, 46px);
  margin: 0 0 6px;
  letter-spacing: -.01em;
}
.collection-head .collection-desc {
  color: var(--text-alt);
  max-width: 620px;
  margin: 0 0 14px;
  font-size: 14.5px;
  line-height: 1.6;
}
.collection-head .collection-count {
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}
@media (max-width: 640px) {
  .collection-head { align-items: flex-start; }
  .collection-head .collection-count { align-self: flex-start; }
}

/* ---- Filter chips (reuse of the category sub-collection filter) ---- */
.collection-chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 18px 0 0; }
.collection-chips .chip {
  display: inline-block;
  padding: 7px 15px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 13px;
  color: var(--text-alt);
  text-decoration: none;
  transition: all 180ms ease;
  background: transparent;
}
.collection-chips .chip:hover { border-color: var(--accent); color: var(--text); }
.collection-chips .chip.on {
  background: var(--text);
  border-color: var(--text);
  color: #fff;
}

/* ---- Empty state ---- */
.collection-empty {
  text-align: center;
  padding: 72px 24px 88px;
  color: var(--text-muted);
}
.collection-empty .ce-title {
  font-size: 20px;
  color: var(--text);
  margin: 0 0 8px;
  font-weight: 500;
}

/* ===== FIX (2026-10-05): the catalogue tile in category grids =====
   The generic .product-card-media rule sets `background: transparent
   !important` (it was designed for the dark editorial homepage where a panel
   looked cheap). Inside category grids we DO want the soft tile — the Ouqla
   catalogue look depends on it — so the override has to be equally specific
   AND !important, and it must also kill the radial glow that the dark theme
   uses instead of a tile. */

.product-grid .product-card-media {
  background: transparent !important;
  box-shadow: none !important;
  padding: 20px;
  border-radius: 10px;
  aspect-ratio: 1 / 1.02;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.product-grid .product-card-media::before { display: none !important; }

.product-grid .product-card-media .card-slide img, .product-grid .product-card-media .card-track img { filter: none !important;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain; }
/* Images are cut-outs with their own shadow: keep a whisper of it on the tile
   so the product still "sits" rather than floating flat. */
.product-grid .product-card-media .card-slide img {
  filter: drop-shadow(0 6px 10px rgba(0,0,0,.10)) !important;
}
.product-grid .product-card:hover .product-card-media { transform: none; }

/* Tighten the dead space under the image so the name/price sit close to it. */
.product-grid .product-card { padding: 18px 18px 16px; }
.product-grid .product-card-body { padding: 12px 2px 0; align-items: flex-start; }
.product-grid .product-card-title { line-height: 1.35; }

/* Price should sit on the SAME visual line as the name even when the name
   wraps: keep it top-aligned and non-shrinking rather than baseline-aligned. */
.product-grid .product-card-price { align-self: flex-start; padding-top: 1px; }
/* ==========================================================================
   FIX (2026-10-05): "box inside a box" on product cards
   ==========================================================================
   THE BUG
   Two blocks in this stylesheet targeted the SAME selector with contradictory
   sizing. The first said `width: 100%` (stretch the image to fill the tile); a
   later block said `width: auto; height: auto` (use the image's natural size).
   CSS takes the LAST match, so `auto` won and every card image rendered at
   whatever size the file happened to be.

   Because studio renders are 900x900 while legacy cut-outs are 800x800, and
   because each product's artwork sits differently inside its own canvas, that
   produced inconsistent results per product — including the charm, which showed
   a small inner panel sitting on the tile, reading as "a box inside a box" with
   its own shadow.

   THE FIX
   One authoritative rule. The <img> must FILL the tile width and use
   object-fit: contain so the artwork scales to the tile while preserving its
   aspect ratio. No `width:auto` anywhere in the grid context.
   ========================================================================== */


.product-grid .product-card-media .card-slide img, .product-grid .product-card-media .card-track img, .product-grid .product-card-track img { /* Fill the tile. `contain` keeps the aspect ratio, so nothing crops. */
  display: block;
  width: 100% !important;
  height: 100% !important;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  object-position: center center;
  /* The studio render already carries its own backdrop + contact shadow, so the
     card must NOT add another filter or drop-shadow on top (that was the second
     shadow the owner could see). */
  filter: none !important;
  box-shadow: none !important;
  border-radius: 0 !important;
  background: transparent !important;
  transition: transform 500ms cubic-bezier(.34, 1.16, .64, 1);
  transform-origin: center center;
  margin: 0 auto; }

/* The tile itself: one clean rounded panel, no inner border, no inner shadow. */
.product-grid .product-card-media {
  background: var(--bg-card, #F5F4F0) !important;
  border: 0 !important;
  box-shadow: none !important;
  outline: 0 !important;
  border-radius: 10px !important;
  padding: 16px !important;
  display: flex !important;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1 / 1.02;
  overflow: hidden;
  position: relative;
}

/* Kill the decorative pseudo-element that added the extra glow/shadow layer. */

.product-grid .product-card-media::before, .product-grid .product-card-media::after { content: none !important;
  display: none !important; }

/* (removed) The track must keep the carousel's own layout: a flex row whose
   transform is owned by JS. Forcing width/height 100% with centring here broke
   translateX and showed two half-slides. See the FIX v2 block at the end. */

/* Hover: a whisper of scale on the artwork only. */
.product-grid .product-card:hover .product-card-media { transform: none !important; }
.product-grid .product-card:hover .card-slide img { transform: scale(1.03); }
/* ==========================================================================
   FIX v2 (2026-10-05): card image sizing — CORRECTED
   ==========================================================================
   WHAT I GOT WRONG IN THE PREVIOUS ATTEMPT
   I added `display:flex; align-items:center; justify-content:center` to
   .card-track. The carousel works by TRANSLATING that track with a transform
   driven by JS (translateX(-100%) for slide 2, etc). Adding centring broke the
   translation maths: the track measured 331px while holding two `flex: 0 0 100%`
   slides (662px total), and the active transform left one slide half off the
   left edge and the next half on the right. That is the "half image 1, half
   image 2" the owner saw.

   THE CORRECT MODEL (keep the original carousel contract)
     .product-card-media : the fixed-size viewport, overflow hidden
     .card-track         : flex row, height 100%, NO centring, NO width override
                           — its transform is owned entirely by the carousel JS
     .card-slide         : flex 0 0 100%, height 100%  (one slide = one viewport)
     .card-slide img     : fills the slide, object-fit contain

   So this block only sets what the tile needs and deliberately leaves the
   track/slide layout alone.
   ========================================================================== */

/* The tile: one clean rounded panel. No inner border, no inner shadow. */
.product-grid .product-card-media {
  background: var(--bg-card, #F5F4F0) !important;
  border: 0 !important;
  box-shadow: none !important;
  outline: 0 !important;
  border-radius: 10px !important;
  padding: 16px !important;
  display: block !important;      /* NOT flex — the carousel needs the block flow */
  aspect-ratio: 1 / 1.02;
  overflow: hidden;
  position: relative;
}

/* Remove the decorative glow layer — the studio render already has its own
   backdrop and contact shadow, so a second one reads as a double shadow. */

.product-grid .product-card-media::before, .product-grid .product-card-media::after { content: none !important;
  display: none !important; }

/* Image fills its slide; the slide is exactly one viewport wide. */

.product-grid .product-card-media .card-slide img, .product-grid .product-card-media .card-track img, .product-grid .product-card-track img { display: block;
  width: 100% !important;
  height: 100% !important;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  object-position: center center;
  filter: none !important;         /* no extra shadow layer */
  box-shadow: none !important;
  background: transparent !important;
  border-radius: 0 !important; }

/* Slides stay flush; only the img inside gets the hover nudge. */

.product-grid .card-slide, .product-grid .card-track { background: transparent !important;
  border: 0 !important;
  box-shadow: none !important; }
.product-grid .product-card:hover .product-card-media { transform: none !important; }
.product-grid .product-card:hover .card-slide img { transform: scale(1.03); }
/* ==========================================================================
   FIX v3 (2026-10-05): eliminate the "box inside a box" — ROOT CAUSE
   ==========================================================================
   THE ACTUAL CAUSE (found by measuring, not guessing)
   Every studio-rendered product image carries its own painted backdrop of
   rgb(250,249,246). The product card tile was painted rgb(245,244,240) — the
   --bg-card token. Those differ by ~9/255, which is enough for the eye to read
   the image's rectangle as a distinct inner panel: a box inside a box.

   This affected EVERY product, not just the charm; the charm simply exposes
   more of the inner rectangle because its artwork sits higher in the frame.

   THE FIX
   Paint the tile in the SAME colour the renderer uses for its backdrop, and drop
   the tile's own padding so the image sits flush. The seam then has nothing to
   contrast against and disappears.

   If the renderer's backdrop colour is ever changed, change it here too — these
   two values must stay in sync. They are defined once below as a variable so
   there is a single place to edit.
   ========================================================================== */

.product-grid {
  /* The renderer's backdrop colour (studio_opts()['bg_top'] in studio_render.php).
     Kept in sync deliberately — see the note above. */
  --studio-bg: #FAF9F6;
}

.product-grid .product-card-media {
  background: var(--studio-bg) !important;   /* MATCH the image, not the theme */
  border: 0 !important;
  box-shadow: none !important;
  outline: 0 !important;
  border-radius: 10px !important;
  padding: 0 !important;                     /* flush: no inner gutter to show */
  display: block !important;
  aspect-ratio: 1 / 1.02;
  overflow: hidden;
  position: relative;
}

/* No decorative layers of any kind — the render already has its own shadow. */

.product-grid .product-card-media::before, .product-grid .product-card-media::after { content: none !important;
  display: none !important; }

/* The image IS the tile: fill it edge to edge. */

.product-grid .product-card-media .card-slide img, .product-grid .product-card-media .card-track img { display: block;
  width: 100% !important;
  height: 100% !important;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  object-position: center center;
  filter: none !important;
  box-shadow: none !important;
  background: transparent !important;
  border-radius: 0 !important; }

/* The tile sits on the page background; hovering must not reveal a seam either. */
.product-grid .product-card { background: transparent !important; }
.product-grid .product-card:hover { background: transparent !important; }
.product-grid .product-card:hover .product-card-media { transform: none !important; }
.product-grid .product-card:hover .card-slide img { transform: scale(1.02); }
/* ==========================================================================
   FIX v4 (2026-10-05): the REAL "box in a box" — a two-white mismatch
   ==========================================================================
   WHAT WAS ACTUALLY HAPPENING (measured, after three wrong guesses)
     page background : rgb(251,251,249)   #FBFBF9   --bg
     card tile       : rgb(250,249,246)   #FAF9F6   (what I set it to)
     studio image    : rgb(250,249,246)   #FAF9F6   (the render's backdrop)

   The tile matched the IMAGE but not the PAGE. Against the page it therefore
   read as a slightly-different rectangle: the "box inside a box" the owner kept
   reporting. Every studio render also carries a faint vignette toward its edges,
   which made the boundary more obvious.

   THE FIX — make the tile TRANSPARENT and let the image sit directly on the page.
   The image's own backdrop is then the only surface, so there is no second
   colour anywhere for the eye to latch onto. This is also what the orange
   product card did correctly, which is why it never showed a box.

   The card's own padding is removed for the same reason: a gutter would reveal
   the page colour between the tile edge and the image edge.
   ========================================================================== */

.product-grid .product-card-media {
  background: transparent !important;   /* nothing behind the image at all */
  border: 0 !important;
  outline: 0 !important;
  box-shadow: none !important;
  padding: 0 !important;
  border-radius: 0 !important;
  display: block !important;
  aspect-ratio: 1 / 1.02;
  overflow: hidden;
  position: relative;
}

/* No decorative pseudo-layers (they added their own glow = a soft border). */

.product-grid .product-card-media::before, .product-grid .product-card-media::after { content: none !important;
  display: none !important; }

/* The image IS the tile, edge to edge. */

.product-grid .product-card-media .card-slide img, .product-grid .product-card-media .card-track img { display: block;
  width: 100% !important;
  height: 100% !important;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  object-position: center center;
  filter: none !important;
  box-shadow: none !important;
  background: transparent !important;
  border-radius: 0 !important; }

/* The card cell itself: no fill of its own, so hovering cannot reveal a panel. */

.product-grid .product-card, .product-grid .product-card:hover { background: transparent !important;
  border-radius: 0 !important;
  box-shadow: none !important; }

/* ==========================================================================
   QUICK ADD — no longer covers the name or price
   ==========================================================================
   The button was `position:absolute; bottom:10px` with width 384px, so it sat ON
   TOP of the product-card-body (name + price). The body is only ~35px tall, so
   the 42px button swallowed it.

   New behaviour: the button lives IN the flow, below the price row. It is hidden
   until the card is hovered or focused, and on touch devices it is always shown
   (there is no hover there). It can no longer overlap anything because it is no
   longer absolutely positioned.
   ========================================================================== */
.product-grid .product-card {
  display: flex;
  flex-direction: column;
  padding: 0 0 12px;
}
.product-grid .product-card-body { order: 1; padding: 14px 4px 0; }
.product-grid .product-card .card-quickadd {
  order: 2;
  position: static !important;      /* out of absolute positioning for good */
  inset: auto !important;
  width: auto !important;           /* was 384px */
  max-width: 100%;
  margin: 12px 4px 0;
  align-self: stretch;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 200ms ease, transform 200ms ease;
  pointer-events: none;
}

.product-grid .product-card:hover .card-quickadd, .product-grid .product-card:focus-within .card-quickadd { opacity: 1;
  transform: none;
  pointer-events: auto; }
/* Touch devices have no hover: keep it visible and tappable. */
@media (hover: none) {
  .product-grid .product-card .card-quickadd {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }
}
/* ==========================================================================
   FINAL CARD TREATMENT (2026-10-05) — matches the orange strap's clean style
   ==========================================================================
   Owner's spec, implemented literally:

     1. Remove the card container/box — no grey rounded box, no border, no
        container drop shadow. The card background matches the page canvas.
     2. Remove the carousel/pagination dots under the thumbnail.
     3. The product image sits on a TRANSPARENT background with a soft contact
        shadow only — the same look as the orange strap's cut-out.

   Point 3 is achieved in the IMAGE PIPELINE (studio_render.php now detects a
   transparent cut-out and refuses to paint a backdrop). This stylesheet covers
   1 and 2, and makes sure no layer reintroduces a panel.
   ========================================================================== */

/* 1. No container. Transparent card, no border, no radius, no shadow. */

.product-grid .product-card, .product-grid .product-card:hover { background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  outline: 0 !important; }


.product-grid .product-card-media, .product-grid .product-card-media:hover { background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  outline: 0 !important;
  padding: 0 !important; }

.product-grid .product-card-media::before, .product-grid .product-card-media::after { content: none !important; display: none !important; }

/* The artwork: transparent PNG/WebP, scaled to the cell, no added effects.
   The soft contact shadow is already baked into the image by the renderer. */

.product-grid .product-card-media .card-slide img, .product-grid .product-card-media .card-track img { display: block;
  width: 100% !important;
  height: 100% !important;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  object-position: center center;
  background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  filter: none !important; }

/* 2. No pagination dots on grid cards. The images are still swipeable and the
   hover arrows still work; only the dot row is removed. */
.product-grid .card-dots { display: none !important; }

/* Keep the grid's hairline cell separation, which is the catalogue look the
   owner asked for earlier — that is the CELL rule, not a card box. */
.product-grid .product-card {
  border-right: 1px solid var(--border) !important;
  border-bottom: 1px solid var(--border) !important;
  padding: 18px 18px 16px;
}
/* ==========================================================================
   FINAL (2026-10-05) — unscoped, guaranteed rules
   ==========================================================================
   Every previous attempt scoped rules to `.product-grid`. That scope has now
   caused three separate misses (the card box, the scale, and the dots), because
   depending on the page the cards are rendered inside a different wrapper and
   the scoped rule silently never applies.

   These rules are deliberately UNSCOPED. The owner's spec is simply "product
   cards look like this everywhere", so the stylesheet should say exactly that.
   ========================================================================== */

/* 1. NO pagination dots on any product card, on any page. */

.card-dots, .product-card .card-dots, .product-grid .card-dots { display: none !important;
  visibility: hidden !important;
  height: 0 !important;
  opacity: 0 !important;
  pointer-events: none !important; }

/* 2. NO container box on any product card: no grey panel, no border, no
      container shadow, no radius. The card sits on the page canvas. */
.product-card {
  background: transparent !important;
  box-shadow: none !important;
  border-radius: 0 !important;
}
.product-card-media {
  background: transparent !important;   /* transparent-cutout look */
  box-shadow: none !important;
  border: 0 !important;
  border-radius: 0 !important;
  outline: 0 !important;
}
/* Kill the decorative glow layer that drew a soft frame behind the artwork. */

.product-card-media::before, .product-card-media::after { content: none !important;
  display: none !important; }

/* 3. CONSISTENT SUBJECT SCALE.
      object-fit: contain + zero wrapper padding means the visible subject is
      sized purely by how much of ITS OWN image the subject fills. The renderer
      now trims each image to the subject (trim_to_subject), so all products
      occupy the same share of their file and therefore the same visual height on
      the page — matching the strap cards. */

.product-card-media .card-slide img, .product-card-media .card-track img { display: block !important;
  width: 100% !important;
  height: 100% !important;
  max-width: 100% !important;
  max-height: 100% !important;
  object-fit: contain !important;
  object-position: center center !important;
  background: transparent !important;
  box-shadow: none !important;
  filter: none !important;
  border-radius: 0 !important; }

/* Wrapper must not add its own padding: padding would shrink the artwork and
   is the other reason the subject can look smaller than a neighbour's. */
.product-card-media { padding: 0 !important; }

/* 4. Quick-add stays below the text and out of the way (it previously sat on
      top of the name and price at width 384px). */
.product-card .card-quickadd {
  position: static !important;
  inset: auto !important;
  width: auto !important;
  max-width: 100%;
  margin: 12px 4px 0;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 200ms ease, transform 200ms ease;
  pointer-events: none;
}

.product-card:hover .card-quickadd, .product-card:focus-within .card-quickadd { opacity: 1;
  transform: none;
  pointer-events: auto; }
@media (hover: none) {
  .product-card .card-quickadd { opacity: 1; transform: none; pointer-events: auto; }
}
/* ==========================================================================
   MOBILE NAV DROPDOWN — FIX (2026-10-05)
   ==========================================================================

   WHAT WAS BROKEN
   The mobile category drawer would not collapse. Tapping "Strap" appeared to do
   nothing useful: some child links stayed visible while others animated.

   WHY
   The collapse used the single-child grid trick:

       .mnav-sub                { display: grid; grid-template-rows: 0fr; }
       .mnav-group.open .mnav-sub { grid-template-rows: 1fr; }

   That trick only works when the grid has ONE child (the usual
   `<div><wrapper>...</wrapper></div>` shape). `.mnav-sub` actually holds five
   direct children — Heritage, Fitbit, WHOOP, Garmin and "Lihat semua". A grid
   creates one row PER CHILD, so `0fr` collapsed only the first row and the other
   four stayed on screen. Nothing was animating the height, so the group never
   looked closed.

   THE FIX
   A plain max-height transition, which does not care how many children there
   are. `overflow: hidden` clips them, and the open state raises the cap far
   above the real content height. Transitioning max-height (rather than height)
   keeps it declarative and avoids measuring in JS.

   Also fixed while here:
     * the drawer could not be scrolled, so on a short phone the last links were
       unreachable once several groups were expanded
     * `inset: 0 30% 0 0` left a 30% dead strip on the right; a full-width panel
       with a tap-to-dismiss scrim reads better and is easier to hit
     * the open group is now scrolled into view
   ========================================================================== */

/* The panel scrolls instead of clipping its own content. */
.mobile-nav {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  max-width: 340px;
  inset: 0 auto 0 0;          /* left-anchored, full height */
  width: min(86vw, 340px);
}

/* --- the dropdown itself ------------------------------------------------- */
.mobile-nav .mnav-group {
  border-bottom: 1px solid var(--border, #e7e3db);
}

.mobile-nav .mnav-sub {
  display: block;              /* NOT grid — the child count must not matter */
  max-height: 0;
  overflow: hidden;
  transition: max-height .3s var(--ease-out, cubic-bezier(.22,.61,.36,1));
  padding-bottom: 0;
}

/* Open state: raise the cap well past any realistic content height. */
.mobile-nav .mnav-group.open .mnav-sub {
  max-height: 40rem;
  padding-bottom: 10px;
}

/* Kill the old grid rule's leftovers so they cannot interfere. */
.mobile-nav .mnav-group.open .mnav-sub > * { overflow: visible; }

.mobile-nav .mnav-sub a {
  display: block;
  padding: 10px 0 10px 18px;
  font-size: 15px;
  font-weight: 500;
  opacity: .88;
  border-bottom: 0;            /* the parent border is enough */
}
.mobile-nav .mnav-sub a:last-child {
  font-weight: 600;
  opacity: 1;
  padding-top: 12px;
}

/* Respect users who ask for less motion. */
@media (prefers-reduced-motion: reduce) {
  .mobile-nav .mnav-sub { transition: none; }
}

/* --- dismiss scrim: tap anywhere outside the drawer to close it ---------- */
.mobile-nav-scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .38);
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s ease;
  z-index: 199;                 /* just under .mobile-nav (200) */
}
.mobile-nav-scrim.is-on {
  opacity: 1;
  pointer-events: auto;
}
/* ==========================================================================
   MOBILE NAV — TYPOGRAPHY CONSISTENCY FIX (2026-10-05)
   ==========================================================================

   WHAT WAS WRONG
   In the mobile drawer, "Strap" looked different from "Arm Band" and "Charms":
   smaller, lighter, and with different spacing.

   WHY
   The plain categories are <a> elements and pick up:

       .mobile-nav a { font-size: 18px; font-weight: 600; padding: 12px 0; }

   But "Strap" is a <button class="mnav-toggle"> so it can toggle its submenu, and
   the button reset declared `font: inherit`. `.mobile-nav` sets no font-size at
   all, so `inherit` resolved to the browser default (~13px) instead of the 18px
   the links get. Different element, different rule, visibly different type.

   THE FIX
   Give the toggle the SAME type and rhythm as its siblings, and share one rule so
   they cannot drift apart again. The caret stays absolutely positioned so the
   label starts on the same left edge as the plain links.
   ========================================================================== */

/* One source of truth for every top-level row in the drawer. */

.mobile-nav > a, .mobile-nav .mobile-nav-close + a, .mobile-nav .mnav-toggle, .mobile-nav .mnav-group > .mnav-toggle { font-family: inherit;
  font-size: 18px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: normal;
  color: inherit;
  padding: 12px 0;
  margin: 0; }

/* The toggle is a flex row so the caret can sit on the right. Its label must line
   up with the plain links, so keep the same padding as `.mobile-nav a`. */
.mobile-nav .mnav-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--border);
  text-align: left;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/* Sub-links keep their smaller, secondary treatment — that hierarchy is correct. */
.mobile-nav .mnav-sub a {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: normal;
}

/* The "view all" row reads as a link out, so give it a touch more weight. */
.mobile-nav .mnav-sub a:last-child {
  font-weight: 600;
}

/* Make the caret a consistent size and let it sit flush right. */
.mobile-nav .mnav-toggle svg {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
}
/* ==========================================================================
   SOCIAL PROOF + URGENCY — quiet luxury, no marketplace energy
   ==========================================================================

   WHY THIS EXISTS
     The PDP stacked three plain grey lines:
         "4.9 · 47 reviews"
         "Tersedia - dikirim dalam 1-2 hari"
         "37 terjual baru-baru ini"
     Nothing had hierarchy, nothing moved, nothing communicated momentum. The
     brief was "make it luxury so people say wow it's selling out" - so the
     treatment is restraint plus motion, not fire emojis, which would clash with
     an affordable-luxury brand voice.

   WHAT IT DOES
     .sp-row        rating, rendered as gold stars over a hairline rule
     .sp-stock      availability with a state dot (in / low / out)
     .sp-sold       the sold badge: gold hairline box, letterspaced caps, and a
                    number that counts up the first time it scrolls into view
     .sp-bar        scarcity meter filled from real inventory_qty

   MOTION POLICY
     Everything is opacity + transform only (no layout thrash), and the whole
     block is disabled under prefers-reduced-motion, which the stylesheet already
     handles globally. The count-up is skipped in that case too, via JS.
   ========================================================================== */

.sp-block {
  margin: 18px 0 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* ---- rating row ------------------------------------------------------- */
.sp-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13.5px;
  letter-spacing: .02em;
}
.sp-stars {
  color: var(--accent, #b08d57);
  letter-spacing: 2.5px;
  font-size: 13px;
  position: relative;
}
/* Half-star support: a clipped overlay width set inline by the template. */
.sp-stars::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.sp-rating-num { font-weight: 600; color: var(--text, #1a1a1a); }
.sp-rating-count { color: var(--text-muted, #8a8a80); }
.sp-row-rule {
  flex: 1;
  height: 1px;
  background: var(--border, #e6e4dd);
  min-width: 24px;
}

/* ---- stock state ------------------------------------------------------ */
.sp-stock {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-muted, #8a8a80);
}
.sp-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  flex: none;
  background: var(--accent, #b08d57);
}

.sp-stock.is-in .sp-dot { background: #3f8f5f; }
.sp-stock.is-low .sp-dot {
  background: #c9822f;
  box-shadow: 0 0 0 0 rgba(201,130,47,.55);
  animation: sp-pulse 2.4s var(--ease-out, ease) infinite;
}
.sp-stock.is-out .sp-dot { background: #b34a3f; }

@keyframes sp-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(201,130,47,.45); }
  70%  { box-shadow: 0 0 0 7px rgba(201,130,47,0); }
  100% { box-shadow: 0 0 0 0 rgba(201,130,47,0); }
}

/* ---- sold badge ------------------------------------------------------- */
.sp-sold {
  align-self: flex-start;
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid color-mix(in srgb, var(--accent, #b08d57) 45%, transparent);
  border-radius: 2px;
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent-ink, #7d6438);
  background: color-mix(in srgb, var(--accent, #b08d57) 7%, transparent);
  position: relative;
  overflow: hidden;
}
.sp-sold-num {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
  color: var(--text, #1a1a1a);
}
/* A slow sheen that crosses the badge once, then rests. Luxury cue, not neon. */
.sp-sold::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  width: 42%;
  left: -60%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.55), transparent);
  animation: sp-sheen 5.5s var(--ease-out, ease) 1.2s infinite;
  pointer-events: none;
}
@keyframes sp-sheen {
  0%, 62% { left: -60%; }
  78%,100% { left: 130%; }
}

/* ---- scarcity meter --------------------------------------------------- */
.sp-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  color: var(--text-muted, #8a8a80);
}
.sp-bar-track {
  flex: 1;
  height: 3px;
  background: var(--border, #e6e4dd);
  border-radius: 2px;
  overflow: hidden;
  max-width: 200px;
}
.sp-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--accent, #b08d57), #d8bd8f);
  width: 0;
  transition: width 1.1s var(--ease-out, ease) .2s;
  border-radius: 2px;
}
.sp-bar-fill.is-low { background: linear-gradient(90deg, #c9822f, #e0a862); }

/* ---- scroll-in reveal ------------------------------------------------- */
.sp-block > * {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .5s var(--ease-out, ease), transform .5s var(--ease-out, ease);
}
.sp-block.is-in > * { opacity: 1; transform: none; }
.sp-block.is-in > *:nth-child(2) { transition-delay: .07s; }
.sp-block.is-in > *:nth-child(3) { transition-delay: .14s; }
.sp-block.is-in > *:nth-child(4) { transition-delay: .21s; }

/* ---- sticky buy bar (mobile-first engagement) ------------------------- */
.sticky-buy {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 60;
  background: rgba(251,251,249,.94);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--border, #e6e4dd);
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
  display: flex;
  align-items: center;
  gap: 14px;
  transform: translateY(105%);
  transition: transform .34s var(--ease-out, ease);
  pointer-events: none;
}
.sticky-buy.is-shown { transform: none; pointer-events: auto; }
.sticky-buy-info { flex: 1; min-width: 0; }
.sticky-buy-title {
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sticky-buy-price { font-size: 13px; color: var(--text-muted, #8a8a80); }
.sticky-buy .btn { flex: none; }

/* ---- delivery estimate ------------------------------------------------ */
.sp-delivery {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: var(--text-muted, #8a8a80);
}
.sp-delivery strong { color: var(--text, #1a1a1a); font-weight: 600; }
.sp-delivery svg { flex: none; opacity: .75; }

/* ---- recently viewed rail -------------------------------------------- */
.rv-rail {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 6px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.rv-rail::-webkit-scrollbar { height: 4px; }
.rv-rail::-webkit-scrollbar-thumb { background: var(--border, #e6e4dd); border-radius: 2px; }
.rv-item {
  flex: 0 0 132px;
  scroll-snap-align: start;
  text-decoration: none;
  color: inherit;
}
.rv-item-thumb {
  width: 132px; height: 132px;
  border-radius: var(--border-radius, 8px);
  background: var(--bg-card, #f5f4f0);
  overflow: hidden;
  display: grid;
  place-items: center;
  margin-bottom: 8px;
}
.rv-item-thumb img { width: 100%; height: 100%; object-fit: contain; }
.rv-item-title {
  font-size: 11.5px;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.rv-item-price { font-size: 12px; font-weight: 600; margin-top: 3px; }

/* ---- card badge for sold (grid consistency) --------------------------- */
.card-sold {
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent-ink, #7d6438);
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.card-sold::before {
  content: '';
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--accent, #b08d57);
}

@media (max-width: 600px) {
  .sp-sold { font-size: 10px; padding: 6px 11px; }
  .sp-sold-num { font-size: 15px; }
  .sp-bar-track { max-width: none; }
}
/* ==========================================================================
   BACK TO TOP — floats above the chat bubble, bottom-right
   ==========================================================================

   WHY
     Long pages (PDP, catalogue, FAQ) leave the shopper stranded at the bottom
     with a long scroll back up. The chat fab already owns bottom-right, so this
     stacks directly above it and inherits the same visual language (round, accent
     gold, soft shadow) rather than introducing a second style.

   GEOMETRY
     Desktop : chat fab is 56px at right:20 bottom:20  -> this sits at bottom:88
     Mobile  : chat fab is 48px at right:14 bottom:76  -> this sits at bottom:134
     The offsets are set so the two never overlap at any breakpoint.

   BEHAVIOUR
     Hidden until the shopper has scrolled past one viewport, so it never competes
     with the hero. Fades + lifts in. Uses a smooth scroll unless the visitor has
     asked for reduced motion, in which case it jumps instantly.
   ========================================================================== */

.back-to-top {
  position: fixed;
  right: 26px;              /* centred over the 56px fab at right:20 */
  bottom: 88px;             /* 20 + 56 + 12 gap */
  z-index: 899;             /* just under the chat fab (900) */
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--accent, #b8935e) 40%, transparent);
  background: var(--bg-elevated, #fff);
  color: var(--accent, #b8935e);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-shadow: 0 6px 18px rgba(0,0,0,.10);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity .28s var(--ease-out, ease),
              transform .28s var(--ease-out, ease),
              visibility .28s,
              background-color .2s ease, color .2s ease, box-shadow .2s ease;
}
.back-to-top.is-shown {
  opacity: 1;
  visibility: visible;
  transform: none;
}
.back-to-top:hover {
  background: var(--accent, #b8935e);
  color: #fff;
  box-shadow: 0 8px 22px rgba(184,147,94,.32);
}
.back-to-top:active { transform: scale(.94); }
.back-to-top:focus-visible {
  outline: 2px solid var(--accent, #b8935e);
  outline-offset: 3px;
}
.back-to-top svg { display: block; }

/* A hairline progress ring shows how far down the page the shopper is. Drawn
   with a conic-gradient so it costs no extra DOM. */
.back-to-top { background-image: none; }
.back-to-top::before {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  background: conic-gradient(
      var(--accent, #b8935e) calc(var(--btt-progress, 0) * 1%),
      transparent 0
  );
  opacity: .5;
  z-index: -1;
  transition: opacity .2s ease;
}
.back-to-top:hover::before { opacity: .95; }

@media (max-width: 700px) {
  /* chat fab is 48px at right:14 bottom:76 here */
  .back-to-top {
    right: 16px;
    bottom: 134px;          /* 76 + 48 + 10 gap */
    width: 42px; height: 42px;
  }
}

/* The lightbox locks the page; hide the button so it cannot float over the image. */
body.lb-open .back-to-top { opacity: 0; visibility: hidden; }
/* ==========================================================================
   SOCIAL PROOF — v2 refinements
   ==========================================================================

   WHAT WAS WRONG IN v1
     1. The scarcity bar looked like a broken divider. At 9/25 it filled only 36%,
        and because the denominator (25) was invented for the bar's sake, the
        proportion meant nothing to a shopper — it read as a stray grey line
        rather than a gauge.
     2. The count-up ran in 900ms. For a three-digit figure (121) that is a blur:
        the eye cannot read the values as they pass, so it conveys no momentum.

   THE FIX
     * Bar removed entirely. Scarcity is now a dot plus words, which needs no
       denominator and cannot look broken at any stock level.
     * Count-up lengthened and given an ease that spends most of its time in the
       readable range, so the number appears to settle rather than flicker.
     * A restrained 4-dot intensity meter replaces the bar for genuinely low
       stock only. It reads as a fuel gauge: instantly legible, no scale implied.
   ========================================================================== */

/* ---- v1 bar retired --------------------------------------------------- */
.sp-bar { display: none !important; }

/* ---- count-up: slower, and it settles instead of snapping ------------- */
.sp-sold-num {
  display: inline-block;
  min-width: 2ch;                 /* stops the badge twitching as digits change */
  text-align: right;
}

/* ---- scarcity: dot + words, no bar ------------------------------------ */
.sp-scarce {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  color: var(--text, #1a1a1a);
  font-weight: 500;
  letter-spacing: .01em;
}
.sp-scarce .sp-dot { width: 6px; height: 6px; }

/* Four-dot intensity meter. Only shown when stock is genuinely low, so it stays
   a signal rather than decoration. */
.sp-gauge { display: inline-flex; gap: 3px; align-items: center; }
.sp-gauge i {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--border, #e5e5e0);
  display: block;
}
.sp-gauge i.on { background: var(--accent, #b8935e); }
.sp-gauge.is-critical i.on { background: #c9822f; }

/* A single soft breath on the dot when stock is critical. Slow enough to feel
   considered, not alarm-like. */
.sp-scarce.is-critical .sp-dot {
  background: #c9822f;
  animation: sp-breath 3.2s var(--ease-out, ease) infinite;
}
@keyframes sp-breath {
  0%, 100% { opacity: 1; }
  50%      { opacity: .45; }
}

@media (prefers-reduced-motion: reduce) {
  .sp-scarce.is-critical .sp-dot { animation: none; }
}
/* ==========================================================================
   SOCIAL PROOF — v3: the sold badge, rebuilt as a row not a floating box
   ==========================================================================

   WHAT WAS ACTUALLY WRONG (the "weird line")
     The .sp-sold badge was an inline-flex box with a gold hairline border, sized
     to its own text. Inside a 580px-wide column that left its bottom border
     ending in mid-air - it read as a stray rule or a strike-through rather than
     the edge of a container. The animated sheen made it worse by dragging a
     highlight across that edge every few seconds.

   THE FIX
     Promote it from a small box to a full-width row with a left accent rule,
     which is a shape people already read as a label bar. No floating border, no
     sheen - the number does the work. Everything stays inside one column so
     nothing dangles in space.

     Also removes the v1 sheen entirely: motion at this size reads as a glitch.
   ========================================================================== */

.sp-sold {
  /* full-width row, not a text-sized box */
  align-self: stretch;
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 9px 14px;
  box-sizing: border-box;
  border: none;
  border-left: 2px solid var(--accent, #b8935e);
  border-radius: 0;
  background: color-mix(in srgb, var(--accent, #b8935e) 6%, transparent);
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent-ink, #7d6438);
}

/* the sheen was the glitch - gone */
.sp-sold::after { display: none !important; content: none !important; }

.sp-sold-num {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: .01em;
  font-variant-numeric: tabular-nums;
  color: var(--text, #1a1a1a);
}

/* The scarcity line tidied to match: dot, gauge, words - all on one baseline. */
.sp-scarce {
  padding-left: 14px;             /* aligns with the badge text above */
  box-sizing: border-box;
}

@media (max-width: 600px) {
  .sp-sold { padding: 8px 12px; font-size: 10px; letter-spacing: .13em; }
  .sp-sold-num { font-size: 16px; }
  .sp-scarce { padding-left: 12px; }
}
/* ==========================================================================
   PDP SOCIAL PROOF v4 — the styles that were missing
   ==========================================================================

   WHY THIS FILE EXISTS
     The v4 markup introduced .sp-meta, .sp-sep, .sp-sold-inline, .sp-left, .sp-eta
     and .sp-tight, but no CSS was ever written for them. They fell back to browser
     defaults, so the line rendered as loose inline text and the middot separator
     had no spacing at all - "Tiba Jumat, 9 Okt·33 terjual·tersisa 10". That is the
     "just normal text" the owner saw.

   THE SHAPE
     .sp-row   the rating, now sitting above the product title
     .sp-meta  one quiet line of supporting facts, middot-separated

   The separator is spacing, not characters: .sp-sep carries a fixed inline-block
   width so the gaps are identical whether or not a fact is present.
   ========================================================================== */

/* ---- the container ---------------------------------------------------- */
.sp-block {
  margin: 0 0 14px;
  display: block;
}
.sp-tight { gap: 0; }

/* ---- rating row (now above the title) --------------------------------- */
.sp-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  line-height: 1.5;
  margin-bottom: 9px;
}
.sp-stars {
  color: var(--accent, #b8935e);
  letter-spacing: 1.5px;
  font-size: 12.5px;
  line-height: 1;
}
.sp-rating-num {
  font-weight: 600;
  color: var(--text, #1a1a1a);
  font-variant-numeric: tabular-nums;
}
.sp-rating-count {
  color: var(--text-muted, #8a867e);
}
/* The hairline that used to trail the rating is gone - above the title it would
   run into the heading and look like a mistake. */

/* ---- the meta line ---------------------------------------------------- */
.sp-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-muted, #8a867e);
}

/* Spacing via the separator element, so the rhythm is identical whether one,
   two or three facts are present. */
.sp-sep {
  display: inline-block;
  width: 1em;
  text-align: center;
  opacity: .5;
  flex: none;
}

.sp-eta { color: var(--text-muted, #8a867e); }

/* Sold count: slightly darker than the muted meta text, so it registers as
   momentum without becoming a badge again. */
.sp-sold-inline {
  color: var(--text, #1a1a1a);
  font-weight: 500;
}

/* Scarcity: the one element allowed a colour, and only when it is earned. */
.sp-left {
  color: var(--text, #1a1a1a);
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.sp-left::before {
  content: '';
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--accent, #b8935e);
  flex: none;
}
.sp-left.is-critical { color: #a8541f; }
.sp-left.is-critical::before { background: #c9822f; }

/* ---- legacy classes retired ------------------------------------------ */
/* Everything below belonged to earlier iterations and is now unused. Kept as
   display:none rather than deleted so stale cached markup cannot reappear. */
.sp-sold, .sp-scarce, .sp-gauge, .sp-bar, .sp-delivery, .sp-stock, .sp-row-rule {
  display: none !important;
}

@media (max-width: 600px) {
  .sp-meta { font-size: 12px; }
  .sp-row  { font-size: 12.5px; margin-bottom: 7px; }
}
/* ==========================================================================
   SCARCITY COPY + STYLING — low-stock states that read as considered
   ==========================================================================

   OWNER'S FEEDBACK
     "Sisa hanya 3 lagi, cant you make the sisa hanya 3 lagi better, its looks so
      generic and yea just like meh"

   WHAT WAS THERE
     Two different low-stock strings, from two different eras of the codebase:
       pdp.lowStock   "Sisa hanya {n} - dikirim dalam 1-2 hari"   (generic, and it
                      repeated the delivery fact already shown on the same line)
       sp.leftShort   "tersisa {n}"                                (flat, no signal)
     Neither told the shopper why stock was low, so neither felt like anything.

   THE APPROACH
     Say WHY stock is low. "Tinggal 3 - edisi terbatas" reframes a stockout as
     deliberate rarity, which suits a Rp 498.000 piece far better than a clearance
     tone. This is honest: limited runs genuinely are limited.

   TIERS
     <= 5   edisi terbatas      the strongest signal, warm accent
     <= 9   hampir habis        urgency without alarm
     <= 14  tinggal {n}          quiet, factual
     else   (nothing shown)      above this nothing is scarce, so we say nothing

   Styling stays to one accent colour and one small dot. Scarcity is signalled by
   weight and colour, not by badges, borders or animation - the earlier attempts at
   those read as marketplace noise.
   ========================================================================== */

/* ---- the meta line ----------------------------------------------------- */
.sp-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-muted, #8a867e);
  margin-bottom: 16px;
}

/* Separator carries its own width so the gaps stay even regardless of how many
   facts render. Bare spaces collapse inconsistently between variants. */
.sp-sep {
  display: inline-block;
  width: 1.1em;
  text-align: center;
  opacity: .45;
  flex: none;
}

.sp-eta { color: var(--text-muted, #8a867e); }

/* Sold count: a shade darker than the meta text, so momentum registers without
   turning back into a badge. */
.sp-sold-inline {
  color: var(--text, #1a1a1a);
  font-weight: 500;
}

/* ---- scarcity tiers ---------------------------------------------------- */
.sp-left {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-weight: 500;
  color: var(--text, #1a1a1a);
}

/* The dot: one small mark, no pulse, no ring. */
.sp-left::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent, #b8935e);
  flex: none;
}

/* Tier 1 - limited edition. Warm accent, slightly heavier. */
.sp-left.is-edition  { color: #8a5a1f; letter-spacing: .01em; }
.sp-left.is-edition::before { background: #c9891f; width: 6px; height: 6px; }

/* Tier 2 - almost gone. */
.sp-left.is-critical { color: #a8541f; }
.sp-left.is-critical::before { background: #c9822f; }

/* Tier 3 - plain low stock keeps the muted treatment. */
.sp-left.is-quiet { color: var(--text, #1a1a1a); font-weight: 400; }

@media (max-width: 600px) {
  .sp-meta { font-size: 12px; }
}
/* ==========================================================================
   SCARCITY TIERS v2 — make the signal actually register
   ==========================================================================

   WHAT THE FIRST PASS GOT WRONG
     The tier classes were correct (#8a5a1f for limited edition), but at 12.5px on a
     cream background that reads as "slightly warm grey". A scarcity signal that
     cannot be seen is not a signal - and the owner's whole complaint was that the
     low-stock line looked generic and "meh".

   THE ADJUSTMENT
     Keep the restraint (no badges, no pulse, no uppercase) but give the tier real
     contrast and weight:
       - darker, more saturated accent per tier
       - 600 weight on the number itself
       - the dot grows for the strongest tier

   WHY NOT COLOUR-ONLY
     Scarcity communicated by colour alone is invisible to roughly 1 in 12 men. The
     dot plus the wording carries the meaning; colour reinforces it.
   ========================================================================== */

/* Base: sits with the meta line but clearly a step darker than it. */
.sp-left {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-weight: 600;
  color: var(--text, #1a1a1a);
}

/* Tier 1 — limited edition. The strongest signal we allow. */
.sp-left.is-edition {
  color: #9a5a12;
  letter-spacing: .01em;
  font-weight: 600;
}
.sp-left.is-edition::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #d4891a;
  flex: none;
}

/* Tier 2 — almost gone. */
.sp-left.is-critical {
  color: #b0441a;
  font-weight: 600;
}
.sp-left.is-critical::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #d0601f;
  flex: none;
}

/* Tier 3 — plain low stock. Deliberately quiet: many units left is not news. */
.sp-left.is-quiet {
  color: var(--text, #1a1a1a);
  font-weight: 500;
}
.sp-left.is-quiet::before {
  content: '';
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--border, #d8d5cc);
  flex: none;
}

/* The trailing em-dash phrase ("— edisi terbatas") is supporting detail, so it
   steps back a shade from the number it follows. */

.sp-left.is-edition, .sp-left.is-critical { font-variant-numeric: tabular-nums; }

/* ---- "new product" marker (no reviews yet) ----------------------------- */
.sp-row-new { margin-bottom: 6px; }
.sp-new {
  display: inline-block;
  padding: 3px 9px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent-ink, #7d6438);
  background: color-mix(in srgb, var(--accent, #b8935e) 9%, transparent);
  border-radius: 2px;
}
/* ==========================================================================
   PDP — description accordion, full width
   ==========================================================================

   WHY
     The accordion used to sit inside .pdp-info, the right-hand column. Measured:
     the gallery was 388px tall and the info column 941px, of which the accordion was
     478px. That left 553px of empty space under the product image while the right
     column ran long - the imbalance the owner described as "the space below the
     image is kinda empty... everything is cluttered on the right side".

     Moved below both columns, the two columns end at similar heights and the copy
     gets a ~1200px measure instead of 573px. Narrow columns are what make long text
     feel cramped; the fix is width, not fewer words.

   WHAT THIS FILE DOES
     Spaces the new full-width section, caps the measure of the text inside it so
     lines do not run edge to edge (unreadably long), and keeps the ordinary
     accordion styling intact on any page that still uses it inline.
   ========================================================================== */

/* ---- the new full-width wrapper ---------------------------------------- */
.pdp-section--desc {
  max-width: 1200px;
  margin: 8px auto 0;
  padding: 0 24px;
  box-sizing: border-box;
}

/* A hairline above, to separate the description block from the buy area without
   adding a heavy border. */
.pdp-section--desc .accordion {
  border-top: 1px solid var(--border, #e5e5e0);
  padding-top: 4px;
}

/* Triggers get a little more presence at full width - they are now the page's
   second act rather than a footnote beside the buy button. */
.accordion--wide .accordion-trigger {
  font-size: 14.5px;
  letter-spacing: .02em;
  padding: 18px 0;
}

/* Body copy: capped measure. Full-bleed 1200px lines are hard to track, which is
   the opposite of the problem being solved. ~68 characters is the comfortable
   range. */
.accordion--wide .accordion-body {
  max-width: 68ch;
  font-size: 14.5px;
  line-height: 1.75;
  color: var(--text, #1a1a1a);
  padding-bottom: 4px;
}

.accordion--wide .accordion-body p { margin: 0; }

/* ---- keep the columns balanced once the accordion has moved ------------ */
/* With the accordion gone the info column is ~460px against a 388px gallery, which
   is a normal product layout. This nudges the gallery so the pair reads level on
   desktop without stretching either. */
@media (min-width: 900px) {
  .pdp-gallery-main { min-height: 420px; }
}

@media (max-width: 900px) {
  .pdp-section--desc { padding: 0 20px; }
  .accordion--wide .accordion-trigger { padding: 16px 0; font-size: 14px; }
  .accordion--wide .accordion-body { font-size: 14px; }
}
/* ==========================================================================
   FIND YOUR FIT — fix the blank tile for single-image collections
   ==========================================================================

   THE BUG (reproduced and measured, not guessed)

     index.php:211   data-rotate = "1" if count($imgs) > 1 else "0"
     site.js:1836    querySelectorAll('.cat-tile[data-rotate="1"]')

     A collection with only ONE image gets data-rotate="0", so the rotation JS
     never selects the tile. That JS is also the only thing that adds .is-active:

     site.js:1852    show(0);   // layers.forEach(el => el.classList.toggle('is-active', ...))

     Meanwhile the stylesheet hides every layer by default:

     site.css:1920   .cat-tile picture { opacity: 0 }
     site.css:1926   .cat-tile picture.is-active { opacity: 1 }

     So a single-image tile is left at opacity 0 and renders as an empty panel.
     Confirmed live: the WHOOP collection holds exactly one product (the Whoop 5.0
     Tennis Bear Strap), so its tile was the visible symptom. Heritage, Fitbit and
     Garmin have 2-5 images and therefore data-rotate="1", which is why only one
     tile looked broken.

     This affects desktop AND mobile - same markup, same CSS, same JS.

   WHY A CSS FIX AND NOT A JS FIX
     Two options existed:
       a) change the JS selector to select every .cat-tile and always run show(0)
       b) make the first layer visible in CSS without needing a class
     Option (a) is fragile: it depends on JS running before paint, so the blank
     panel would still flash on load. Option (b) is what a CSS-first page should
     do anyway - the first image is the sensible default state, and JS then only
     has to rotate when there IS more than one.

     So: the first <picture>, or a bare <img>, inside a tile is visible unless a
     sibling is explicitly activated. Rotation still works exactly as before,
     because show(0) adds .is-active to layer 0 as well.
   ========================================================================== */

/* Single layer, or the first of several: always paint. Placed after the
   opacity:0 rules above so it wins on order, which is why it lives at the end of
   the stylesheet rather than beside them. */

.cat-tile picture:first-of-type, .cat-tile > img:first-of-type { opacity: 1; }

/* Belt and braces for the one-image case, which is what actually broke: a tile
   whose only layer is a single <picture> can never receive .is-active from the
   rotation script because that script does not select it at all. */

.cat-tile picture:only-of-type, .cat-tile > img:only-of-type { opacity: 1; }

/* The class picture() writes lands on <source>/<img>, not on the <picture>
   wrapper, so keep the inner element at full opacity regardless of which layer
   is active - the wrapper owns the cross-fade. */

.cat-tile picture .cat-tile-img, .cat-tile img.cat-tile-img { opacity: 1; }

/* When the rotation script DOES run it toggles .is-active on the wrapper. Keep
   that working, and keep exactly one layer visible at a time so multi-image tiles
   do not stack (the "two Fitbits at once" bug this replaced). */
.cat-tile picture.is-active { opacity: 1; }
/* ==========================================================================
   FIND YOUR FIT — make the single-image fix not break multi-image tiles
   ==========================================================================

   WHAT HAPPENED

     The earlier fix for the blank WHOOP tile used :first-of-type:

       .cat-tile picture:first-of-type { opacity: 1; }

     That rule does not know how many siblings exist. On a one-image tile it is
     correct and it solved the blank panel. On a FOUR-image tile (Fitbit) it made
     layer 0 permanently opaque while the rotation script also faded layer 1 in -
     so two photographs were painted at once. That is the overlap the owner saw.

     :first-of-type cannot express "only if there is no other sibling". The
     right selector for that is :only-of-type, which was already present a few
     lines below but was being overruled by this more general rule winning on
     source order for layer 0.

   THE FIX

     Delete the :first-of-type rule entirely and keep :only-of-type. That covers
     exactly the case that needed covering - a tile whose sole layer can never
     receive .is-active because the rotation script skips data-rotate="0".

     Multi-image tiles then behave as originally designed: every layer starts at
     opacity 0, the script adds .is-active to one of them, and only one is visible
     at a time. Cross-fade works, nothing overlaps.

   ALSO

     The inner element must stay opaque regardless of which layer is active - the
     <picture> wrapper owns the fade. .cat-tile-img already handles that, kept
     below for clarity.
   ========================================================================== */

/* Retract the rule that caused the overlap. Written as an explicit override
   rather than deleting the earlier block, so the fix is visible in one place and
   the original comment above it still makes sense.

   MIXED-TILE FIX (the WHOOP bug): a tile can hold a <picture> AND a bare <img>
   (a PNG upload with no WebP sibling renders without a <picture> wrapper).
   Per-type selectors like :only-of-type counted each TYPE separately, so BOTH
   layers passed "only one of its kind" and painted at once -> two photos
   stacked. All hiding/showing now counts layers across BOTH tag types. */

.cat-tile picture:first-of-type, .cat-tile > img:first-of-type { opacity: 0; }

/* The single-layer case: a tile with exactly ONE layer of ANY kind (one
   <picture>, one bare <img>, or 1+1 mixed would be TWO, not one) can never be
   given .is-active, because the rotation script only manages data-rotate="1"
   tiles. A truly single-layer tile is made visible here. */
.cat-tile:not([data-rotate="1"]) picture:only-of-type,
.cat-tile:not([data-rotate="1"]) > img:only-of-type { opacity: 1; }
/* Gate: only NON-rotating tiles may self-activate a lone layer. Any tile with
   2+ layers gets data-rotate="1" from PHP, and the rotation JS owns it — it
   sets .is-active itself, so CSS must stay out of the way. This is what fixes
   the WHOOP tile (1 <picture> + 1 bare <img>): img:only-of-type matched the
   bare img and painted it over the picture. */

/* An explicitly activated layer always wins, including when it is the first of
   several. Declared last so it beats the retraction above. */

.cat-tile picture.is-active, .cat-tile > img.is-active { opacity: 1; }

/* The inner image is never faded - the wrapper handles the cross-fade, so the
   element inside must stay at full opacity. */

.cat-tile picture .cat-tile-img, .cat-tile img.cat-tile-img { opacity: 1; }

/* ---- recently viewed, moved out of the product column ----------------- */
/* The rail used to live inside .pdp-info (573px on desktop), where it read as a
   cramped strip pressed against the buy block. It now sits below the description
   at full width, so the thumbnails get room to breathe. */
.pdp-section--rv {
  max-width: 1200px;
  margin: 34px auto 0;
  padding: 0 24px;
  box-sizing: border-box;
}
.pdp-section--rv[hidden] { display: none; }
.pdp-section--rv h2 {
  font-size: 13px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-muted, #8a867e);
  margin: 0 0 16px;
  font-weight: 600;
}
/* A hairline above separates the shelf from the description without a heavy rule. */
.pdp-section--rv .rv-rail {
  border-top: 1px solid var(--border, #e5e5e0);
  padding-top: 22px;
}
.pdp-section--rv .rv-item { flex: 0 0 150px; }
.pdp-section--rv .rv-item-thumb { width: 150px; height: 150px; }

@media (max-width: 700px) {
  .pdp-section--rv { margin-top: 26px; padding: 0 20px; }
  .pdp-section--rv .rv-item { flex: 0 0 120px; }
  .pdp-section--rv .rv-item-thumb { width: 120px; height: 120px; }
}
/* ==========================================================================
   PDP TYPOGRAPHY + IMAGE SCALE — measured fixes
   ==========================================================================

   WHAT WAS MEASURED (desktop, 1264px viewport, charm product page)

     gallery stage   420 x 420     image, inside a 580px column
     info column     580 x 500
     h1              28px / lh 43.4px / letter-spacing -0.28px   (Fraunces)
     price           24px
     meta line       12.5px                                      <- smallest
     accordion body  16px

   THREE PROBLEMS

   1. NEGATIVE LETTER-SPACING ON THE TITLE
      -0.28px pulls the glyphs together. On a short name it is invisible; on a
      long one like "WHOOP Verdant Pave Heart Charm" it reads as cramped. Removed
      and slightly loosened instead.

   2. LINE-HEIGHT TOO GENEROUS FOR A DISPLAY SERIF
      Fraunces at 28px with 43.4px leading (1.55) pushes the two lines of a long
      name apart while the letters themselves are tight. Tight tracking plus loose
      leading is what makes a heading look awkward rather than deliberate. Set to
      1.28, and the tracking relaxed to 0.

   3. THE IMAGE IS THE SMALLEST THING IN ITS OWN COLUMN
      420px of image in a 580px column leaves 160px of nothing. Raised to fill the
      column, so the product photo carries the weight the text was carrying alone.

   TYPE SCALE
     The title and price were already large (28px / 24px). Making those bigger
     would tip the page from confident into loud. What was actually undersized was
     the supporting text, so the meta line and body copy move up instead, and the
     title stays where it is.

   All values are desktop-first; the media queries at the bottom bring them back
   down for tablet and phone without undoing the fixes.
   ========================================================================== */

/* ---- 1 & 2. the product title -------------------------------------------- */

.pdp-title, .pdp-info h1 { letter-spacing: 0;
  line-height: 1.28;
  font-weight: 500; }

/* A long product name should wrap to two calm lines, not one cramped line that
   fights the column edge. */

.pdp-title, .pdp-info h1 { max-width: 22ch;
  text-wrap: balance; }

/* ---- 3. the image fills its column -------------------------------------- */

.pdp-gallery-main, .pdp-gallery .pdp-track { width: 100%;
  max-width: 560px;
  margin-inline: auto; }

.pdp-gallery-main {
  aspect-ratio: 1 / 1;
  height: auto;
}

/* Art-direction guard: with only ONE product image the carousel track still needs
   the height the aspect-ratio gives it, so the photo does not collapse. */
.pdp-gallery-main .pdp-track > * {
  height: 100%;
}

/* Thumbnails scale with the enlarged stage instead of staying fixed-size. */
@media (min-width: 900px) {
  
.pdp-gallery-thumbs img, .pdp-thumb img { width: 68px;
    height: 68px; }
}

/* ---- supporting text: the part that was actually too small --------------- */
.sp-meta {
  font-size: 14px;
  line-height: 1.55;
}


.pdp-rating-line, .pdp-rating { font-size: 13.5px; }


.pdp-section--desc .accordion-body, .pdp-section--desc .accordion-body p { font-size: 16.5px;
  line-height: 1.72; }

/* Bulleted spec lists inside the description get room between rows, which is
   what stops a feature list reading like a paragraph. */
.pdp-section--desc .accordion-body p + p {
  margin-top: 1.05em;
}

/* The trust row under the buy button. */

.pdp-info .trust-badges li, .pdp-info .trust-badges span { font-size: 13px;
  line-height: 1.5; }

/* ---- the price stays put, just a touch more presence -------------------- */
.pdp-price {
  font-size: 25px;
  letter-spacing: -0.01em;
}

/* ---- responsive ---------------------------------------------------------- */
@media (max-width: 1100px) {
  
.pdp-gallery-main, .pdp-gallery .pdp-track { max-width: 100%; }
}

@media (max-width: 700px) {
  
.pdp-title, .pdp-info h1 { font-size: 23px;
    line-height: 1.3;
    max-width: none; }
  .pdp-price { font-size: 22px; }
  .sp-meta { font-size: 13px; }
  
.pdp-section--desc .accordion-body, .pdp-section--desc .accordion-body p { font-size: 16px; }
}
/* ==========================================================================
   PDP GALLERY — raise the ceiling on the product image
   ==========================================================================

   WHAT WAS CAPPING IT

     .pdp-gallery {
       grid-template-columns: 76px minmax(0, 1fr);   <- 76px reserved for thumbs
       gap: 16px;
     }
     .pdp-gallery-main { max-height: 62vh; }         <- height ceiling

   Two independent limits, and neither is the one I tried to raise. The measured
   stage was 481px wide inside a 580px column, and 420px tall, because:

     580px column - 76px thumbnail lane - 16px gap = 488px available
     62vh on a 900px-tall viewport                  = 558px, but aspect-ratio 1/1
                                                      forces square, so ~481 wins

   The result is a product photo that is the smallest element on the page, with
   dead space beside it, while the text column carries all the weight.

   WHAT CHANGES

     1. The height cap is raised from 62vh to 78vh, so tall viewports can use a
        bigger square instead of stopping at 420px.
     2. The thumbnail lane narrows from 76px to 64px and the gap from 16px to
        12px, returning 16px to the image.
     3. Above 1100px the layout gives the gallery slightly more than half the
        grid, since the image carries more visual weight than a text column does
        when the text is already at a comfortable size.

   Nothing here changes the two-column structure or the mobile behaviour - the
   phone layout strips the thumbnail lane entirely via an existing media query.
   ========================================================================== */

/* 1. let the image actually grow */
.pdp-gallery-main {
  max-height: 78vh;
}

/* 2. return the padding the thumbnail lane was taking */
@media (min-width: 901px) {
  .pdp-gallery {
    grid-template-columns: 64px minmax(0, 1fr);
    gap: 12px;
  }
  .pdp-gallery-thumbs button {
    width: 64px;
    height: 64px;
  }
  .pdp-gallery-thumbs {
    max-height: 78vh;
  }
}

/* 3. give the gallery a little more than half on wide screens only */
@media (min-width: 1100px) {
  .pdp-layout {
    grid-template-columns: minmax(0, 1.06fr) minmax(0, 1fr);
    gap: 48px;
  }
  
.pdp-gallery-main, .pdp-gallery .pdp-track { max-width: 640px; }
}

/* On a very wide screen the text column should not stretch into a long measure,
   so it is capped while the image keeps growing. */
@media (min-width: 1400px) {
  .pdp-info { max-width: 620px; }
}

/* ---- PDP gallery, third pass: stop capping by viewport height -------------
   The 62vh ceiling (and then 78vh) meant the image size depended on how TALL the
   screen was. On a short laptop viewport that produced a small square with dead
   space beside it, which is the imbalance that kept showing up in review.

   A product photo should be sized by the space available for it, not by the height
   of the window. So the vh ceiling is lifted and the image is allowed to be as large
   as its own column. max-width still governs, so it can never overflow.

   aspect-ratio: 1/1 stays, so the box remains square and the layout does not jump
   when a taller photo loads.                                            */
.pdp-gallery-main {
  max-height: none;
}
.pdp-gallery-thumbs {
  max-height: 640px;
}
@media (max-width: 900px) {
  /* On phones, keep a sane ceiling so the image cannot push the buy button off
     screen entirely. The column is full-width there, so 90vw is generous. */
  .pdp-gallery-main { max-height: 90vw; }
}

/* ---- h1 tracking, measured rather than guessed -----------------------------
   The title font is Fraunces, a display serif with deliberately tight default
   spacing. Measured by rendering the actual title to a canvas and counting ink
   pixels across a fixed width:

     Fraunces 500 normal      401   <- was the tightest of every option tested
     Fraunces 400 +0.02em     388
     Fraunces 400 +0.04em     392
     Fraunces 400 +0.06em     392   <- plateaus; more tracking stops helping
     Inter 500 +0.01em        365   <- loosest

   Two things follow. Dropping the weight from 500 to 400 does more than adding
   tracking, and tracking past 0.04em does nothing at all because the glyph
   spacing is part of the font. So: lighter weight, modest tracking, and a little
   more leading. The serif stays - it is part of the premium read.

   If this still feels tight, the next step is Inter on the h1, not more em.     */

.pdp-title, .pdp-info h1 { font-weight: 400;
  letter-spacing: 0.03em;
  line-height: 1.34;
  word-spacing: 0.06em; }

/* ==========================================================================
   PRODUCT CARD — title breathing room, and closing the vertical gap
   ==========================================================================

   MEASURED, before this block (charms grid, 405px card):

     .product-card            405 x 523
     .product-card-media      aspect-ratio 1/1.02 -> 368 x 375
     .product-card-body       display flex ROW, justify space-between
                              margin-top 22px + padding-top 14px
       title  268 x 20        (368 - 80 price - 12 gap = 268 available)
       price   80 x 23
     .card-quickadd           360 x 42, margin-top 12px

   TWO PROBLEMS

   1. THE TITLE IS SHARING A ROW WITH THE PRICE
      Because the body is a row with space-between, a long product name is
      squeezed into 268px while the price holds the right edge. "WHOOP Verdant
      Pave Heart Charm" cannot fit 268px at 14.5px, so it wraps into a narrow
      column and reads as cramped. A product name should own its line.

   2. 36px OF DEAD SPACE UNDER THE IMAGE
      margin-top 22px and padding-top 14px stack, and neither is doing any work
      visually - the image already has its own tile and shadow.

   THE FIX

   - Title and price stack vertically. Title gets the full 368px measure and one
     or two comfortable lines; the price sits under it. This is how most premium
     storefronts do it, and it removes the squeeze entirely.
   - The stacked gaps collapse to a single 14px margin.
   - Fraunces at this size needs the same treatment as the PDP title: weight 400
     and a little tracking. Weight 500 at 14.5px is where the huddling is worst.
   ========================================================================== */

.product-card-body {
  display: block;          /* was: flex row, space-between */
  margin-top: 14px;        /* was: 22px (+ 14px padding = 36px total) */
  padding-top: 0;
}

.product-card-title {
  display: block;
  width: 100%;
  max-width: none;
  font-weight: 400;        /* was 500 */
  letter-spacing: 0.022em;
  line-height: 1.45;       /* a hair looser so two lines do not touch */
  word-spacing: 0.05em;
}

.product-card-price {
  margin-top: 7px;
  display: block;
}

/* The quick-add button now follows the stacked block, so it only needs a small gap. */
.card-quickadd { margin-top: 12px; }

/* Capping the title at two lines keeps every card in the grid the same height,
   which is what stops one long product name from making that row of cards taller
   than its neighbours. */
.product-card-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ==========================================================================
   PRODUCT CARD — round two. The first attempt lost on CSS specificity.
   ==========================================================================

   WHY THE FIRST FIX DID NOTHING

     The rules that actually govern the grid card are:

       .product-card-title            { font-weight: 500; letter-spacing: .01em }
       .product-grid .product-card-title {
           font-size: 14.5px; font-weight: 500; letter-spacing: .01em;
           margin: 0; flex: 1 1 auto;
       }

     Both are more specific than a bare .product-card-title rule, so every
     declaration in the first attempt was discarded except margin-top on the body
     (which nothing else claimed). `flex: 1 1 auto` in particular kept the title
     inside the flex row, so it never got the full card width.

   THE FIX

     Match the winner's specificity exactly and declare the block last, so these
     values win on order. The body stops being a row: the title takes the whole
     measure and the price drops beneath it. A product name should not be
     negotiating width with a price.
   ========================================================================== */


.product-card .product-card-body, .product-grid .product-card-body { display: block;
  margin-top: 13px;
  padding-top: 0; }


.product-grid .product-card-title, .product-card .product-card-title, .product-card-title { display: block;
  flex: none;                 /* out of the flex row */
  width: 100%;
  max-width: none;
  font-size: 14.5px;
  font-weight: 400;           /* was 500 - Fraunces at 14.5px hugs at 500 */
  letter-spacing: 0.021em;    /* was 0.01em */
  line-height: 1.46;          /* was 1.35 */
  word-spacing: 0.05em;
  margin: 0;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden; }


.product-card .product-card-price, .product-grid .product-card-price { display: block;
  margin-top: 7px; }

/* ---- banner headline: word-by-word scroll reveal ---------------------------
   Companion to the word-splitter in site.js. Each word starts low and faint, then
   rises into place with a staggered delay driven by --w (its index).

   Chosen values:
     0.75em travel  - enough to be felt, small enough to stay elegant
     520ms          - long enough to read as a settle, short enough not to delay
     55ms per word  - a six-word line completes in ~330ms after the first lands
     cubic-bezier   - decelerating only; an overshoot here would read as bouncy

   The spans are inline-block so transform applies, and white-space:pre so the
   spaces between them survive.                                              */
.eb-split .eb-word {
  display: inline-block;
  white-space: pre;
  opacity: 0;
  transform: translateY(0.75em);
  transition:
    opacity 520ms cubic-bezier(.22,.61,.36,1),
    transform 520ms cubic-bezier(.22,.61,.36,1);
  transition-delay: calc(var(--w) * 55ms);
  will-change: opacity, transform;
}

.eb-split.eb-words-in .eb-word {
  opacity: 1;
  transform: translateY(0);
}

/* Once the animation has played, drop the compositor hints so the browser is not
   holding layers for text that is no longer moving. */
.eb-split.eb-words-in {
  will-change: auto;
}

@media (prefers-reduced-motion: reduce) {
  .eb-split .eb-word {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}



/* ==========================================================================
   BANNER WORD REVEAL - namespaced to avoid a collision
   ==========================================================================

   WHY THE CLASSES CHANGED

     The first version added the class `eb-split` to the banner <h2>. That name was
     ALREADY in use by an unrelated component - an editorial split-banner - whose
     rule is:

       .eb-split { overflow: hidden; border-radius: var(--border-radius);
                   background: var(--bg-alt); }

     So the headline silently inherited a solid #F5F4F0 background block with
     rounded corners, and white text on it was illegible. That is the "white
     highlight box" - it was never a highlight, it was another component's panel.

     The collision also broke the animation: `.eb-split .eb-word { opacity: 0 }`
     from the other component was more specific than the reveal rules, so the words
     were governed by a transition that never received the triggering class.

   THE FIX

     Everything for the banner reveal is now prefixed `ebw-` (eb word), a name used
     nowhere else. And to be certain the headline cannot pick up panel styling from
     any ancestor-matching selector, its own background is explicitly reset.
   ========================================================================== */

/* The heading must never carry a background - the banner image shows through. */
.editorial-banner h2 {
  background: none !important;
  background-color: transparent !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  overflow: visible;
}

/* Default: readable, no transform. Nothing here depends on JS. */
.editorial-banner h2 .ebw {
  display: inline-block;
  white-space: pre;
  opacity: 1;
  transform: none;
  background: none;
}

/* Hidden start-state, armed only once the script has run. */
.editorial-banner h2.ebw-js .ebw {
  opacity: 0;
  transform: translateY(0.6em);
  transition:
    opacity 480ms cubic-bezier(.22,.61,.36,1),
    transform 480ms cubic-bezier(.22,.61,.36,1);
  transition-delay: calc(var(--w) * 50ms);
}

.editorial-banner h2.ebw-js.ebw-in .ebw {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .editorial-banner h2.ebw-js .ebw {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}


/* (old mystery-charm styles removed — feature retired) */

/* ==========================================================================
   COLLECTION UPGRADE — visual swatches, view toggle, grid polish
   ========================================================================== */

/* ---- 2. Grid / carousel view toggle ---- */
.view-toggle { display: flex; gap: 2px; margin-left: 10px; }
.view-toggle button {
  width: 34px; height: 34px;
  border: 1px solid var(--border);
  background: #fff;
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  border-radius: 6px;
  transition: background 150ms, color 150ms, border-color 150ms;
}
.view-toggle button[aria-pressed="true"] {
  background: var(--text);
  color: #fff;
  border-color: var(--text);
}

/* Carousel view: one horizontal rail of cards instead of a grid. */
.product-grid.is-carousel {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 18px;
  border: 0;
  scrollbar-width: thin;
}
.product-grid.is-carousel .product-card {
  flex: 0 0 clamp(200px, 24vw, 280px);
  scroll-snap-align: start;
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
}
@media (max-width: 560px) {
  .product-grid.is-carousel .product-card { flex-basis: 74vw; }
}

/* ---- 3. Consistent media box + image scale (audit fix) ----------------
   Cards held 400x400, 2000x2000, 1896x1896, 396x396, 800x800 and 1448x1448
   files at once, so products appeared at wildly different sizes. One fixed
   box + object-fit contain normalises all of them. */
.product-grid .product-card-media {
  aspect-ratio: 1 / 1;
  padding: 10px;
}
/* Card images arrive at different framing (some shot tight, some floating in
   transparent margin). --fit normalises them so every product reads at the same
   visual weight. Applied to the SLIDE (the box), not the img, so the box stays
   fixed and only the artwork scales. Ken, 2026-10-31. */
.product-grid .product-card-media .card-slide,
.product-grid .product-card-track .card-slide {
  transform: scale(var(--fit, 1));
  transform-origin: center;
  transition: transform 240ms var(--ease-out, ease);
}
.product-grid .product-card-media {
  overflow: hidden;          /* keep a scaled-up image inside its tile */
}
.product-grid .product-card-media .card-slide img,
.product-grid .product-card-track img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}

/* ---- 4. One badge per card, consistent corner ------------------------- */
.product-card .product-card-badge {
  max-width: calc(100% - 52px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- 5. Sold-out cards read as sold out without shouting ------------- */
.product-card.is-sold-out .product-card-media { opacity: .72; }
.product-card.is-sold-out:hover .product-card-media { opacity: .9; }
/* ==========================================================================
   PRODUCT CARD — device label, short name, colour swatches
   ========================================================================== */

/* Small device label above the name ("WHOOP 5.0") */
.product-card-device {
  font-size: 10.5px;
  line-height: 1.2;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 3px;
}

/* Short display name — one line, never two, so every card aligns */
.product-card-title {
  display: block;
  font-size: 14.5px;
  line-height: 1.35;
  font-weight: 500;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Colour swatch dots — small, quiet, one per sibling product */
.card-swatches {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 8px 0 2px;
  flex-wrap: wrap;
}
.card-swatch {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--dot, #b8b3a8);
  border: 1px solid rgba(0, 0, 0, .18);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .35);
  display: inline-block;
  transition: transform 150ms var(--ease-out, ease), box-shadow 150ms;
  position: relative;
}
.card-swatch:hover { transform: scale(1.22); }
.card-swatch.is-current {
  box-shadow: 0 0 0 2px var(--bg, #fff), 0 0 0 3px var(--text, #2c2928);
  transform: scale(1.08);
}
.card-swatch.is-out { opacity: .38; }
.card-swatch.is-out::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: linear-gradient(to top right, transparent 44%, rgba(0,0,0,.45) 46%, rgba(0,0,0,.45) 54%, transparent 56%);
}
.card-swatch-more {
  font-size: 11px;
  line-height: 1;
  color: var(--text-muted);
  padding-left: 1px;
}

/* Badge tones: Sale reads as a price story, Tinggal N as scarcity, Habis quiet */
.product-card-badge { letter-spacing: .06em; }
.product-card-badge:where([data-tone="sale"]) { background: var(--text, #2c2928); color: #fff; }
.card-swatch:focus-visible,
.product-card-title:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Collection tile descriptor — one quiet line under the name */
.collection-descriptor {
  margin: 6px 0 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-muted);
}
/* ==========================================================================
   HOMEPAGE PRODUCT ROWS — four rows, device tabs
   ========================================================================== */
.home-row { padding-top: 34px; padding-bottom: 8px; }

/* Tabs: quiet text buttons, understated until active */
.row-tabs {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  align-items: center;
}
.row-tab {
  appearance: none;
  background: none;
  border: 0;
  padding: 6px 11px;
  font: inherit;
  font-size: 11.5px;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: 999px;
  transition: color 160ms, background 160ms;
}
.row-tab:hover { color: var(--text); }
.row-tab.is-active {
  color: var(--text);
  background: var(--bg-alt);
  font-weight: 600;
}
.row-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The rail: horizontally scrollable on every width, snap for a tidy stop */
.home-row-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(215px, 1fr);
  gap: 16px;
  overflow-x: auto;
  padding: 4px 0 18px;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}
.home-row-item { scroll-snap-align: start; }
.home-row-item[hidden] { display: none; }
@media (max-width: 700px) {
  .home-row-track { grid-auto-columns: 78%; gap: 12px; }
}
/* Hero CTA pair: one primary, one quiet secondary */
.hero-cta-row {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.hero-cta-secondary {
  font-size: 13px;
  letter-spacing: .02em;
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--text) 30%, transparent);
  padding-bottom: 2px;
  transition: border-color 160ms, opacity 160ms;
}
.hero-cta-secondary:hover { border-color: var(--text); }
.hero-cta-secondary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* ==========================================================================
   CHECKOUT — delivery area search (replaces the 3-dropdown cascade)
   ========================================================================== */
.area-search-wrap { position: relative; }
.area-hint {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
  margin-top: 6px;
}
.area-results {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #fff;
  max-height: 280px;
  overflow-y: auto;
  box-shadow: 0 12px 28px -20px rgba(0,0,0,.45);
}
.area-result {
  padding: 11px 14px;
  font-size: 14px;
  line-height: 1.4;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.area-result:last-child { border-bottom: 0; }
.area-result:hover,
.area-result:focus-visible {
  background: var(--bg-alt);
  outline: none;
}
.area-result:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); }
@media (max-width: 560px) {
  .area-result { padding: 13px 14px; font-size: 14.5px; }  /* 44px+ tap target */
}
/* ==========================================================================
   PRODUCT GRID — mobile containment fix
   --------------------------------------------------------------------------
   The grid columns are minmax(0,1fr) so they can shrink below their content's
   intrinsic width. These rules make the card and its media box actually honour
   that: without them a 1:1 image plus padding forces the column wider than its
   share and the whole page scrolls sideways on a phone.
   ========================================================================== */
.product-grid { max-width: 100%; }
.product-grid .product-card {
  min-width: 0;              /* a flex/grid item may now shrink */
  max-width: 100%;
  box-sizing: border-box;
  overflow: hidden;          /* never let a child push the card wider */
}
.product-grid .product-card-media {
  min-width: 0;
  max-width: 100%;
}
/* Everything inside the card body must be allowed to shrink too. */
.product-card-body,
.product-card-title,
.product-card-price,
.card-swatches { min-width: 0; max-width: 100%; }

/* Quick-add sits inside the card, not spilling out of it. */
.product-card .card-quickadd {
  max-width: 100%;
  box-sizing: border-box;
}

/* On a phone the card padding was 18px each side of a ~150px column: nearly a
   third of the tile was gutter. Tighten it so the product gets the space. */
@media (max-width: 560px) {
  .product-grid .product-card { padding: 10px 10px 8px; }
  .product-card-title { font-size: 13px; }
  .product-card-device { font-size: 9.5px; letter-spacing: .06em; }
  .card-swatches { gap: 5px; margin: 6px 0 2px; }
  .card-swatch { width: 11px; height: 11px; }
}
/* ==========================================================================
   PRODUCT CARD — buy button always visible, titles never clipped
   ========================================================================== */

/* The quick-add used to fade in on hover (desktop) and be forced visible only
   under (hover: none). Any device that claims hover support therefore showed a
   card with an invisible button. Selling beats the flourish: always show it. */
.card-quickadd,
.product-grid .product-card .card-quickadd,
.product-grid .product-card:hover .card-quickadd {
  opacity: 1;
  transform: none;
}

/* Titles: two lines on phones so "Already Late — Black" is not cut to "Bl…",
   and the underline wrap gives a long name somewhere to go without growing
   the card by much. */
@media (max-width: 560px) {
  .product-card-title {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.3;
    min-height: 2.6em;      /* equal heights whether the name is 1 or 2 lines */
  }
}
/* ==========================================================================
   MOBILE TOUCH TARGETS — every control reachable with a thumb
   --------------------------------------------------------------------------
   Measured before this pass: 42 colour swatches at 12x12, 17 wishlist hearts at
   36x36, 8 carousel arrows at 38x38, close buttons ~31x31, chips 36 high.
   44x44 is the minimum a thumb can reliably hit. Where the VISUAL element must
   stay small (a colour dot), the hit area is grown with an invisible overlay so
   the design does not change.
   ========================================================================== */

/* --- colour swatches: 12px dot, 44px hit area ---------------------------- */
.card-swatch {
  position: relative;
}
.card-swatch::after {
  content: "";
  position: absolute;
  /* centre a 44x44 target on the 13px dot */
  left: 50%;
  top: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
  /* must not paint anything — it is purely a hit area */
  background: transparent;
}
/* Keep the dots comfortably spaced so neighbouring targets do not overlap. */
.card-swatches { gap: 10px; }

/* --- wishlist heart, carousel arrows, close buttons, chips ---------------
   NOTE: .pdp-dot / .hero-dot / .eb-dot are deliberately NOT in this list. They
   are 8px pagination dots; forcing a 44px box made them giant circles covering
   the product photo. They get their touch area from the ::after overlay below,
   which keeps the visible dot small. */
.card-wishlist,
.card-arrow,
.chat-fab,
.mobile-nav-close,
.cart-close,
.cart-line-remove,
.gallery-arrow,
.review-slider-arrow,
.modal-close,
.lang-toggle a,
.chip,
.mtab-bar a,
.mtab-bar button {
  min-width: 44px;
  min-height: 44px;
}
/* Icon-only buttons: keep the glyph centred in the bigger box. */
.card-wishlist,
.card-arrow,
.mobile-nav-close,
.cart-close,
.gallery-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Small dots (carousel pagination) are decorative — give them a real target. */
.pdp-dot, .hero-dot, .eb-dot {
  position: relative;
}
.pdp-dot::after, .hero-dot::after, .eb-dot::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
  background: transparent;
}

/* --- form controls ------------------------------------------------------- */
input[type="checkbox"],
input[type="radio"] {
  min-width: 20px;
  min-height: 20px;
}
/* The label carries the tap, so the row must be a comfortable target. */
.collection-filters label,
.pay-method,
.form-field label.checkbox,
label[for] {
  min-height: 44px;
}
.collection-filters label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 2px;
  cursor: pointer;
}

/* --- header icons on mobile --------------------------------------------- */
@media (max-width: 560px) {
  .header-icon { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
  .logo { min-height: 44px; display: inline-flex; align-items: center; }
  /* Give the two-column card titles room to breathe. */
  .product-card-title { font-size: 13.5px; }
}

/* --- product titles are links: make the whole text a 44px target -------- */
.product-card-title {
  display: block;
  padding: 4px 0 2px;
  min-height: 24px;
}
/* ==========================================================================
   MOBILE TOUCH TARGETS — round two (the 1-2px stragglers)
   ========================================================================== */

/* Menu / nav rows measured 43px: nudge to 46 so rounding cannot drop them. */
.mobile-nav a,
.mobile-nav button,
.mobile-nav-auth,
.mobile-menu a,
.nav-drawer a {
  min-height: 46px;
  display: flex;
  align-items: center;
}

/* A card title is a link. Two short lines on a phone should still be a
   comfortable target, without changing how it looks. */
.product-card-title {
  min-height: 44px;
  display: flex;
  align-items: center;
}
@media (max-width: 560px) {
  .product-card-title {
    min-height: 44px;
    padding: 6px 0;
  }
}

/* Checkboxes: the input stays visually small (a 40px box looks broken), but the
   LABEL wraps it and provides the full 44px tap. */
input[type="checkbox"],
input[type="radio"] {
  width: 18px;
  height: 18px;
  min-width: 18px;
  min-height: 18px;
  flex: 0 0 auto;
}
.collection-filters label,
label:has(> input[type="checkbox"]),
label:has(> input[type="radio"]) {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
}

/* Icon buttons that were 34px. */
.view-toggle button { width: 44px; height: 44px; }
.row-tab { min-height: 44px; padding-top: 10px; padding-bottom: 10px; }
.chip { min-height: 44px; display: inline-flex; align-items: center; padding-top: 8px; padding-bottom: 8px; }
.chat-gate-btn { min-height: 44px; padding-top: 10px; padding-bottom: 10px; }
.lang-toggle a { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }

/* Sorter / filter controls */
.collection-toolbar .form-select { min-height: 44px; }
.collection-filters .btn { min-height: 44px; }
/* ==========================================================================
   MOBILE TOUCH TARGETS — final pass
   ========================================================================== */

/* Footer links were 20px tall; stacked lists are fine, tiny rows are not. */
footer a,
.footer-links a,
.footer-col a,
.footer-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
/* Keep the footer visually tight despite the taller targets. */
footer .footer-links,
footer .footer-col { row-gap: 2px; }

/* Quick add was 42px — 2px short of the rule. */
.card-quickadd,
.product-grid .product-card .card-quickadd { min-height: 44px; }

/* Text inputs: 29px tall is hard to tap. 44 makes forms usable with a thumb. */
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="number"],
input[type="search"],
input[type="password"],
input[type="url"],
textarea,
select,
.form-input,
.form-select,
.form-textarea {
  min-height: 44px;
}
textarea,
.form-textarea { min-height: 88px; }

/* Review star buttons: 26px glyph, 44px target. */
.star-btn {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.star-picker { gap: 2px; }

/* Hero secondary CTA. */
.hero-cta-secondary {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 0 2px;
}

/* Any remaining small icon button (popup close, panel close). */
[aria-label*="Close"],
[aria-label*="close"],
.close,
.modal-close,
.popup-close,
.panel-close {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* ==========================================================================
   TRUST STRIP — specific, checkable promises on the product page
   ========================================================================== */
.trust-strip {
  list-style: none;
  margin: 18px 0 6px;
  padding: 16px 0 4px;
  border-top: 1px solid var(--border);
  display: grid;
  gap: 12px;
}
.trust-strip-item {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: 10px;
  align-items: start;
  font-size: 13.5px;
  line-height: 1.45;
}
.trust-strip-ico {
  width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 16%, #fff);
  color: #7a5c33;
  font-size: 12px;
  font-weight: 700;
  flex: none;
}
.trust-strip-item strong { display: block; font-weight: 600; color: var(--text); }
.trust-strip-sub { display: block; color: var(--text-muted); font-size: 12.5px; margin-top: 1px; }

/* Footer identity block */
.footer-identity {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-muted);
  margin-top: 10px;
  max-width: 46ch;
}
.footer-identity strong { color: var(--text); font-weight: 600; }
/* ==========================================================================
   PAGINATION DOTS — visible dot stays 8px; only the hit area grows
   ========================================================================== */
.pdp-dot,
.hero-dot,
.eb-dot {
  width: 8px !important;
  height: 8px !important;
  min-width: 8px !important;
  min-height: 8px !important;
  max-width: 8px;
  max-height: 8px;
  flex: 0 0 auto;
}
.pdp-dot.active { transform: scale(1.3); }

/* The dots sit ON the photo, so the pill holding them must stay compact. */
.pdp-dots {
  gap: 6px;
  padding: 5px 9px;
  bottom: 10px;
}
/* ==========================================================================
   SUPPORT CHAT — guest intro form (.chat-gate)
   --------------------------------------------------------------------------
   This form had no styling beyond a min-height, so labels sat inline with
   206px inputs and the submit was a bare text link. Rebuilt as a proper form:
   label above, full-width field, 44px tall, clear primary action.
   ========================================================================== */
.chat-gate {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px 16px 18px;
  margin: 0;
  overflow-y: auto;
}

.chat-gate-intro {
  margin: 0 0 2px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-muted);
}

/* Each field is a block: label, then control, then error. */
.chat-gate-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
}
.chat-gate-field > span {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-muted);
  line-height: 1.3;
}
.chat-gate-field > span em {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  opacity: .7;
}

.chat-gate-field input {
  width: 100%;
  box-sizing: border-box;
  min-height: 46px;
  padding: 12px 14px;
  font-size: 15px;              /* 16px avoids iOS zoom-on-focus; 15 is close */
  font-family: var(--font-body);
  color: var(--text);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 12px;
  transition: border-color 160ms ease, box-shadow 160ms ease;
  -webkit-appearance: none;
  appearance: none;
}
.chat-gate-field input::placeholder { color: color-mix(in srgb, var(--text-muted) 75%, transparent); }
.chat-gate-field input:focus {
  outline: none;
  border-color: var(--text);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--text) 10%, transparent);
}
.chat-gate-field input.is-invalid {
  border-color: #b91c1c;
  box-shadow: 0 0 0 3px rgba(185, 28, 28, .1);
}

.chat-gate-err {
  font-size: 12px;
  font-style: normal;
  line-height: 1.4;
  color: #b91c1c;
}

/* The primary action: a real button, full width, unmistakable. */
.chat-gate-btn {
  width: 100%;
  min-height: 48px;
  margin-top: 4px;
  padding: 13px 18px;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .04em;
  color: #fff;
  background: var(--text);
  border: 0;
  border-radius: 12px;
  cursor: pointer;
  transition: opacity 160ms ease, transform 160ms ease;
}
.chat-gate-btn:hover { opacity: .9; }
.chat-gate-btn:active { transform: translateY(1px); }
.chat-gate-btn:disabled { opacity: .55; cursor: default; }
.chat-gate-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.chat-gate-note {
  margin: 2px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: #b91c1c;
}

/* On a phone the panel becomes a proper sheet so the form has room. */
@media (max-width: 560px) {
  .chat-panel {
    left: 10px;
    right: 10px;
    bottom: 76px;
    width: auto;
    max-width: none;
    height: auto;
    max-height: calc(100dvh - 150px);
  }
  .chat-gate { gap: 16px; padding: 18px 18px 20px; }
  .chat-gate-field input { font-size: 16px; }   /* iOS: no zoom on focus */
}
/* Support panel close: a 44px target that still reads as an icon. */
.chat-head button {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  transition: background 160ms ease;
}
.chat-head button:hover { background: var(--bg-alt); }