/* Base reset */
html {
  font-size: 16px;
}

/* ── Kalles motion language (kx-*) ──────────────────────────────────────────
 * Extracted from the reference theme (reference/kalles-5-2/PARITY.md) — every
 * duration/easing value here was read off the live theme's CSS, not guessed.
 * Blocks reference these tokens (node-factories.mjs, shared-sections.mjs) via
 * `var(--kx-*)` and `data-kx-*` attributes; this file is what actually gives
 * those attributes motion. See PARITY.md for the full porting spec.
 */
:root {
  /* One measure for the whole storefront. The header was capped at 1440, the
     category strip at 1280, the shop grid at 1200 and the strip's background
     ran full-bleed — four different left edges stacked down one page, which
     is what "broken" looked like. */
  --site-measure: 1440px;
  --site-gutter: 2.5rem;
  /* The width of the centred content column on desktop. Must stay >= the 768px
     responsive breakpoint so `desktopStyles` are applied at a width they were
     actually authored for. Read by .page-wrapper and the fixed bottom nav. */
  --page-column: 768px;
  --kx-dur-short: 0.1s;
  --kx-dur-default: 0.2s;
  --kx-dur-long: 0.5s;
  --kx-dur-extra-long: 0.6s;
  --kx-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --kx-ease-zoom: cubic-bezier(0, 0, 0.44, 1.18);
  --kx-ease-reveal: cubic-bezier(0, 0, 0.3, 1);
  --kx-ease-drawer: cubic-bezier(0.645, 0.045, 0.355, 1);
}

/* 1. Product-card hover — the 2nd image's rest state + slow "breathing" zoom.
 * Rest lives here (not inline) so an unset bind.style never leaves it visible —
 * see the comment on `image2` in node-factories.mjs productCard. */
[data-kx-hover-img] {
  opacity: 0;
  transform: scale(1);
  transition:
    opacity var(--kx-dur-long) var(--kx-ease-standard),
    transform 2s var(--kx-ease-zoom);
}

/* 3. Mega-menu open — fade + slide-down, replayed each time the panel flips
 * to display:flex (a CSS animation restarts on display:none → non-none). */
@keyframes kx-menu-open {
  0% {
    opacity: 0;
    transform: translateY(-1.5rem);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

[data-kx-menu] {
  animation: kx-menu-open var(--kx-dur-extra-long) var(--kx-ease-reveal);
}

/* 4. Drawers / overlays — the engine's generic overlay (overlay-styles.ts,
 * `[data-nd-overlay]`) shows via `[data-open]` → display:flex, so the same
 * "animation restarts on display change" trick applies to panels here. */
@keyframes kx-overlay-fade {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

@keyframes kx-pop-in {
  0% {
    opacity: 0;
    transform: scale(0.96);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes kx-slide-up {
  0% {
    transform: translateY(100%);
  }
  100% {
    transform: translateY(0);
  }
}

@keyframes kx-slide-in-right {
  0% {
    transform: translateX(100%);
    opacity: 0;
  }
  100% {
    transform: translateX(0);
    opacity: 1;
  }
}

[data-nd-overlay][data-open] .nd-overlay__backdrop {
  animation: kx-overlay-fade var(--kx-dur-long) var(--kx-ease-standard);
}

[data-nd-overlay][data-open] .nd-overlay__panel {
  animation: kx-pop-in var(--kx-dur-long) var(--kx-ease-drawer);
}

[data-nd-overlay][data-nd-presentation="sheet"][data-open] .nd-overlay__panel {
  animation: kx-slide-up var(--kx-dur-long) var(--kx-ease-drawer);
}

/* Cart is a right-side, full-height drawer in Kalles — not the engine's
 * default bottom sheet or centered modal. Override presentation for this
 * specific overlay identity only; every other overlay (quick-view, etc.)
 * keeps the engine default. */
#cart-overlay[data-nd-overlay] {
  align-items: stretch;
  justify-content: flex-end;
  padding: 0;
}

#cart-overlay[data-nd-overlay] .nd-overlay__panel {
  width: min(420px, 100%);
  height: 100%;
  max-height: 100%;
  border-radius: 0;
}

#cart-overlay[data-nd-overlay][data-open] .nd-overlay__panel {
  animation: kx-slide-in-right var(--kx-dur-long) var(--kx-ease-drawer);
}

/* 5. Scroll reveal — gated behind `.kx-js` (set by an early inline script in
 * <head>, before paint) so content is never stuck invisible with JS off or a
 * slow script load; kx-reveal.js observes `[data-kx-reveal]` and flips
 * `data-kx-inview` + sets `--kx-order` per element for the 75ms cascade. */
@keyframes kx-reveal-slide-in {
  0% {
    transform: translateY(2rem);
    opacity: 0.01;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

@keyframes kx-reveal-fade-in {
  0% {
    opacity: 0.01;
  }
  100% {
    opacity: 1;
  }
}

html.kx-js [data-kx-reveal]:not([data-kx-inview]) {
  opacity: 0.01;
}

html.kx-js [data-kx-reveal="slide"]:not([data-kx-inview]) {
  transform: translateY(2rem);
}

html.kx-js [data-kx-reveal="slide"][data-kx-inview] {
  animation: kx-reveal-slide-in var(--kx-dur-extra-long) var(--kx-ease-reveal) forwards;
  animation-delay: calc(var(--kx-order, 0) * 75ms);
}

html.kx-js [data-kx-reveal="fade"][data-kx-inview] {
  animation: kx-reveal-fade-in var(--kx-dur-extra-long) var(--kx-ease-reveal) forwards;
  animation-delay: calc(var(--kx-order, 0) * 75ms);
}

@media (prefers-reduced-motion: reduce) {
  [data-kx-hover-img],
  [data-kx-menu],
  [data-nd-overlay][data-open] .nd-overlay__backdrop,
  [data-nd-overlay][data-open] .nd-overlay__panel,
  html.kx-js [data-kx-reveal="slide"][data-kx-inview],
  html.kx-js [data-kx-reveal="fade"][data-kx-inview] {
    animation: none;
    transition: none;
  }
  html.kx-js [data-kx-reveal]:not([data-kx-inview]) {
    opacity: 1;
    transform: none;
  }
}

/* Interactive defaults — every <button> shows a pointer on hover; disabled
 * buttons get the native "not-allowed" cursor. Individual templates that
 * wanted this added inline forgot to on a few (hamburger, product back
 * button), so we cover it globally once. */
button {
  cursor: pointer;
}
/* Children of buttons (icons, SVGs, spans wrapping them) should never
 * override the button's cursor — so hover shows a pointer anywhere inside
 * the button hit area, not just on padding. */
button * {
  cursor: inherit;
}
button:disabled,
button[aria-disabled="true"] {
  cursor: not-allowed;
}

html,
body {
  margin: 0;
  padding: 0;
  overflow-x: hidden;
}

body {
  background-color: var(--bg-page);
  color: var(--text-main);
  line-height: var(--line-height);
  font-size: var(--text-base);
  font-weight: var(--font-normal);
}

/*
 * Storefront layout — mobile render everywhere, clamped to a phone-width
 * column on desktop (LinkedIn/Instagram style). The wrapper is emitted by
 * `pageWrapperSchema` and sits between <body> and the page-specific
 * template, so every page inherits the column automatically.
 */
.page-wrapper {
  width: 100%;
  min-height: 100vh;
  background: var(--bg-page);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
}

.page-wrapper > #page-content {
  display: flex;
  flex-direction: column;
  flex: 1;
}

.page-wrapper footer {
  margin-top: auto;
}

@media (min-width: 768px) {
  body {
    background: #f3f4f6;
  }
  /* The clamp must be AT LEAST the responsive breakpoint.
     `desktopStyles` compile to `@media (min-width: 768px)`, which keys off the
     VIEWPORT — but the content lives inside this column. At 520px every theme's
     desktop rules fired into a container 248px narrower than they were written
     for, so a product page switching to `flexDirection: row` put the image and
     the details side by side in 520px and the image overflowed its box. That is
     the clipped-image bug reported across several pages, and it was never
     theme-specific: viewport and container simply could not agree.
     Matching the breakpoint makes them agree. Every ready-made design drops
     the clamp entirely (see page-wrapper.ts); what is left here is the shell
     for stores still on their own Edit-site build, which are authored
     phone-first. */
  .page-wrapper {
    max-width: var(--page-column, 768px);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 10px 40px rgba(0, 0, 0, 0.06);
  }
  /* A ready-made design scales to the viewport: it has a real desktop
     composition, so drop the phone-width clamp and the side shadow and let
     it use the window. The inner page templates keep their own desktop
     measures (1100–1440px), so this never goes edge-to-edge on ultrawide. */
  .page-wrapper--wide {
    max-width: none;
    box-shadow: none;
    background: var(--bg-page);
  }
  /* Wide-wrapper pages with feed-column content (checkout, cart, orders,
     profile) — without anchor, the 640px column floats unframed in the
     middle of the wide page background. Give it a soft surface so it
     reads as a card rather than a void. Scoped to wide wrapper so it
     doesn't touch the existing column-clamped variants. */
  .page-wrapper--wide .feed-column-anchor,
  .page-wrapper--wide #checkout-page,
  .page-wrapper--wide #cart-page {
    background: var(--bg-surface, #ffffff);
    border-radius: 16px;
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
  }
}

/* Typography */
h1,
h2,
h3 {
  color: var(--text-heading);
  font-weight: var(--font-bold);
}

h1 {
  margin: 0;
}

small,
.caption {
  color: var(--text-muted);
  font-size: var(--text-xs);
}

/* Input base */
input {
  height: var(--h-10);
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  width: 100%;
  margin: 0;
  box-sizing: border-box;
  /* Bind the input surface + text to theme tokens. Previously unset, so the
     browser used its own white box while text inherited the (light, on dark
     presets) body colour — i.e. white-on-white. Now both follow the active
     theme/style and keep their guaranteed contrast pairing. */
  color: var(--text-main);
  background: var(--bg-surface);
  font-family: var(--font-sans);
}

/* Textarea — mirror the input surface/text binding so multi-line fields theme
   identically (templates historically styled textareas only inline). */
textarea {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  width: 100%;
  margin: 0;
  box-sizing: border-box;
  color: var(--text-main);
  background: var(--bg-surface);
  font-family: var(--font-sans);
}

input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="number"] {
  -moz-appearance: textfield;
}

input::placeholder,
textarea::placeholder {
  letter-spacing: normal;
  font-size: var(--text-sm) !important;
  color: var(--text-muted);
}

/* Checkbox */
input[type="checkbox"] {
  padding: 0.7rem 0.6rem;
  width: auto;
  height: auto;
  appearance: none;
  border: 2px solid var(--color-primary);
  border-radius: 0;
  background: var(--bg-surface);
  cursor: pointer;
  position: relative;
  vertical-align: middle;
}

input[type="checkbox"]:checked::after {
  content: "";
  display: block;
  position: absolute;
  left: 5px;
  top: 2px;
  width: 6px;
  height: 12px;
  border: solid var(--button-text);
  border-width: 0 3px 3px 0;
  transform: rotate(45deg);
}

/* Radio */
input[type="radio"] {
  width: 20px;
  height: 20px;
  padding: 0;
  appearance: none;
  border: 2px solid var(--color-primary);
  border-radius: 50%;
  background: var(--bg-surface);
  cursor: pointer;
  position: relative;
  vertical-align: middle;
  box-sizing: border-box;
  display: inline-block;
}

input[type="radio"]:checked::after {
  content: "";
  display: block;
  position: absolute;
  left: 3px;
  top: 3px;
  width: 8px;
  height: 8px;
  background: var(--color-primary);
  border-radius: 50%;
}

/* Select */
select {
  padding: 0.5rem 1rem;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background: var(--bg-surface);
  cursor: pointer;
  font-size: var(--text-xs);
  color: var(--text-main);
  appearance: none;
  /* Mid-grey arrow (#888) reads on both light and dark theme surfaces — a data
     URI can't reference a CSS var, so #333 went invisible on dark presets. */
  background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20fill%3D%22%23888888%22%20d%3D%22M7.41%208.59L12%2013.17l4.59-4.58L18%2010l-6%206-6-6z%22%2F%3E%3C%2Fsvg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 1rem;
}

/* Links */
a {
  text-decoration: none;
  color: var(--text-main);
}

/* Error styles */
.input-error {
  color: var(--text-error);
  font-size: var(--text-xs);
}

.form-error {
  color: var(--text-error);
  font-size: var(--text-lg);
}

.text-error {
  color: var(--text-error);
  font-size: var(--text-base);
}

/* Navigation */
.nav a {
  color: var(--text-on-dark);
  text-decoration: none;
  margin-right: var(--space-md);
}

.nav a:hover {
  color: var(--color-secondary);
}

/* Outline button */
.outline-btn:hover,
.outline-btn:focus,
.outline-btn.active {
  background: var(--bg-surface);
  border-color: var(--color-primary);
  box-shadow: var(--shadow-sm);
}

.outline-btn.selected {
  border-color: var(--color-primary) !important;
  box-shadow: var(--shadow-sm) !important;
}

/* Circular button */
.circular-btn {
  border-radius: var(--radius-full);
  color: var(--text-main);
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
}

.light-circular-btn {
  background-color: var(--text-on-dark);
}

.light-circular-btn:hover,
.light-circular-btn:focus,
.light-circular-btn.selected,
.light-circular-btn.active {
  border-color: var(--color-primary);
  background-color: var(--color-primary);
  color: var(--button-text) !important;
}

/* Chip button — bound to theme tokens (was pinned to --color-white/black, so
   chips never followed a style change). */
.chip-button {
  background-color: var(--bg-surface);
  color: var(--text-main);
  border: 1px solid var(--border-color);
}

.chip-button.active {
  background-color: var(--color-primary);
  color: var(--button-text);
  border-color: var(--color-primary);
}

/* ── Search ───────────────────────────────────────────────────────────────
   A full-height sheet on a phone — search is the whole screen while you are
   doing it — and a panel under the header on desktop, so the page you were
   on stays visible behind it. */
.sf-search-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: var(--z-search-backdrop, 999);
}
.sf-search-panel {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  flex-direction: column;
  background: var(--bg-page);
  z-index: var(--z-search-panel, 1000);
}
.sf-search-link { text-decoration: none; color: inherit; display: block; }
/* Pointer and keyboard land on the same state. */
.sf-search-row:hover,
.sf-search-link:hover .sf-search-row,
.sf-search-row.is-highlighted,
.sf-search-link.is-highlighted .sf-search-row {
  background: var(--bg-raised, rgba(0, 0, 0, 0.04));
}
.sf-search-chip:hover,
.sf-search-link.is-highlighted .sf-search-chip {
  border-color: var(--color-primary);
  color: var(--color-primary);
}
.sf-search-panel .sf-search-price del {
  margin-left: 0.4rem;
  font-weight: 400;
  color: var(--text-muted);
}

@media (min-width: 768px) {
  .sf-search-panel {
    top: 4.5rem;
    left: 50%;
    transform: translateX(-50%);
    width: min(44rem, calc(100% - 4rem));
    height: auto;
    max-height: min(32rem, calc(100vh - 7rem));
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius, 16px);
    overflow: hidden;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.18);
  }
}

/* Utility */
.divider {
  height: 1px;
  background-color: var(--border-color);
  width: 100%;
  margin: 4px 0;
}

/* Responsive */
@media (max-width: 767px) {
  .responsive-container {
    width: 100%;
    min-width: unset;
    border-radius: 0;
  }
}

@media (min-width: 768px) {
  body {
    margin-left: auto;
    margin-right: auto;
  }
}

/* Add to cart animation */
.flying-image {
  position: fixed;
  z-index: 1000;
  width: var(--product-image-width);
  height: var(--product-image-height);
  transition:
    transform 1s ease-in,
    opacity 1s ease-in;
  pointer-events: none;
}

@keyframes shake {
  0%,
  100% {
    transform: translateX(0);
  }
  25%,
  75% {
    transform: translateX(-5px);
  }
  50% {
    transform: translateX(5px);
  }
}

#add-to-cart-btn.shake {
  animation: shake 0.5s;
}

/* Bottom-nav ongoing-order "live / in-progress" badge: a radar-style pulse on
   the accent dot, signalling the active order is in flight. Uses currentColor
   (the dot's color is var(--color-accent)) so it reskins with the theme. */
.bottom-nav-order-live {
  animation: bottom-nav-live-pulse 1.6s ease-out infinite;
}

@keyframes bottom-nav-live-pulse {
  0% {
    box-shadow: 0 0 0 0 currentColor;
  }
  70% {
    box-shadow: 0 0 0 6px transparent;
  }
  100% {
    box-shadow: 0 0 0 0 transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bottom-nav-order-live {
    animation: none;
  }
}

/* Hero overlay text scales with the hero's OWN width (cqw) instead of the
   viewport (vw). On a mobile-only template the hero is the ~520px phone column,
   so on a laptop the old vw clamps maxed out and rendered desktop-sized hero
   text; cqw keeps it column-sized. Marketplace (full-width hero) still scales up. */
.hero-section {
  container-type: inline-size;
}

/* Slide entrances — see the slide script in kx-reveal.ts. A slider holds up to
 * three slides and each position has its own look: the first slide's copy sits
 * left, the second centred, the third right, and each plays its own entrances
 * (piece by piece) every time it becomes the slide in view. */
@keyframes nd-in-left { from { opacity: 0; transform: translateX(-60px); } to { opacity: 1; transform: none; } }
@keyframes nd-in-right { from { opacity: 0; transform: translateX(60px); } to { opacity: 1; transform: none; } }
@keyframes nd-in-up { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: none; } }
@keyframes nd-in-down { from { opacity: 0; transform: translateY(-24px); } to { opacity: 1; transform: none; } }
@keyframes nd-in-blur { from { opacity: 0; filter: blur(12px); transform: translateX(-30px); } to { opacity: 1; filter: none; transform: none; } }
@keyframes nd-in-zoom { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: none; } }
@keyframes nd-in-pop { 0% { opacity: 0; transform: scale(0.6); } 70% { opacity: 1; transform: scale(1.08); } 100% { opacity: 1; transform: none; } }
@keyframes nd-in-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes nd-in-wipe { from { opacity: 1; clip-path: inset(0 0 0 100%); } to { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes nd-media-out { from { transform: scale(1.12); } to { transform: scale(1); } }
@keyframes nd-media-in { from { transform: scale(1); } to { transform: scale(1.1); } }
@keyframes nd-media-pan { from { transform: scale(1.1) translateX(3%); } to { transform: scale(1.1) translateX(-1%); } }

/* Placement and shading per position. */
[data-nd-slide] [data-nd-slide-shade] { background: linear-gradient(90deg, rgba(0,0,0,0.65), rgba(0,0,0,0.05) 70%) !important; }
[data-nd-slide-index="1"] [data-nd-slide-shade] { background: rgba(0,0,0,0.48) !important; }
[data-nd-slide-index="2"] [data-nd-slide-shade] { background: linear-gradient(270deg, rgba(0,0,0,0.65), rgba(0,0,0,0.05) 70%) !important; }
[data-nd-slide-index="1"] [data-nd-slide-copy] {
  margin-left: auto; margin-right: auto; align-items: center !important; text-align: center;
}
[data-nd-slide-index="2"] [data-nd-slide-copy] {
  margin-left: auto; align-items: flex-end !important; text-align: right;
}
/* Clear of the carousel arrows. */
@media (min-width: 768px) {
  [data-nd-slide-index="0"] [data-nd-slide-copy] { margin-left: 48px; }
  [data-nd-slide-index="2"] [data-nd-slide-copy] { margin-right: 48px; }
}
@media (max-width: 767px) {
  [data-nd-slide] [data-nd-slide-shade] { background: rgba(0,0,0,0.5) !important; }
}

/* Entrances: hidden until the slide is in view (JS on only). */
html.kx-js [data-nd-slide] [data-nd-slide-part] { opacity: 0; }
[data-nd-slide][data-nd-slide-active] [data-nd-slide-part] {
  opacity: 1;
  animation: nd-in-fade 0.8s cubic-bezier(0.22, 1, 0.36, 1) both;
}
[data-nd-slide][data-nd-slide-active] [data-nd-slide-part="headline"] { animation-delay: 0.15s; }
[data-nd-slide][data-nd-slide-active] [data-nd-slide-part="text"] { animation-delay: 0.35s; }
[data-nd-slide][data-nd-slide-active] [data-nd-slide-part="cta"] { animation-delay: 0.55s; }

/* First slide (left): copy sweeps in from the left, headline out of a blur. */
[data-nd-slide-index="0"][data-nd-slide-active] [data-nd-slide-part="eyebrow"] { animation-name: nd-in-left; }
[data-nd-slide-index="0"][data-nd-slide-active] [data-nd-slide-part="headline"] { animation-name: nd-in-blur; animation-duration: 1s; }
[data-nd-slide-index="0"][data-nd-slide-active] [data-nd-slide-part="text"] { animation-name: nd-in-up; }
[data-nd-slide-index="0"][data-nd-slide-active] [data-nd-slide-part="cta"] { animation-name: nd-in-left; }
[data-nd-slide-index="0"][data-nd-slide-active] [data-nd-slide-media] { animation: nd-media-out 6s ease-out both; }

/* Second slide (centre): heading drops in, headline zooms, button pops. */
[data-nd-slide-index="1"][data-nd-slide-active] [data-nd-slide-part="eyebrow"] { animation-name: nd-in-down; }
[data-nd-slide-index="1"][data-nd-slide-active] [data-nd-slide-part="headline"] { animation-name: nd-in-zoom; animation-duration: 0.9s; }
[data-nd-slide-index="1"][data-nd-slide-active] [data-nd-slide-part="text"] { animation-name: nd-in-fade; }
[data-nd-slide-index="1"][data-nd-slide-active] [data-nd-slide-part="cta"] {
  animation-name: nd-in-pop; animation-duration: 0.6s; animation-timing-function: ease-out;
}
[data-nd-slide-index="1"][data-nd-slide-active] [data-nd-slide-media] { animation: nd-media-in 7s ease-out both; }

/* Third slide (right): copy comes from the right, headline is wiped on. */
[data-nd-slide-index="2"][data-nd-slide-active] [data-nd-slide-part="eyebrow"] { animation-name: nd-in-right; }
[data-nd-slide-index="2"][data-nd-slide-active] [data-nd-slide-part="headline"] {
  animation-name: nd-in-wipe; animation-duration: 1s; animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1);
}
[data-nd-slide-index="2"][data-nd-slide-active] [data-nd-slide-part="text"] { animation-name: nd-in-right; }
[data-nd-slide-index="2"][data-nd-slide-active] [data-nd-slide-part="cta"] { animation-name: nd-in-up; }
[data-nd-slide-index="2"][data-nd-slide-active] [data-nd-slide-media] { animation: nd-media-pan 7s ease-out both; }

/* A slider of full-bleed slides: its dots sit on the image, and phones swipe
 * instead of showing arrows that would cover the copy. */
[data-nd-carousel]:has([data-nd-slide]) > [data-nd-carousel-dots] {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 22px;
  z-index: 3;
  margin-top: 0 !important;
}
[data-nd-carousel]:has([data-nd-slide]) > [data-nd-carousel-dots] > button {
  background: rgba(255, 255, 255, 0.45) !important;
}
[data-nd-carousel]:has([data-nd-slide]) > [data-nd-carousel-dots] > button[aria-current="true"] {
  background: #ffffff !important;
}
@media (max-width: 767px) {
  [data-nd-carousel]:has([data-nd-slide]) > [data-nd-carousel-prev],
  [data-nd-carousel]:has([data-nd-slide]) > [data-nd-carousel-next] {
    display: none !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  html.kx-js [data-nd-slide] [data-nd-slide-part] { opacity: 1; }
  [data-nd-slide] [data-nd-slide-part],
  [data-nd-slide] [data-nd-slide-media] { animation: none !important; }
}

/* Merchant-written markdown (app/markdown.ts), e.g. a blog post body. */
[data-nd-format="markdown"] > * { margin: 0 0 1em; }
[data-nd-format="markdown"] > :last-child { margin-bottom: 0; }
[data-nd-format="markdown"] h2 { font-size: 1.5em; line-height: 1.3; font-weight: 700; margin-top: 1.6em; color: var(--text-heading, #111111); }
[data-nd-format="markdown"] h3 { font-size: 1.2em; line-height: 1.35; font-weight: 700; margin-top: 1.4em; color: var(--text-heading, #111111); }
[data-nd-format="markdown"] > :first-child { margin-top: 0; }
[data-nd-format="markdown"] ul,
[data-nd-format="markdown"] ol { padding-left: 1.4em; }
[data-nd-format="markdown"] ul { list-style: disc; }
[data-nd-format="markdown"] ol { list-style: decimal; }
[data-nd-format="markdown"] li { margin: 0.3em 0; }
[data-nd-format="markdown"] a { color: var(--color-accent, #e8434f); text-decoration: underline; text-underline-offset: 2px; }
[data-nd-format="markdown"] blockquote {
  border-left: 3px solid var(--color-accent, #e8434f);
  padding: 0.2em 0 0.2em 1em;
  font-size: 1.1em;
  font-style: italic;
  color: var(--text-secondary, #555555);
}
[data-nd-format="markdown"] hr { border: 0; border-top: 1px solid var(--border-color, rgba(0,0,0,0.12)); margin: 2em 0; }

/* Photo tiles (a category tile): the photo eases in on hover, the tile lifts. */
[data-nd-tile] { transition: transform 0.35s ease, box-shadow 0.35s ease; }
[data-nd-tile] [data-nd-tile-media] { transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1); }
[data-nd-tile] [data-nd-tile-cta] { transition: letter-spacing 0.3s ease, color 0.3s ease; }
@media (hover: hover) {
  [data-nd-tile]:hover { transform: translateY(-4px); box-shadow: 0 14px 30px rgba(0, 0, 0, 0.14); }
  [data-nd-tile]:hover [data-nd-tile-media] { transform: scale(1.07); }
  [data-nd-tile]:hover [data-nd-tile-cta] { color: #ffffff; letter-spacing: 0.14em; }
}
@media (prefers-reduced-motion: reduce) {
  [data-nd-tile], [data-nd-tile] * { transition: none !important; transform: none !important; }
}

/* ── Loading skeletons ──────────────────────────────────────────────────────
   Products arrive from the client bundle, so the grid is empty on first paint
   and used to sit blank until the fetch resolved. A skeleton holds the shape of
   the thing that is coming, so the page does not reflow when it lands.

   Each theme sizes its own blocks — a fashion skeleton is a 3:4 rectangle with
   square corners, a toy-shop skeleton is a fat rounded square — so the wait
   already looks like the store. Only the shimmer is shared.

   No JS teardown needed: the runtime clears #product-list before it appends
   real cards, which removes these with it. */
@keyframes sk-shimmer {
  from { background-position: 100% 0; }
  to   { background-position: -100% 0; }
}

.sk {
  background-color: var(--bg-raised, #eeeeee);
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.55) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: sk-shimmer 1.4s ease-in-out infinite;
  /* Never let a skeleton be read out or focused — it is scaffolding. */
  pointer-events: none;
  user-select: none;
}

/* A still skeleton is still a skeleton: it keeps holding the layout, it just
   stops moving for anyone who asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .sk { animation: none; }
}

/* ───────────────────────────────────────────────────────────────────────────
   Theme product cards — responsive rules that must survive cloning.

   The compiler emits mobileStyles/desktopStyles as `#id { … }` rules, and the
   runtime strips the id off every card it clones from #product-card-template.
   So a theme card's whole desktop composition (tech's image-left comparison
   row, food's wider thumbnail) applied to the hidden template and to nothing
   the shopper ever saw. These are the same rules, keyed to the card's class,
   which the clone keeps.
   ─────────────────────────────────────────────────────────────────────────── */

/* Fashion — the add bar is always visible on touch, revealed on hover. */
.fa-tile .fa-add { opacity: 1; transform: translateY(0); }
@media (min-width: 768px) {
  .fa-tile .fa-add { opacity: 0; transform: translateY(100%); }
  .fa-tile:hover .fa-add,
  .fa-tile:focus-within .fa-add { opacity: 1; transform: translateY(0); }
}

@media (min-width: 768px) {
  /* Tech — comparison row: image left, detail right. */
  .tg-cell { flex-direction: row !important; align-items: stretch !important; }
  .tg-cell > :first-child {
    width: 11rem !important;
    flex: 0 0 11rem !important;
    aspect-ratio: auto !important;
    border-right: 1px solid var(--border-color, #DEE1E6) !important;
  }
  /* Two lines, not one: a truncated product name next to an Add button is the
     one thing a comparison row must not hide. */
  .tg-cell .clamp-2 { -webkit-line-clamp: 2 !important; }
  .tg-cell > :nth-child(2) {
    flex: 1 !important;
    justify-content: center !important;
    padding: 1.1rem 1.25rem !important;
    gap: 0.6rem !important;
  }

  /* Beauty — a larger, softer pad. */
  .bs-tile > :first-child { border-radius: 36px !important; padding: 2.4rem !important; }

  /* Food — a wider plate and more air between rows. */
  .fb-row { gap: 1.1rem !important; padding: 1.1rem 0 !important; }
  .fb-row > :first-child {
    width: 7rem !important;
    height: 5.25rem !important;
    border-radius: 16px !important;
  }
}

/* Food rows use a thumbnail, not a card face — the standard wishlist heart is
   sized for a full-width image and spills off a 5.5rem crop. */
.fb-row [data-wishlist-btn] {
  width: 1.65rem !important;
  height: 1.65rem !important;
  top: 0.3rem !important;
  right: 0.3rem !important;
  left: auto !important;
}
.fb-row [data-wishlist-btn] svg { width: 0.85rem !important; height: 0.85rem !important; }

/* ───────────────────────────────────────────────────────────────────────────
   Theme cart rows — same cloning problem as the product cards.

   The cart controller clones #checkout-product-item and strips the id, so the
   compiled `#checkout-product-item { … }` media rules stop matching and every
   themed cart row fell back to its phone layout on desktop.
   ─────────────────────────────────────────────────────────────────────────── */
@media (min-width: 768px) {
  /* Fashion — a wider portrait on the docket. */
  .fa-docket { grid-template-columns: 6rem 1fr auto !important; gap: 1.5rem !important; }

  /* Tech — the table's four columns: item, quantity, line total, remove. */
  .tg-cart-row {
    grid-template-columns: 1fr 8rem 7rem 2rem !important;
    gap: 1rem !important;
    padding: 0.9rem 1rem !important;
  }
  .tg-cart-row > :nth-child(3) { text-align: right !important; }

  /* Beauty — the pad turns side-on, text left of centre. */
  .bs-cart-pad {
    flex-direction: row !important;
    align-items: center !important;
    gap: 2rem !important;
    padding: 1.75rem 2rem !important;
    border-radius: 36px !important;
  }
  .bs-cart-pad > :nth-child(2) { align-items: flex-start !important; text-align: left !important; }
}

/* ───────────────────────────────────────────────────────────────────────────
   Home showcase — how each theme behaves when the merchant has little data.

   A shop with one collection should not show a lonely card in a four-column
   grid, and a shop with three categories should not show a row of chips with
   a gap where nine more would be. `data-count` is stamped by the runtime;
   these rules are the themes' answers to it. Cloned cards lose their id, so
   these are keyed to classes for the same reason the product cards are.
   ─────────────────────────────────────────────────────────────────────────── */

/* One collection is a single wide portrait, not a lonely card in a row of
   four; two stay side by side. Inline styles set the card's aspect ratio, so
   these have to be !important to change it.

   DESKTOP ONLY. Unscoped, the three-across rule also fired on a phone and
   gave each card 103px — a collection called "Winter collection" then wrapped
   across three lines inside it. A phone keeps the two-up grid. */
@media (min-width: 768px) {
  [data-home-zone="collections"][data-count="1"] .fa-collection-grid {
    grid-template-columns: 1fr !important;
  }
  [data-home-zone="collections"][data-count="1"] .fa-collection {
    aspect-ratio: 16 / 7 !important;
  }
  [data-home-zone="collections"][data-count="2"] .fa-collection-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }
  [data-home-zone="collections"][data-count="2"] .fa-collection {
    aspect-ratio: 4 / 5 !important;
  }
  [data-home-zone="collections"][data-count="3"] .fa-collection-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
  }
}


/* A collection with no cover uploaded should not be a giant empty rectangle.
   The runtime marks it; the card becomes a typographic band instead of a
   photograph, which is a design rather than a hole. */
[data-home-zone="collections"][data-count] .fa-collection[data-no-image] {
  aspect-ratio: auto !important;
  min-height: 6.5rem;
  background: var(--text-heading, #111111) !important;
  justify-content: center !important;
}
[data-home-zone="collections"][data-count] .fa-collection[data-no-image] > * { background: none !important; }

/* Two-line clamp, once, for every theme's product name.
   Each card set -webkit-line-clamp inline and none of them set the box
   display it needs, so a long title simply overflowed and was cut mid-line
   with no ellipsis. Thirteen cards, one rule. */
.clamp-2 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* Hide the scrollbar on the food category rail without hiding the scrolling. */
.no-scrollbar { scrollbar-width: none; }
.no-scrollbar::-webkit-scrollbar { display: none; }

/* Tech — a directory grid: more columns on desktop, and a short list stays
   narrow rather than stretching four labels across a 1200px measure. */
@media (min-width: 768px) {
  .tg-index-grid { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; }
  .tg-brand-grid { grid-template-columns: repeat(6, minmax(0, 1fr)) !important; }
  .tg-collection-grid { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
}
[data-home-zone="categories"][data-count="1"] .tg-index-grid,
[data-home-zone="categories"][data-count="2"] .tg-index-grid,
[data-home-zone="categories"][data-count="3"] .tg-index-grid {
  grid-template-columns: repeat(auto-fit, minmax(10rem, 14rem)) !important;
  justify-content: start;
}
[data-home-zone="brands"][data-count="1"] .tg-brand-grid,
[data-home-zone="brands"][data-count="2"] .tg-brand-grid,
[data-home-zone="brands"][data-count="3"] .tg-brand-grid {
  grid-template-columns: repeat(auto-fit, minmax(8rem, 12rem)) !important;
  justify-content: start;
}
[data-home-zone="collections"][data-count="1"] .tg-collection-grid,
[data-home-zone="collections"][data-count="2"] .tg-collection-grid {
  grid-template-columns: repeat(auto-fit, minmax(16rem, 24rem)) !important;
  justify-content: start;
}
/* No cover: the card keeps its frame but loses the empty 16:9 window. */
[data-home-zone="collections"][data-count] .tg-collection[data-no-image] > :first-child {
  display: none !important;
}

/* ── A strip that has nothing to scroll should not look scrolled ──────────
   On a phone the collection strips give each card a fixed share of the width
   so a long list peeks and invites a swipe. With one or two collections that
   peek became a card sliced in half at the screen edge with no way to reach
   it — the merchant has two collections and the second one looked broken.
   Below the count that fits, the cards share the row exactly and the strip
   stops scrolling; above it, the peek is doing its job. */
@media (max-width: 767px) {
  [data-home-zone="collections"][data-count="1"] .sf-collection-grid,
  [data-home-zone="collections"][data-count="2"] .sf-collection-grid,
  [data-home-zone="collections"][data-count="1"] .bs-collection-grid,
  [data-home-zone="collections"][data-count="2"] .bs-collection-grid,
  [data-home-zone="collections"][data-count="1"] .hl-set-grid,
  [data-home-zone="collections"][data-count="2"] .hl-set-grid,
  [data-home-zone="collections"][data-count="1"] .bk-list-grid,
  [data-home-zone="collections"][data-count="2"] .bk-list-grid,
  [data-home-zone="collections"][data-count="1"] .fb-offer-grid,
  [data-home-zone="collections"][data-count="2"] .fb-offer-grid {
    overflow-x: visible !important;
  }
  [data-home-zone="collections"][data-count="1"] .sf-collection-grid > *,
  [data-home-zone="collections"][data-count="2"] .sf-collection-grid > *,
  [data-home-zone="collections"][data-count="1"] .bs-collection-grid > *,
  [data-home-zone="collections"][data-count="2"] .bs-collection-grid > *,
  [data-home-zone="collections"][data-count="1"] .hl-set-grid > *,
  [data-home-zone="collections"][data-count="2"] .hl-set-grid > *,
  [data-home-zone="collections"][data-count="1"] .bk-list-grid > *,
  [data-home-zone="collections"][data-count="2"] .bk-list-grid > *,
  [data-home-zone="collections"][data-count="1"] .fb-offer-grid > *,
  [data-home-zone="collections"][data-count="2"] .fb-offer-grid > * {
    /* An even share of whatever is left after the gap, so two cards meet the
       gutters exactly however wide the phone is. */
    flex: 1 1 0 !important;
    min-width: 0;
  }

  /* Same for the category rails: four discs or fewer fit across a phone, so
     they sit on the line rather than leaving a sliced one at the edge. */
  [data-home-zone="categories"][data-count="1"] .sf-category-rail,
  [data-home-zone="categories"][data-count="2"] .sf-category-rail,
  [data-home-zone="categories"][data-count="3"] .sf-category-rail,
  [data-home-zone="categories"][data-count="4"] .sf-category-rail,
  [data-home-zone="categories"][data-count="1"] .bs-category-row,
  [data-home-zone="categories"][data-count="2"] .bs-category-row,
  [data-home-zone="categories"][data-count="3"] .bs-category-row,
  [data-home-zone="categories"][data-count="4"] .bs-category-row,
  [data-home-zone="categories"][data-count="1"] .bk-genre-row,
  [data-home-zone="categories"][data-count="2"] .bk-genre-row,
  [data-home-zone="categories"][data-count="3"] .bk-genre-row,
  [data-home-zone="categories"][data-count="4"] .bk-genre-row,
  [data-home-zone="categories"][data-count="1"] .hl-room-band,
  [data-home-zone="categories"][data-count="2"] .hl-room-band,
  [data-home-zone="categories"][data-count="3"] .hl-room-band,
  [data-home-zone="categories"][data-count="4"] .hl-room-band,
  [data-home-zone="categories"][data-count="1"] .fb-category-rail,
  [data-home-zone="categories"][data-count="2"] .fb-category-rail,
  [data-home-zone="categories"][data-count="3"] .fb-category-rail,
  [data-home-zone="categories"][data-count="4"] .fb-category-rail,
  [data-home-zone="categories"][data-count="1"] .fa-category-rail,
  [data-home-zone="categories"][data-count="2"] .fa-category-rail,
  [data-home-zone="categories"][data-count="3"] .fa-category-rail,
  [data-home-zone="categories"][data-count="4"] .fa-category-rail {
    overflow-x: visible !important;
    justify-content: space-between;
  }
}

/* ── A collection strip is a way in, not a billboard ──────────────────────
   Square pads three across a wide screen are ~480px tall each: the whole
   first screen is collections and the products are below the fold. These cap
   what the strip may take on desktop, whatever the viewport does.

   The cap differs by where the card puts its name, because that decides how
   much room the card needs to stay legible:
     - name OVER the picture  → the card carries the name, the count and its
       shop link, so it needs ~9rem; capping it at a picture's height made
       those three overlap each other (and the photograph).
     - name UNDER the picture → only the picture is capped; the caption sits
       below it and is unaffected.
   And the strip is NOT centred: it lines up with the heading above it, which
   on a left-aligned theme sits at the page gutter. Centring it left a hole
   where the heading started and the cards did not. */
@media (min-width: 768px) {
  [data-home-zone="collections"] .sf-collection,
  [data-home-zone="collections"] .hl-set,
  [data-home-zone="collections"] .bk-list,
  [data-home-zone="collections"] .fa-collection {
    aspect-ratio: auto !important;
    max-height: 9rem !important;
    min-height: 8rem !important;
  }
  [data-home-zone="collections"] .bs-collection > :first-child,
  [data-home-zone="collections"] .tg-collection > :first-child {
    aspect-ratio: 16 / 10 !important;
    max-height: 5.5rem !important;
  }

  /* Category pictures: capped by HEIGHT, with the theme's own aspect left
     alone — forcing them square turned fashion's 4:5 portraits into stamps. */
  [data-home-zone="categories"] .sf-category-chip > :first-child,
  [data-home-zone="categories"] .bs-category-pill > :first-child,
  [data-home-zone="categories"] .bk-genre > :first-child,
  [data-home-zone="categories"] .hl-room > :first-child,
  [data-home-zone="categories"] .fb-category-chip > :first-child {
    max-height: 4.5rem !important;
  }
  [data-home-zone="categories"] .fa-category-tile {
    max-width: 5.5rem !important;
  }
  [data-home-zone="categories"] .sf-category-chip > :first-child,
  [data-home-zone="categories"] .bs-category-pill > :first-child,
  [data-home-zone="categories"] .bk-genre > :first-child,
  [data-home-zone="categories"] .hl-room > :first-child,
  [data-home-zone="categories"] .fb-category-chip > :first-child {
    max-width: 4.5rem !important;
  }
}

/* Beauty — three pads across on desktop; one or two stay centred and small
   rather than blowing a single circle up to half the page. */
@media (min-width: 768px) {
  .bs-collection-grid { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; gap: 2.5rem !important; }
}
[data-home-zone="collections"][data-count="1"] .bs-collection-grid,
[data-home-zone="collections"][data-count="2"] .bs-collection-grid {
  grid-template-columns: repeat(auto-fit, minmax(12rem, 18rem)) !important;
  justify-content: center;
}
[data-home-zone="collections"][data-count] .bs-collection[data-no-image] > :first-child {
  display: flex !important;
  align-items: center;
  justify-content: center;
}

/* Food — the offer list goes two-up on desktop, one-up while there is one. */
@media (min-width: 768px) {
  .fb-offer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
}
[data-home-zone="collections"][data-count="1"] .fb-offer-grid {
  grid-template-columns: minmax(0, 32rem) !important;
}
[data-home-zone="collections"][data-count] .fb-offer[data-no-image] > :first-child {
  display: none !important;
}

/* A collection with no cover falls back to its initial. Only fashion had a
   treatment for it; everywhere else the frame stayed empty, so a shop whose
   one collection has no cover showed a blank grey slab above its products.
   The initial is now shown by default and a theme opts out. */
[data-collection-initial] {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-height: 3.5rem;
  font-family: var(--font-display);
  font-size: 1.75rem;
  line-height: 1;
  color: var(--text-muted);
}
/* Inside a mosaic the tiles are the treatment, so the letter stays out. */
.fa-collection-mosaic [data-collection-initial],
.fb-offer [data-collection-initial] { display: none; }
[data-home-zone="collections"][data-count] .bs-collection[data-no-image] [data-collection-initial] {
  font-family: var(--font-display);
  font-size: 2.25rem;
  font-weight: 400;
  color: var(--text-muted, #9A8F8A);
  line-height: 1;
}
[data-home-zone="collections"][data-count] .bs-collection[data-no-image] > :first-child {
  aspect-ratio: 16 / 9 !important;
  min-height: 4.5rem;
}
@media (min-width: 768px) {
  [data-home-zone="collections"][data-count] .bs-collection[data-no-image] > :first-child {
    aspect-ratio: auto !important;
    min-height: 8rem;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   FASHION — the moving parts.

   The compiler's inline styles can lay a page out but cannot express a
   keyframe, a hover that changes two elements, or a state driven by scroll.
   That is what this block is for, and it stays small: sections reveal as they
   are reached and cards answer a hover. There is no hero banner and no
   marquee — this is a catalogue, and the products are the page. Everything
   switches off under prefers-reduced-motion at the bottom of the block.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Reveal ──────────────────────────────────────────────────────────────── */
[data-reveal] > * {
  opacity: 0;
  transform: translateY(1.75rem);
  transition:
    opacity 700ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-delay, 0ms),
    transform 700ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-delay, 0ms);
}
[data-reveal].is-revealed > * { opacity: 1; transform: translateY(0); }

/* ── Cards ───────────────────────────────────────────────────────────────── */
/* The photograph moves, the frame does not. */
.fa-tile img, .fa-collection img {
  transition: transform 900ms cubic-bezier(0.22, 1, 0.36, 1);
}
.fa-tile:hover img, .fa-collection:hover img { transform: scale(1.045); }

.fa-collection { transition: transform 500ms cubic-bezier(0.22, 1, 0.36, 1); }
.fa-collection:hover { transform: translateY(-4px); }

/* Category rows: an ink wipe from the left, type inverting as it passes. */
/* (superseded: the category rows became image tiles) */
.fa-category-row::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--text-heading, #111);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 460ms cubic-bezier(0.22, 1, 0.36, 1);
}
.fa-category-row:hover::before { transform: scaleX(1); }
.fa-category-row { transition: color 300ms ease 60ms; }
.fa-category-row:hover { color: var(--bg-page, #fff); }
.fa-category-row .fa-category-arrow { transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1); }
.fa-category-row:hover .fa-category-arrow { transform: translateX(0.4rem); }

@media (min-width: 768px) {
  .fa-category-name { font-size: 2rem !important; }
  .fa-category-row { padding: 1.5rem 1.25rem !important; }
}

/* ── Reduced motion ──────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  [data-reveal] > *,
  .fa-tile img,
  .fa-collection,
  .fa-category-row,
  .fa-category-arrow {
    animation: none !important;
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ── Fashion: category rail + collection grid ─────────────────────────────── */
/* The cloned tiles lose their id, so their responsive rules live here. */
.fa-collection-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.4rem; }
@media (min-width: 768px) {
  .fa-collection-grid { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; gap: 1.5rem !important; }
  /* Desktop has room for the whole set, so the rail stops being a rail. */
  .fa-category-rail {
    display: grid !important;
    grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
    gap: 1.5rem !important;
    overflow: visible !important;
    padding: 0 !important;
  }
  .fa-category-tile { width: auto !important; }
}
/* Sparse: a handful of categories sit left, sized, rather than stretching. */
[data-home-zone="categories"][data-count="1"] .fa-category-rail,
[data-home-zone="categories"][data-count="2"] .fa-category-rail,
[data-home-zone="categories"][data-count="3"] .fa-category-rail,
[data-home-zone="categories"][data-count="4"] .fa-category-rail {
  grid-template-columns: repeat(auto-fit, minmax(9rem, 12rem)) !important;
  justify-content: start;
}
.fa-category-tile img { transition: transform 700ms cubic-bezier(0.22, 1, 0.36, 1); }
/* A collection's name gets the card's full width and breaks rather than
   running past the edge — "WINTER COLLECTION" on a 197px phone card was
   clipped mid-word. */
.fa-collection-name { overflow-wrap: anywhere; }
.fa-category-tile:hover img { transform: scale(1.06); }
/* A category with no picture keeps the tile and shows its initial. */
.fa-category-tile[data-no-image] [data-collection-initial] {
  display: block;
  font-family: var(--font-display);
  font-size: 2rem;
  color: var(--text-muted, #8A8A8A);
}

/* Category tiles in the other three themes — the cloned chips lose their id,
   so their layout and the no-picture fallback live here. */
@media (min-width: 768px) {
  .bs-category-row { gap: 2rem !important; }
  .fb-category-rail { gap: 1.25rem !important; }
}
.bs-category-pill img,
.fb-category-chip img { transition: transform 600ms cubic-bezier(0.22, 1, 0.36, 1); }
.bs-category-pill:hover img,
.fb-category-chip:hover img { transform: scale(1.08); }
.fb-category-chip:hover > :first-child { border-color: var(--color-primary, #D1462F) !important; }

.bs-category-pill[data-no-image] [data-collection-initial],
.fb-category-chip[data-no-image] [data-collection-initial],
.tg-index-cell[data-no-image] [data-collection-initial] {
  display: block;
  font-family: var(--font-display);
  color: var(--text-muted);
}
.bs-category-pill[data-no-image] [data-collection-initial] { font-size: 1.6rem; }
.fb-category-chip[data-no-image] [data-collection-initial] { font-size: 1.2rem; }
.tg-index-cell[data-no-image] [data-collection-initial] { font-size: 0.9rem; }

/* ── Fashion product page ─────────────────────────────────────────────────── */
/* The runtime sets `display:flex` inline on #product-template when the product
   loads, so the desktop spread has to be declared here to outrank it. */
@media (min-width: 768px) {
  .fa-pdp-spread {
    display: grid !important;
    grid-template-columns: minmax(0, 1.15fr) minmax(22rem, 0.85fr);
    gap: 4rem;
    align-items: start;
    max-width: 1320px;
    margin: 0 auto;
    padding: 2.5rem 2.5rem 1rem;
    box-sizing: border-box;
  }
  .fa-pdp-measure {
    position: sticky;
    top: 6rem;
    padding: 0 !important;
    gap: 1.75rem !important;
  }
  /* On desktop the CTA is not a bar across the page: it sits under the
     measure, in the measure's own column. */
  .fa-pdp-cta {
    position: static !important;
    border: none !important;
    box-sizing: border-box;
    width: 100%;
    max-width: 1320px;
    margin: 0 auto 3.5rem;
    padding: 0 2.5rem !important;
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(22rem, 0.85fr);
    gap: 4rem;
  }
  .fa-pdp-cta > * { grid-column: 2; }
}
/* ── Themed product page (every theme but fashion, which has its own) ─────── */
/* Same reason as above: the runtime writes `display:flex` inline onto
   #product-template when the product loads, so the desktop spread has to be
   declared in a stylesheet to outrank it. Each theme sets its own measure and
   column split through these custom properties. */
@media (min-width: 768px) {
  .sf-pdp-spread {
    display: grid !important;
    grid-template-columns: minmax(0, 1.1fr) minmax(20rem, 0.9fr);
    gap: 3.5rem;
    align-items: start;
    max-width: 1240px;
    margin: 0 auto;
    padding: 2.5rem 2.5rem 1rem;
    box-sizing: border-box;
  }
  .sf-pdp-measure {
    position: sticky;
    top: 6rem;
    padding: 0 !important;
    gap: 1.75rem !important;
  }
  .sf-pdp-cta {
    position: static !important;
    border: none !important;
    box-sizing: border-box;
    width: 100%;
    max-width: 1240px;
    margin: 0 auto 3.5rem;
    padding: 0 2.5rem !important;
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(20rem, 0.9fr);
    gap: 3.5rem;
  }
  .sf-pdp-cta > * { grid-column: 2; }

  /* Three measures, because a jeweller's page and a marketplace listing do
     not want the same amount of room. The page sets one on itself and the
     spread and CTA inside it follow. */
  .sf-pdp-narrow .sf-pdp-spread,
  .sf-pdp-narrow .sf-pdp-cta { max-width: 1040px; }
  .sf-pdp-wide .sf-pdp-spread,
  .sf-pdp-wide .sf-pdp-cta { max-width: 1440px; }
  .sf-pdp-narrow .sf-pdp-spread { gap: 3rem; }
}

.pdp-thumb { transition: border-color 220ms ease, opacity 220ms ease; }
.pdp-thumb:hover { border-color: var(--text-heading, #111) !important; }

/* The product page wears the theme header on desktop and the floating
   controls on a phone — never both. */
.fa-pdp-page > #fa-header { display: none; }
@media (min-width: 768px) {
  .fa-pdp-page > #fa-header { display: grid; }
}

/* A coverless collection shows what is inside it: a strip of its own products,
   dimmed just enough for the name to sit over them. */
.fa-collection-mosaic { display: flex; }
.fa-collection-ghost { display: none; }
[data-home-zone="collections"][data-count] .fa-collection-ghost { color: rgba(0, 0, 0, 0.045) !important; }
.fa-collection-mosaic img {
  flex: 1;
  min-width: 0;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: brightness(0.82);
}
[data-home-zone="collections"][data-count] .fa-collection[data-no-image] {
  background: var(--bg-raised, #F2F0ED) !important;
  min-height: 0;
}
/* With photographs behind it, the coverless card is a wide picture strip. */
[data-home-zone="collections"][data-count="1"] .fa-collection[data-has-tiles] {
  aspect-ratio: 16 / 5 !important;
}
/* With none — a collection whose products have no photographs either — the
   card is SET rather than filled. Paper, a hairline, the name at display size
   with an oversized ghost of itself behind it, the count, and Shop →. No
   black slab, and nothing pretending to be a photograph. */
[data-home-zone="collections"][data-count] .fa-collection[data-no-image]:not([data-has-tiles]) {
  aspect-ratio: auto !important;
  min-height: 0;
  background: var(--bg-raised, #F2F0ED) !important;
  border: 1px solid var(--border-color, #E6E4E0);
  color: var(--text-heading, #111) !important;
  overflow: hidden;
}
[data-home-zone="collections"][data-count] .fa-collection[data-no-image]:not([data-has-tiles]) .fa-collection-mosaic {
  display: none !important;
}
[data-home-zone="collections"][data-count] .fa-collection[data-no-image]:not([data-has-tiles]) .fa-collection-ghost {
  display: block;
}
/* The caption block: no scrim, ink type, room to breathe. */
[data-home-zone="collections"][data-count] .fa-collection[data-no-image]:not([data-has-tiles]) > :last-child {
  background: none !important;
  padding: 1.6rem 1.4rem !important;
  align-items: flex-end !important;
}
[data-home-zone="collections"][data-count] .fa-collection[data-no-image]:not([data-has-tiles]) .fa-collection-name {
  color: var(--text-heading, #111) !important;
  font-size: 1.5rem !important;
  letter-spacing: -0.01em !important;
  text-transform: none !important;
}
[data-home-zone="collections"][data-count] .fa-collection[data-no-image]:not([data-has-tiles]) .fa-collection-count {
  color: var(--text-muted, #8A8A8A) !important;
}
/* Shop → becomes an outlined control on the light card. */
[data-home-zone="collections"][data-count] .fa-collection[data-no-image]:not([data-has-tiles]) .fa-collection-go {
  color: var(--text-heading, #111) !important;
  border: 1px solid currentColor;
  padding: 0.6rem 1.1rem;
  border-radius: 9999px;
  /* Sits ON the ghost type, so it needs its own ground. */
  background: var(--bg-page, #fff);
  position: relative;
  z-index: 1;
}
[data-home-zone="collections"][data-count] .fa-collection[data-no-image]:not([data-has-tiles]) .fa-collection-go:hover {
  background: var(--text-heading, #111);
  color: var(--bg-page, #fff) !important;
}

/* The collection's "Shop →" travels on hover, like the category rows do. */
.fa-collection-go > :last-child { transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1); }
.fa-collection:hover .fa-collection-go > :last-child { transform: translateX(0.4rem); }
@media (prefers-reduced-motion: reduce) {
  .fa-collection-go > :last-child { transition: none !important; transform: none !important; }
}

/* The catalogue invitation inverts on hover; its arrow travels. */
.fa-shop-all { transition: background 320ms ease, color 320ms ease; }
.fa-shop-all:hover { background: var(--text-heading, #111); color: var(--bg-page, #fff); }
.fa-shop-all:hover * { color: var(--bg-page, #fff) !important; }
.fa-shop-all-arrow { transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1); }
.fa-shop-all:hover .fa-shop-all-arrow { transform: translateX(0.5rem); }
@media (prefers-reduced-motion: reduce) {
  .fa-shop-all, .fa-shop-all-arrow { transition: none !important; transform: none !important; }
}

/* ── A collection's own page ──────────────────────────────────────────────
   The hero used to be hardcoded pixel sizes in the runtime, so every theme
   got the same 1.65rem bold title over the same 16rem cover. These read the
   theme's tokens instead, which is what makes a bookshop's collection page
   look like a bookshop. */
.sf-collection-hero {
  /* The products' section is max-width 1200 with a 1rem / 2rem gutter. The
     hero matches it exactly so the cover's edges sit on the same line as the
     first and last product in the grid. */
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 1.25rem 1rem 1rem;
  box-sizing: border-box;
  gap: 0.5rem !important;
}
.sf-collection-cover {
  width: 100%;
  max-height: 11rem;
  object-fit: cover;
  border-radius: var(--border-radius, 12px);
  margin-bottom: 0.5rem;
}
.sf-collection-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-heading);
}
/* The set's closing panel, under the products. A band the width of the grid
   it follows: what the whole thing costs, what that saves, and the action. */
.sf-set-panel {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  width: 100%;
  margin-top: 1.75rem;
  padding: 1.1rem 1.25rem 1.25rem;
  box-sizing: border-box;
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius, 14px);
}
.sf-set-copy { display: flex; flex-direction: column; gap: 0.2rem; }
.sf-set-heading {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text-heading);
}
.sf-set-note {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-muted);
}
.sf-set-action {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem;
}
.sf-set-price {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--text-heading);
}
.sf-set-was {
  font-size: 0.9rem;
  color: var(--text-muted);
  text-decoration: line-through;
}
.sf-collection-cta {
  /* Full width on a phone: this is the page's one action, and a button
     floated to the right of a price is a small target for a thumb. */
  width: 100%;
  background: var(--color-primary);
  color: var(--button-text);
  border: none;
  border-radius: var(--border-radius, 12px);
  padding: 0.8rem 1.6rem;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
}
@media (min-width: 768px) {
  .sf-set-panel {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 2rem;
    padding: 1.25rem 1.75rem;
  }
  .sf-set-heading { font-size: 1.2rem; }
  .sf-set-action { flex-wrap: nowrap; }
  .sf-collection-cta { width: auto; }
}
.sf-collection-cta:disabled { opacity: 0.6; cursor: default; }

@media (min-width: 768px) {
  .sf-collection-hero {
    padding: 1.5rem 2rem 1.25rem;
  }
  .sf-collection-cover { max-height: 15rem; }
  .sf-collection-title { font-size: 2rem; }
}

/* A collection in the cart is ONE line: its name, its price, one stepper.
   Its members are listed inside it, quietly. */
.sf-cart-collection-members {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin-top: 0.3rem;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.sf-cart-collection-badge {
  display: inline-block;
  margin-bottom: 0.25rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: var(--bg-raised, #eee);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* An order line that came from a collection says so, quietly, under the
   product name — including when the collection itself has since been
   deleted (the order carries its own copy of the name and price). */
.order-line-collection {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.75rem;
  color: var(--text-muted);
}

/* A collection that no longer exists: say so, and open the two doors out. */
.sf-collection-missing {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 3rem 1.5rem;
  text-align: center;
}
.sf-collection-missing-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--text-heading);
}
.sf-collection-missing-note {
  margin: 0;
  max-width: 34ch;
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--text-muted);
}
.sf-collection-missing-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 0.75rem;
  flex-wrap: wrap;
  justify-content: center;
}
.sf-collection-missing-link {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-muted);
}
.sf-collection-missing .sf-collection-cta { width: auto; text-decoration: none; }
