@charset "UTF-8";
/* =========================
   Base
========================= */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  min-height: 100vh;
}

img,
picture,
svg,
video {
  display: block;
  max-width: 100%;
}

/* Safety net: any raw inline <svg> icon dropped anywhere in markup renders
   at a sane, predictable size by default. Specific contexts (.icon-btn,
   .trust-item__icon, etc.) size their icons explicitly and win via
   specificity — this rule only protects against an icon with no sizing
   context at all, which is what caused the oversized-icon bug. */
svg {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0;
}

a,
button {
  /* Mobile Safari/Chrome paint their own gray tap-highlight rectangle over any tapped link or
     button by default -- a separate browser behavior from CSS :focus/:focus-visible entirely, so
     it stays visible (and can look "stuck" through a press-and-hold or a slow tap) even though
     every button/link in this project already scopes its own focus ring to :focus-visible below.
     Disabling only the highlight, not the outline rule itself, keeps real keyboard focus fully
     intact for keyboard users. */
  -webkit-tap-highlight-color: transparent;
}

a {
  color: inherit;
  text-decoration: none;
}

ul,
ol {
  list-style: none;
  padding: 0;
  margin: 0;
}

table {
  border-collapse: collapse;
}

:focus-visible {
  outline: none;
  outline-offset: 2px;
}

:root {
  /* Palette — extracted from a reference perfume retailer's live, real CSS custom properties
     (see REFERENCE_COLOR_PALETTE.md at the repo root for the exact extraction): a deliberately
     monochrome white / near-black / light-gray system with a single dark-navy secondary accent
     reserved for limited emphasis (matching the reference's own restrained "black is the only
     real accent, hierarchy comes from opacity, not hue" strategy) — no gold, no beige/cream, no
     brown/tobacco, no burgundy. Variable names below are kept stable (components.css/base.css/
     loading.css reference many of them directly) — repointed to the new values so every consumer
     picks up the rebrand without needing to change selector-by-selector. WhatsApp's own official
     green is the one deliberate exception, reserved only for WhatsApp CTAs/icons. */
  --color-porcelain: #ffffff;
  --color-ivory: #ffffff;
  --color-champagne: #f3f3f3;
  --color-sand: #e2e2e2;
  --color-espresso: #121212;
  --color-espresso-soft: #242833;
  --color-charcoal: #121212;
  --color-charcoal-soft: rgba(18, 18, 18, 0.55);
  --color-gold: #121212;
  --color-gold-soft: #242833;
  --color-gold-deep: #000000;
  --color-blush: var(--color-gold-soft);
  --color-blush-soft: var(--color-champagne);
  --color-white: #ffffff;
  --color-navy: #242833;
  --color-whatsapp: #25d366;
  /* Explicit, narrowly-scoped exception — the site's one deliberate gold, per explicit direction.
     Used only by the two scroll-progress indicators (the top page-scroll line and the Back to Top
     button's ring); not reintroduced as a general accent elsewhere in the palette. */
  --color-scroll-progress-gold: #b8912f;
  /* Explicit semantic token names */
  --color-black: var(--color-espresso);
  --color-black-soft: var(--color-espresso-soft);
  --color-gold-light: var(--color-gold-soft);
  --color-muted: var(--color-charcoal-soft);
  --color-page: var(--color-porcelain);
  --color-surface-alt: var(--color-champagne);
  --color-card: var(--color-white);
  --color-footer: var(--color-white);
  --color-primary: var(--color-espresso);
  --color-primary-hover: rgba(18, 18, 18, 0.06);
  --color-secondary: var(--color-navy);
  --color-input: rgba(18, 18, 18, 0.55);
  /* Semantic tokens */
  --color-bg: var(--color-porcelain);
  --color-bg-alt: var(--color-ivory);
  --color-bg-warm: var(--color-champagne);
  --color-bg-deep: var(--color-espresso);
  --color-surface: var(--color-white);
  --color-surface-warm: var(--color-porcelain);
  --color-text: rgba(18, 18, 18, 0.75);
  --color-text-muted: var(--color-charcoal-soft);
  --color-text-on-dark: var(--color-white);
  --color-text-on-dark-muted: rgba(255, 255, 255, 0.75);
  --color-heading: var(--color-charcoal);
  --color-accent: var(--color-espresso);
  --color-accent-soft: rgba(18, 18, 18, 0.55);
  --color-accent-deep: #000000;
  --color-highlight: var(--color-navy);
  --color-border: rgba(18, 18, 18, 0.12);
  --color-border-strong: rgba(18, 18, 18, 0.28);
  --color-danger: #a13f3f;
  --color-success: #3f6b4a;
  --color-success-bg: #e7efe4;
  /* Skeleton/loading shimmer — neutral light-gray, matching the reference's own light-gray
     alternate-surface tone. */
  --color-skeleton-base: var(--color-white);
  --color-skeleton-shine: var(--color-champagne);
  /* Typography */
  --font-heading: "Cormorant Garamond", "Georgia", serif;
  --font-body: "Inter", "Segoe UI", sans-serif;
  --font-heading-ar: "Cairo", "Tahoma", sans-serif;
  --font-body-ar: "Cairo", "Tahoma", sans-serif;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-md: 1.125rem;
  --text-lg: 1.375rem;
  --text-xl: 1.75rem;
  --text-2xl: 2.25rem;
  --text-3xl: 2.75rem;
  --text-hero: 3.25rem;
  --leading-tight: 1.15;
  --leading-normal: 1.6;
  --leading-relaxed: 1.8;
  /* Spacing scale (4px base) — generous, this design lives on white space */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2.25rem;
  --space-7: 3.5rem;
  --space-8: 5rem;
  --space-9: 7rem;
  --space-10: 9rem;
  /* Public storefront rhythm: compact on phones, comfortably editorial on large screens. */
  --storefront-section-space: clamp(1.5rem, 0.85rem + 3vw, 3.5rem);
  --storefront-section-space-tight: clamp(1rem, 0.7rem + 1.5vw, 2.25rem);
  --storefront-heading-gap: clamp(0.875rem, 1.6vw, 1.5rem);
  --storefront-content-gap: clamp(0.75rem, 1.6vw, 1.5rem);
  --storefront-grid-gap: clamp(0.625rem, 1vw, 1rem);
  --storefront-card-gap: clamp(0.3rem, 0.55vw, 0.5rem);
  --storefront-page-header-space: clamp(0.875rem, 0.55rem + 1.8vw, 2.25rem);
  /* Layout */
  --container-max: 1400px;
  --container-padding: var(--space-4);
  --radius-sm: 3px;
  --radius-md: 6px;
  --radius-lg: 10px;
  --radius-xl: 18px;
  /* Effects — the reference's own theme sets shadow-opacity to 0 almost everywhere (depth comes
     from hairline borders, not shadows); these stay very restrained/near-flat to match, using the
     same near-black tone as the rest of the palette rather than a colored glow. */
  --shadow-sm: 0 1px 3px rgba(18, 18, 18, 0.08);
  --shadow-md: 0 10px 26px rgba(18, 18, 18, 0.10);
  --shadow-lg: 0 20px 48px rgba(18, 18, 18, 0.14);
  --shadow-gold: 0 8px 22px rgba(18, 18, 18, 0.14);
  /* Motion — centralized so every component pulls from the same small set of durations/curves
     instead of hand-rolling its own. --transition-fast/base already carry the "premium" curve
     and --ease-luxury the "cinematic settle" curve; --ease-sharp below is the third reference
     curve, reserved for quick snap-back/press/exit motion (never entrances). Every one of these
     collapses to 1ms under prefers-reduced-motion (see the media query further down) — since
     almost every transition site-wide is declared in terms of these vars, that one override
     neutralizes decorative motion globally without a per-component reduced-motion rule. */
  --transition-fast: 180ms cubic-bezier(0.22, 1, 0.36, 1);
  --transition-base: 320ms cubic-bezier(0.22, 1, 0.36, 1);
  /* 400ms — fast/smooth per the site-wide reveal-timing spec (section reveal 280-420ms, heading
     reveal 320-480ms); this is the single shared "slow" tier every entrance/reveal transition
     pulls from, so tightening it here tightens all of them together. */
  --transition-slow: 400ms cubic-bezier(0.16, 1, 0.3, 1);
  --ease-luxury: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-premium: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-sharp: cubic-bezier(0.76, 0, 0.24, 1);
  /* Drawer/modal speed are named aliases of the existing slow/base durations (not new values) —
     used where a rule wants to say "this is a drawer/modal timing" for readability rather than
     re-deriving the same number. */
  --drawer-speed: var(--transition-slow);
  --modal-speed: var(--transition-base);
  /* Scroll-reveal travel distance — responsive by itself (no separate mobile override needed
     anywhere that reads it): short on narrow phones, capped on desktop so it never overshoots. */
  --reveal-distance: clamp(8px, 2vw, 18px);
  --reveal-distance-sm: clamp(8px, 1.5vw, 14px);
  --stagger-delay: 50ms;
  --hover-scale: 1.03;
  --hover-scale-lg: 1.06;
  --blur-amount: 8px;
  --blur-amount-sm: 4px;
  /* Header — these are only the static fallback values used before JS runs (and if it's ever
     disabled); animations.js's setupHeaderHeightSync() measures the real .site-header and
     overwrites this with the actual pixel value, clamped and change-gated so it can never grow
     without bound (see that function's own comments for why that matters). The desktop value is
     larger than mobile because .site-header__category-row only exists at that breakpoint (see
     the matching min-width override below and .site-header__category-row's own display rule).
     Compact header pass: .site-header__logo now carries an explicit per-breakpoint height (3rem/
     4.25rem/4.5rem — see components.css) instead of the bar being sized purely by the logo's own
     intrinsic size, so these estimates track that much shorter real content height per tier.
     --announce-height is unused while .site-header__announce is commented out in Header.cshtml
     (see "Header announcement temporarily disabled") — kept, not deleted, so re-enabling it there
     only needs the matching min-width(960px) term below restored, not a new variable. */
  --header-height: 4.5rem;
  /* Same value as --header-height, kept as its own named token because the homepage hero's own
     height calc (see .hero--cinematic) reads this one specifically — see setupHeaderHeightSync()
     in animations.js, which measures the real rendered header (both rows) and writes the precise
     pixel value to both variables together on every resize. */
  --site-header-height: var(--header-height);
  --announce-height: 2.25rem;
  /* Icon sizing scale — every icon context reads from one of these so
     nothing can render at an unconstrained default size again. */
  --icon-sm: 1rem;
  --icon-md: 1.25rem;
  --icon-lg: 1.5rem;
}

@media (min-width: 768px) {
  :root {
    --container-padding: var(--space-6);
    --text-2xl: 2.75rem;
    --text-3xl: 3.5rem;
    --text-hero: 4.5rem;
    --header-height: 6rem;
  }
}
@media (min-width: 960px) {
  :root {
    /* .site-header__category-row only exists from this breakpoint up — bar (~logo 4.5rem height
       + padding) plus the compact nav row together, not the old announce-band term (dropped while
       that band renders nothing — restore it here too if it's ever re-enabled). */
    --header-height: 8.5rem;
  }
}
[lang=ar],
[dir=rtl] {
  --font-heading: var(--font-heading-ar);
  --font-body: var(--font-body-ar);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --transition-fast: 1ms linear;
    --transition-base: 1ms linear;
    --transition-slow: 1ms linear;
  }
}
h1,
h2,
h3,
h4 {
  font-family: var(--font-heading);
  color: var(--color-heading);
  line-height: var(--leading-tight);
  font-weight: 600;
  letter-spacing: 0.01em;
}

[dir=rtl] h1,
[dir=rtl] h2,
[dir=rtl] h3,
[dir=rtl] h4 {
  letter-spacing: 0;
}

/* Fluid, not the fixed --text-* value alone: every one of these already resolves to the exact
   same fixed size from 768px up (nothing on tablet/desktop changes), they only scale down below
   that. This is the base heading scale every page's h1-h4 falls back to when it has no more
   specific component override of its own (e.g. a product detail page's <h1> product name, or a
   plain page's <h1>/<h2>) — with no responsive rule at all here, those were rendering at the
   full desktop size (h1=44px) on a 320px phone with zero scaling, which is the single biggest
   reason page/product titles were reading oversized on mobile. Each level keeps its own clamp so
   h1 > h2 > h3 > h4 stays visually true at every width, not a flat shrink-everything ratio. */
h1 {
  font-size: clamp(1.85rem, 1.5rem + 3vw, var(--text-3xl));
}

h2 {
  font-size: clamp(1.5rem, 1.2rem + 2.4vw, var(--text-2xl));
}

h3 {
  font-size: clamp(1.35rem, 1.1rem + 1.8vw, var(--text-xl));
}

h4 {
  font-size: clamp(1.15rem, 1rem + 1vw, var(--text-lg));
}

p {
  color: var(--color-text);
}

html {
  direction: ltr;
  scroll-behavior: smooth;
  /* The real scrolling element in standards mode is <html>, not <body> —
     overflow-x:hidden on body alone doesn't reliably stop page-level
     horizontal scroll caused by full-viewport fixed overlays (mobile nav,
     filter drawer) or the carousel, especially under RTL. */
  overflow-x: hidden;
}

html[dir=rtl] {
  direction: rtl;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}
/* ---------------------------------------------------------------------- */
/* Storefront scrollbar                                                  */
/*                                                                        */
/* This stylesheet (compiled to wwwroot/css/style.css) is only ever       */
/* linked from Views/_layout.cshtml (see its <link> tag) — Umbraco's own  */
/* backoffice is a separate SPA                                          */
/* under /umbraco that never references this stylesheet, so this styling  */
/* can only ever reach public storefront pages, in both languages, never  */
/* the admin/dashboard UI. Firefox via the standard scrollbar-width/color  */
/* properties; Chromium/WebKit via the older but still-required           */
/* ::-webkit-scrollbar pseudo-elements. Never `scrollbar-width: none` or   */
/* `display: none` — a thin, low-contrast bar stays present and usable.   */
/* ---------------------------------------------------------------------- */
html {
  scrollbar-width: thin;
  scrollbar-color: var(--color-border-strong) transparent;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background-color: var(--color-border-strong);
  border-radius: 999px;
  /* A same-color-as-page border + padding-box clip trims the thumb visually thinner than the
     full 10px track without an extra wrapper element — the classic thin-scrollbar technique. */
  border: 2px solid var(--color-bg);
  background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
  background-color: var(--color-accent);
}

::-webkit-scrollbar-corner {
  background: transparent;
}

/* ---------------------------------------------------------------------- */
/* Custom cursor                                                          */
/*                                                                        */
/* The dot/ring elements and the `custom-cursor-active` class are only    */
/* ever added by custom-cursor.js, and only after it has confirmed        */
/* (hover: hover) and (pointer: fine) and no prefers-reduced-motion — so   */
/* `cursor: none` below can never apply on a touch device, a coarse        */
/* pointer, a reduced-motion preference, or if that script fails to load  */
/* or throws. Scoped the same way the scrollbar above is (this stylesheet */
/* only loads on public storefront pages), so this never reaches Umbraco's */
/* backoffice either.                                                     */
/* ---------------------------------------------------------------------- */
@media (hover: hover) and (pointer: fine) {
  html.custom-cursor-active,
  html.custom-cursor-active * {
    cursor: none;
  }
  /* Native cursor stays for anything text-entry/selection/resize-related, or disabled — a
     hidden cursor over a field the user is meant to type/select/resize into would just be
     confusing, not premium. */
  html.custom-cursor-active input,
  html.custom-cursor-active textarea,
  html.custom-cursor-active select,
  html.custom-cursor-active [contenteditable=true],
  html.custom-cursor-active [contenteditable=""],
  html.custom-cursor-active button:disabled,
  html.custom-cursor-active input:disabled,
  html.custom-cursor-active textarea:disabled,
  html.custom-cursor-active select:disabled,
  html.custom-cursor-active [aria-disabled=true] {
    cursor: auto;
  }
}
.custom-cursor,
.custom-cursor-ring {
  position: fixed;
  top: 0;
  left: 0;
  /* pointer-events: none is what guarantees this can never intercept or block a click — every
     mouse/pointer event still reaches the real element beneath it. */
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--transition-fast);
}

.custom-cursor.is-visible,
.custom-cursor-ring.is-visible {
  opacity: 1;
}

.custom-cursor {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-accent);
  z-index: 2147483647;
  will-change: transform;
  transition: opacity var(--transition-fast), background-color var(--transition-base);
}

.custom-cursor.is-on-dark {
  background: #ffffff;
}

.custom-cursor-ring {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--color-border-strong);
  background: rgba(18, 18, 18, 0.03);
  z-index: 2147483646;
  will-change: transform;
  transition: opacity var(--transition-fast), width var(--transition-fast), height var(--transition-fast), border-color var(--transition-fast), background-color var(--transition-fast);
}

.custom-cursor-ring.is-hovering {
  width: 46px;
  height: 46px;
  border-color: var(--color-accent);
  background: rgba(18, 18, 18, 0.05);
}

.custom-cursor-ring.is-on-dark {
  border-color: rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.1);
}

.custom-cursor-ring.is-hovering.is-on-dark {
  border-color: #ffffff;
  background: rgba(255, 255, 255, 0.16);
}

body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  /*  -webkit-font-smoothing: antialiased;
    overflow-x: hidden;*/
}

::selection {
  background: var(--color-gold);
  color: var(--color-white);
}

/* ---------------------------------------------------------------------- */
/* Scroll-reveal system                                                   */
/*                                                                        */
/* Content is visible by default. The hidden pre-animation offset below   */
/* only ever applies once html carries the "js" class — added by a       */
/* synchronous inline script in _layout.cshtml's <head> the instant      */
/* script execution is confirmed, and stripped again if animations.js    */
/* fails to finish setting up (see animations.js) — so a blocked/failed/ */
/* disabled script can never leave real content invisible. The :where()  */
/* wrapper keeps the hidden rule's specificity at exactly one attribute  */
/* selector, so .is-visible below (two selectors) always wins the        */
/* cascade and reveals content even if "js" is still present. Reduced-   */
/* motion additionally removes the hidden state outright, independent of */
/* JS timing, so those users never see a pre-reveal flash at all.        */
/* ---------------------------------------------------------------------- */
@media not (prefers-reduced-motion: reduce) {
  /* Default variant (no value, or [data-reveal="up"]): fade + rise. Distance is the responsive
     --reveal-distance token, so mobile automatically gets a shorter, cheaper travel than desktop
     without a separate breakpoint rule anywhere that uses it. */
  :where(html.js) [data-reveal] {
    opacity: 0;
    transform: translateY(var(--reveal-distance));
    transition: opacity var(--transition-slow), transform var(--transition-slow);
    will-change: opacity, transform;
  }
  :where(html.js) [data-reveal=fade] {
    transform: none;
  }
  :where(html.js) [data-reveal=zoom] {
    transform: scale(0.94);
  }
  /* Sideways variants — for content that reads more naturally sliding in from one edge (e.g. an
     image/content split row) than rising from below. "start"/"end" follow logical inline
     direction so they're automatically correct in RTL without a [dir] override; "left"/"right"
     are the rare case a layout wants a fixed physical direction regardless of language. */
  :where(html.js) [data-reveal=start] {
    transform: translateX(calc(var(--reveal-distance) * -1));
  }
  :where(html.js) [data-reveal=end] {
    transform: translateX(var(--reveal-distance));
  }
  [dir=rtl] :where(html.js) [data-reveal=start] {
    transform: translateX(var(--reveal-distance));
  }
  [dir=rtl] :where(html.js) [data-reveal=end] {
    transform: translateX(calc(var(--reveal-distance) * -1));
  }
  :where(html.js) [data-reveal=left] {
    transform: translateX(calc(var(--reveal-distance) * -1));
  }
  :where(html.js) [data-reveal=right] {
    transform: translateX(var(--reveal-distance));
  }
  /* Soft-focus reveal — no movement, just resolves from a gentle blur into full clarity. Suits
     supporting text/secondary content that shouldn't compete with a primary rise/mask reveal
     happening in the same section. */
  :where(html.js) [data-reveal=blur] {
    opacity: 0;
    transform: none;
    filter: blur(var(--blur-amount));
    transition: opacity var(--transition-slow), filter var(--transition-slow);
  }
  /* Mask/clip reveal — for imagery: the image itself is always fully painted and positioned;
     only a clip-path window grows to uncover it, which reads as a much more "cinematic" reveal
     than a fade for hero-weight imagery, and — unlike animating width/height — clip-path is
     compositor-friendly and never triggers layout. */
  :where(html.js) [data-reveal=mask] {
    opacity: 1;
    transform: none;
    clip-path: inset(0 0 100% 0);
    transition: clip-path var(--transition-slow) var(--ease-luxury);
  }
  :where(html.js) [data-reveal-group] > * {
    opacity: 0;
    transform: translateY(var(--reveal-distance-sm));
    transition: opacity var(--transition-base) var(--ease-luxury), transform var(--transition-base) var(--ease-luxury);
  }
  /* Shop by Gender's two cards use [data-reveal="right"] purely as a shared CSS start-state hook
     (see above) — their actual entrance is driven entirely by shop-by-gender-reveal.js's own
     scrub-progress engine writing per-frame inline styles, never this transition-based system.
     Neutralizing the plain [data-reveal="right"] rule specifically inside
     [data-shop-by-gender-reveal] stops the two engines from fighting over the same
     opacity/transform properties on the same elements. */
}
/* Product grids/carousels (Shop/Men/Women/Best Sellers/category/collection listings, search
   results, homepage product carousels — anywhere ProductGrid/ProductCarousel render) are
   deliberately NOT part of [data-reveal-group] above. Two real bugs surfaced from trying to
   speed up a per-card stagger there instead of removing it:
   1. These grids/carousels almost always sit inside an outer `[data-reveal]` page/section
      wrapper (shopPage.cshtml's "container section--tight" div, productCategory.cshtml/
      collection.cshtml's equivalent, homePage.cshtml's Featured Products/Best Sellers
      <section>, product.cshtml's related-products <section>) — that ancestor's own opacity:0
      state, on the slow scroll-triggered var(--transition-slow) (560ms) timing, visually masks
      ANY speed applied to the grid inside it regardless of how fast the inner rule is. Fixed by
      removing `data-reveal` from those specific wrapper elements (see the .cshtml files) rather
      than by animating anything faster.
   2. .product-card has its own hover-transition rule (components.css — box-shadow/transform/
      border-color) at the same specificity as a bare `[data-reveal-group] > *` override, loaded
      later, which was silently winning the transition-property cascade and dropping opacity from
      the transitioned list entirely (an instant snap, not a fade) for any card-level attempt.
   The fix here sidesteps both: ONE reveal on the grid/carousel CONTAINER itself (not per card),
   gated behind a dedicated .product-reveal-ready class (see product-reveal.js) rather than the
   shared [data-reveal-group] system, so it can never re-collide with either issue above. */
@media not (prefers-reduced-motion: reduce) {
  html.js.product-reveal-ready [data-product-reveal] {
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 200ms cubic-bezier(0.22, 1, 0.36, 1), transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
    will-change: opacity, transform;
  }
  html.js.product-reveal-ready [data-product-reveal].is-visible {
    opacity: 1;
    transform: none;
  }
}
/* Hero/page-heading entrance — synced to the page-loader's exit (see hero-reveal.js and
   page-transition.js's page-loader-exit-start event), not the scroll-triggered [data-reveal]
   system: a hero or page-title band sits above the fold on every page that has one, so it should
   settle into place as the curtain lifts, never wait on a scroll that may never happen.
   .hero-line is the existing mask-wrapper already used for every page's <h1> (the homepage's
   cinematic Hero.cshtml AND the compact page-title band on Shop/Men/Women/category/collection
   pages all share it) — one mechanism here reaches every one of them. [data-hero-fade] marks the
   supporting elements that follow the heading (subtitle, then CTAs), each with its own small
   step-delay below. Progressive enhancement: visible by default, gated entirely behind
   html.js.hero-reveal-ready, which hero-reveal.js adds only once it has confirmed at least one
   of these elements actually exists on the page. */
@media not (prefers-reduced-motion: reduce) {
  html.js.hero-reveal-ready .hero-line span {
    transform: translateY(100%);
    transition: transform 450ms var(--ease-luxury);
  }
  html.js.hero-reveal-ready .hero-line.is-visible span {
    transform: none;
  }
  html.js.hero-reveal-ready [data-hero-fade] {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 360ms var(--ease-premium), transform 360ms var(--ease-premium);
  }
  html.js.hero-reveal-ready [data-hero-fade].is-visible {
    opacity: 1;
    transform: none;
  }
  html.js.hero-reveal-ready [data-hero-fade="1"] {
    transition-delay: 60ms;
  }
  html.js.hero-reveal-ready [data-hero-fade="2"] {
    transition-delay: 120ms;
  }
}
[data-reveal].is-visible {
  opacity: 1;
  transform: none;
  filter: none;
  clip-path: inset(0 0 0% 0);
}

[data-reveal-group].is-visible > * {
  opacity: 1;
  transform: none;
}

[data-reveal-group].is-visible > *:nth-child(1) {
  transition-delay: 0ms;
}

[data-reveal-group].is-visible > *:nth-child(2) {
  transition-delay: 50ms;
}

[data-reveal-group].is-visible > *:nth-child(3) {
  transition-delay: 100ms;
}

[data-reveal-group].is-visible > *:nth-child(4) {
  transition-delay: 150ms;
}

[data-reveal-group].is-visible > *:nth-child(5) {
  transition-delay: 200ms;
}

[data-reveal-group].is-visible > *:nth-child(6) {
  transition-delay: 250ms;
}

[data-reveal-group].is-visible > *:nth-child(7) {
  transition-delay: 300ms;
}

[data-reveal-group].is-visible > *:nth-child(8) {
  transition-delay: 350ms;
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal],
  [data-reveal-group] > * {
    opacity: 1;
    transform: none;
    filter: none;
    clip-path: none;
    transition: none;
  }
}
/* .page-shell/.page-main (formerly layout.css) — global page skeleton, not a reusable utility
   class in the .container/.grid/.stack sense, so it lives here rather than in
   _useful_classes.scss. */
.page-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  /* .site-header is position:fixed (removed from flow) so its height must be
     reserved here instead, or it would sit on top of the page's own content. */
  padding-block-start: var(--header-height);
}

.page-main {
  flex: 1 1 auto;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  font-size: var(--text-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--color-accent);
  font-weight: 600;
  margin-block-end: var(--space-3);
}

.eyebrow::before {
  content: "";
  display: inline-block;
  width: 1.5em;
  height: 1px;
  background: currentColor;
  opacity: 0.7;
}

[dir=rtl] .eyebrow {
  letter-spacing: 0.03em;
  text-transform: none;
}

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

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  inset-block-start: -3rem;
  inset-inline-start: var(--space-4);
  background: var(--color-charcoal);
  color: var(--color-white);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  z-index: 200;
  transition: top var(--transition-fast);
}

.skip-link:focus {
  inset-block-start: var(--space-2);
}

/* ---------------------------------------------------------------------- */
/* Heading underline expansion — a small reusable accent for section      */
/* titles/eyebrows. Purely decorative: draws in via scaleX once its       */
/* [data-reveal] ancestor (or itself, if given the attribute directly)    */
/* becomes .is-visible. Safe with no JS (starts fully drawn) and under    */
/* reduced motion (transition removed via the --transition-* collapse).  */
/* ---------------------------------------------------------------------- */
.heading-underline {
  display: block;
  width: 2.75rem;
  height: 2px;
  background: currentColor;
  transform-origin: left center;
  transition: transform var(--transition-slow) var(--ease-luxury);
}

[dir=rtl] .heading-underline {
  transform-origin: right center;
}

@media not (prefers-reduced-motion: reduce) {
  :where(html.js) [data-reveal] .heading-underline {
    transform: scaleX(0);
  }
  :where(html.js) [data-reveal].is-visible .heading-underline {
    transform: scaleX(1);
  }
}
/* Thin decorative divider for separating quiet, light sections */
.hairline {
  border: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--color-border-strong) 50%, transparent);
}

/* ---------------------------------------------------------------------- */
/* Layout utilities (formerly layout.css)                                 */
/* ---------------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-padding);
}

.section {
  padding-block: var(--storefront-section-space);
}

.section--tight {
  padding-block: var(--storefront-section-space-tight);
}

/* Alternate light background used to separate the rhythm of white
   sections without ever going dark. */
.section--alt {
  background: var(--color-bg-alt);
}

.section-heading {
  max-width: 40rem;
  margin-block-end: var(--storefront-heading-gap);
}

/* Section titles are a stronger visual anchor than a generic h2 elsewhere on the page (e.g. an
   editorial banner's own heading) — sized up from the shared h2 default for clearer hierarchy.
   Fluid, not a fixed --text-3xl: at any width from 768px up this already resolves to the exact
   same 2.75rem/44px as before (the desktop appearance is unchanged), it only scales down below
   that, reaching ~2rem/32px at 320px — every section heading sitewide (Top Releases, Best
   Sellers, Shop by Brand, Customer Reviews, ...) was rendering at the full 44px on every phone
   width with no scaling at all, which is the single largest contributor to "everything reads
   oversized" on mobile. */
.section-heading h2 {
  font-size: clamp(1.75rem, 1.4rem + 3vw, 2.75rem);
}

.section-heading--center {
  margin-inline: auto;
  text-align: center;
}

.grid {
  display: grid;
  gap: var(--storefront-grid-gap);
}

.grid--2 {
  grid-template-columns: repeat(1, 1fr);
}

.grid--3 {
  grid-template-columns: repeat(1, 1fr);
}

.grid--4 {
  grid-template-columns: repeat(2, 1fr);
}

@media (min-width: 640px) {
  .grid--4 {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 768px) {
  .grid--2 {
    grid-template-columns: repeat(2, 1fr);
  }
  .grid--3 {
    grid-template-columns: repeat(3, 1fr);
  }
  .grid--4 {
    grid-template-columns: repeat(4, 1fr);
  }
}
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.cluster {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.two-col {
  display: grid;
  gap: var(--storefront-content-gap);
}

@media (min-width: 900px) {
  .two-col {
    grid-template-columns: 1.1fr 0.9fr;
    align-items: start;
  }
  /* Gives the product visual more room on PDP / gift-set pages. */
  .two-col--wide-media {
    grid-template-columns: 1.25fr 0.75fr;
  }
}
/* =========================
   Components
========================= */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.9rem 1.9rem;
  border-radius: 999px;
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid transparent;
  white-space: nowrap;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
  /* Positioning context for the diagonal gold hover/press sweep (::before, below) — isolation
   gives this button its own stacking context so the pseudo-element's negative z-index only
   needs to beat *this* button's own background, never anything outside it; overflow:hidden
   clips the sweep to the button's own rounded silhouette (border-radius, unchanged, still
   applies to this same box) instead of letting its corners poke out past it. Neither property
   changes the button's own size or layout. */
  position: relative;
  isolation: isolate;
  overflow: hidden;
}
@media (max-width: 497px) {
  .btn {
    padding: 0.5rem 1.5rem;
    font-size: 14px;
  }
}

/* Diagonal half-swipe hover/press overlay — gold color matches the one real gold already in the
palette (var(--color-scroll-progress-gold), today only used for the scroll-progress bar/
Back-to-Top ring). Rests fully off the start edge; on hover (desktop, a real mouse) or press
(any device, including touch/mobile — see the confirmed follow-up to also cover mobile) it
slides in to cover roughly the start half of the button behind a diagonal edge, leaving the
rest of the button in its own existing color untouched. Disabled buttons never trigger it. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--color-scroll-progress-gold);
  /* A parallelogram spanning roughly the start 35-55% of the button width, angled by the same
  20%-width slant top and bottom — "roughly half", with a diagonal (not vertical) dividing
  edge. clip-path is relative to this pseudo-element's own (inset:0, full-button-size) box, so
  the same shape holds regardless of the button's own width/padding/size. */
  clip-path: polygon(0 0, 55% 0, 35% 100%, 0 100%);
  transform: translateX(-110%);
  transition: transform var(--transition-base);
  pointer-events: none;
}

[dir=rtl] .btn::before {
  /* Mirrors the shape itself (not just the translate direction) so the diagonal still leans the
  same visual way relative to reading direction, and rests off the (now-start=visual-right)
  edge correctly. */
  clip-path: polygon(100% 0, 45% 0, 65% 100%, 100% 100%);
  transform: translateX(110%);
}

.btn:not(:disabled):not([aria-disabled=true]):hover::before,
.btn:not(:disabled):not([aria-disabled=true]):active::before {
  transform: translateX(0);
}

[dir=rtl] .btn {
  letter-spacing: 0;
  text-transform: none;
}

.btn svg {
  width: 1rem;
  height: 1rem;
}

.btn:active {
  transform: scale(0.97);
  transition-duration: 90ms;
  transition-timing-function: var(--ease-sharp);
}

.btn:focus-visible {
  outline: none;
  outline-offset: 2px;
}

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

.btn--primary:hover {
  background: var(--color-black-soft);
  color: var(--color-white);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

/* The reference's own secondary-button pattern: white background, black text/border — kept the
existing class name (avoids touching every view/partial that already uses it) but this is no
longer a gold accent button, just the site's second (white) button variant. */
.btn--gold {
  background: var(--color-white);
  color: var(--color-primary);
  border-color: var(--color-primary);
}

.btn--gold:hover {
  background: var(--color-surface-alt);
  color: var(--color-primary);
  box-shadow: var(--shadow-gold);
  transform: translateY(-2px);
}

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

.btn--outline:hover {
  background: var(--color-espresso);
  color: var(--color-ivory);
  transform: translateY(-2px);
}

.btn--ghost {
  background: transparent;
  color: var(--color-text);
  border-color: var(--color-border);
}

.btn--ghost:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.btn--sm {
  padding: 0.6rem 1.2rem;
  font-size: var(--text-xs);
}

.btn--block {
  width: 100%;
}

.btn--whatsapp {
  /* The one deliberate exception to the monochrome palette — the real, official WhatsApp brand
  green, not an approximated "luxury green". */
  background: var(--color-whatsapp);
  color: var(--color-white);
}

.btn--whatsapp:hover {
  background: #1ebe5a;
  transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  .btn,
  .btn:active,
  .btn--primary:hover,
  .btn--gold:hover,
  .btn--outline:hover,
  .btn--whatsapp:hover {
    transition: none;
    transform: none;
  }
}
/* Relocated here from its original position (mid-file, physically near the product-card section)
— it's a button state rule, so it belongs with every other .btn rule. Moving it earlier in the
compiled output changes nothing visually: no other selector between its old and new position
also targets .btn:disabled. */
.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  color: var(--color-heading);
  position: relative;
  transition: background-color var(--transition-fast), transform var(--transition-fast), color var(--transition-fast);
}

.icon-btn:hover {
  background: var(--color-bg-alt);
  color: var(--color-accent);
  transform: translateY(-1px);
}

.icon-btn:active {
  transform: scale(0.9);
  transition-duration: 90ms;
  transition-timing-function: var(--ease-sharp);
}

.icon-btn:focus-visible {
  outline: none;
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .icon-btn,
  .icon-btn:hover,
  .icon-btn:active {
    transition: none;
    transform: none;
  }
}
.icon-btn svg {
  width: 1.25rem;
  height: 1.25rem;
}

.icon-btn.is-active,
.icon-btn.is-authenticated,
.product-card__wishlist.is-active {
  color: var(--color-accent);
}

.icon-btn.is-active svg,
.product-card__wishlist.is-active svg {
  fill: currentColor;
}

/* Plays once whenever .is-active is freshly added — the browser restarts a CSS animation
whenever the rule it's declared under starts newly matching, so no JS class-toggle beyond the
existing is-active add/remove (already how wishlist state is synced) is needed for this. */
@keyframes heart-pulse {
  0% {
    transform: scale(1);
  }
  40% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}
@media not (prefers-reduced-motion: reduce) {
  .icon-btn.is-active svg,
  .product-card__wishlist.is-active svg {
    animation: heart-pulse 420ms var(--ease-luxury);
  }
}
.btn.is-added {
  background: var(--color-accent);
  color: var(--color-white);
  border-color: transparent;
}

.icon-btn__badge {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
  min-width: 1.1rem;
  height: 1.1rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  background: var(--color-accent);
  color: var(--color-white);
  font-size: 0.65rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Cart icon "bump" pulse when an item is added (was in the mobile-cart-bar section of the
original file — it's an .icon-btn state, so it belongs here). */
@keyframes icon-bump {
  0%, 100% {
    transform: scale(1);
  }
  35% {
    transform: scale(1.22);
  }
  60% {
    transform: scale(0.95);
  }
}
.icon-btn.is-bumped {
  animation: icon-bump 420ms var(--ease-luxury);
}

@media (prefers-reduced-motion: reduce) {
  .icon-btn.is-bumped {
    animation: none;
  }
}
.form-grid {
  display: grid;
  gap: var(--space-4);
}

@media (min-width: 640px) {
  .form-grid--2 {
    grid-template-columns: repeat(2, 1fr);
  }
}
.form-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.form-field label {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-heading);
}

.form-field input,
.form-field select,
.form-field textarea,
.coupon-field__row input {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 0.7rem 1rem;
  font: inherit;
  font-size: var(--text-sm);
  background: var(--color-surface);
  color: var(--color-text);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.form-field select:invalid {
  color: var(--color-text-muted);
}

@media (max-width: 767px) {
  /* Real form fields must render at >=16px on mobile or iOS Safari auto-zooms the whole page on
     focus — covers every checkout/account/coupon text input, select, and textarea (14px
     otherwise); the field's padding/border/layout are unchanged, and desktop keeps the existing
     14px. */
  .form-field input,
  .form-field select,
  .form-field textarea,
  .coupon-field__row input {
    font-size: var(--text-base);
  }
}
.form-field textarea {
  resize: vertical;
  min-height: 5rem;
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus,
.coupon-field__row input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px rgba(18, 18, 18, 0.12);
}

/* Required-field marker (Checkout contact/address fields) and per-field validation message —
   shown/hidden by checkout-form.js, never by server-rendered state. */
.field-required {
  color: var(--color-danger, #a3352a);
  font-weight: 600;
}

.field-error {
  font-size: var(--text-xs);
  color: var(--color-danger, #a3352a);
}

.form-field input.is-invalid,
.form-field select.is-invalid,
.form-field textarea.is-invalid {
  border-color: var(--color-danger, #a3352a);
}

/* Fixed, non-editable "+962" prefix + the customer's own 7XXXXXXXX local digits — same pattern
   used by the Gift Checkout recipient phone field. Always LTR (a phone number's digit order
   never flips for RTL layout) regardless of the page's own direction. */
.phone-input {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  overflow: hidden;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.phone-input__prefix {
  display: flex;
  align-items: center;
  padding: 0.7rem 0.75rem;
  background: var(--color-bg-alt);
  color: var(--color-text-muted);
  font-weight: 600;
  font-size: var(--text-sm);
  border-inline-end: 1px solid var(--color-border);
  white-space: nowrap;
}

.phone-input input {
  flex: 1;
  min-width: 0;
  border: none;
  border-radius: 0;
  padding: 0.7rem 1rem;
  font: inherit;
  font-size: var(--text-sm);
  background: transparent;
  color: var(--color-text);
}

.phone-input input:focus {
  outline: none;
}

.phone-input:focus-within {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px rgba(18, 18, 18, 0.12);
}

.phone-input.is-invalid {
  border-color: var(--color-danger, #a3352a);
}

@media (max-width: 767px) {
  /* Same >=16px mobile-zoom-prevention rule as .form-field above. */
  .phone-input__prefix,
  .phone-input input {
    font-size: var(--text-base);
  }
}
.form-message {
  font-size: var(--text-sm);
  color: var(--color-accent);
}

.form-message.is-error {
  color: var(--color-danger);
}

.form-notice {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-bg-alt);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* The only .form-notice with an action button inside it (see the "verify your email" banner in
   accountPage.cshtml) — wraps to a stacked layout on narrow phones instead of squeezing the
   button next to the message text. */
[data-email-verification-banner] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-block-end: var(--space-4);
}

[data-email-verification-banner] span {
  flex: 1 1 auto;
  min-width: 12rem;
}

[data-email-verification-banner] button {
  flex: 0 0 auto;
}

.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.22rem 0.5rem;
  border-radius: 999px;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--color-white);
  color: var(--color-charcoal);
  border: 1px solid var(--color-border);
}

[dir=rtl] .badge {
  letter-spacing: 0;
  text-transform: none;
}

@media (max-width: 767px) {
  /* On the 2-column mobile grid (~139-166px cards), the longest badge labels ("Best Seller",
     "Out of Stock") sit close to the width `.product-card__media`'s `overflow: hidden` leaves for
     them — shrinking just the product-card badge text keeps every label fully visible instead of
     risking a clipped edge, without touching `.badge` anywhere else on the site. */
  .product-card__badges .badge {
    font-size: 0.58rem;
    padding: 0.2rem 0.42rem;
  }
  .product-card__badges .badge--out-of-stock {
    padding: 0.32rem 0.55rem;
  }
}
.badge--gold {
  background: var(--color-gold);
  color: var(--color-white);
  border-color: transparent;
}

.badge--sale {
  background: var(--color-accent-deep);
  color: var(--color-white);
  border-color: transparent;
  box-shadow: 0 2px 8px rgba(18, 18, 18, 0.12);
}

.badge--muted {
  background: var(--color-bg-warm);
  color: var(--color-text-muted);
  border-color: transparent;
}

.badge--out-of-stock {
  max-inline-size: none;
  padding: 0.38rem 0.65rem;
  border-color: transparent;
  border-radius: 999px;
  background: #624742;
  color: var(--color-white);
  line-height: 1.15;
  white-space: nowrap;
  text-align: center;
  box-shadow: 0 2px 8px rgba(18, 18, 18, 0.12);
}

/* Checkout mode badge — shown only while a gift-checkout draft is active (see checkout-form.js). */
.badge--gift {
  background: var(--color-accent);
  color: var(--color-white);
  border-color: transparent;
  margin-inline-start: var(--space-3);
  vertical-align: middle;
}

.product-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  align-self: stretch;
  width: 100%;
  border-radius: var(--radius-xl);
  overflow: hidden;
  border: 1px solid rgba(18, 18, 18, 0.14);
  box-shadow: 0 1px 0 rgba(18, 18, 18, 0.03);
  transition: box-shadow var(--transition-slow), transform var(--transition-slow), border-color var(--transition-base);
}

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

/* Explicit compact heights keep every image stage stable without aspect-ratio. Bottle packshots
   use `contain`, and both hover images share this same positioned stage, so no crop or layout
   shift is introduced when the image changes. */
.product-card__media {
  position: relative;
  height: 11rem;
  flex: 0 0 auto;
  background: radial-gradient(120% 100% at 50% 15%, var(--color-champagne) 0%, var(--color-ivory) 75%);
  overflow: hidden;
  display: block;
}

@media (min-width: 640px) {
  .product-card__media {
    height: 14rem;
  }
}
@media (min-width: 1100px) {
  .product-card__media {
    height: 17rem;
  }
}
.product-card__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.product-card__image--primary {
  z-index: 1;
  opacity: 1;
}

.product-card__image--secondary {
  z-index: 2;
  opacity: 0;
  pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
  .product-card:hover .product-card__media--has-secondary .product-card__image--primary {
    opacity: 0;
    transform: scale(1.015);
  }
  .product-card:hover .product-card__image--secondary {
    opacity: 1;
    transform: scale(1.015);
  }
  /* The single-image case — no secondary hover-swap image to crossfade to. Gated on the
     `--has-secondary` modifier (the actual condition that matters) rather than `:only-child`,
     which silently never matched whenever a badge (Sale/New Arrival/Best Seller/etc.) rendered
     as a sibling `<div>` next to the image — i.e. most real cards, since badges are common. */
  .product-card:hover .product-card__media:not(.product-card__media--has-secondary) .product-card__image--primary {
    transform: scale(1.025);
  }
}
.product-card__badges {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-start: var(--space-2);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  z-index: 3;
}

.product-card__stock-note,
.cart-row__stock-note {
  font-size: var(--text-xs);
  color: var(--color-accent-deep);
}

.product-card__wishlist {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-end: var(--space-2);
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid var(--color-border);
  z-index: 3;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--transition-fast), transform var(--transition-fast);
}

.product-card:hover .product-card__wishlist,
.product-card:focus-within .product-card__wishlist {
  opacity: 1;
  transform: none;
}

/* A touch/stylus input has no hover state at all, so a device without a mouse/trackpad could
   never trigger the rule above — the wishlist button would render but stay permanently
   invisible and unreachable. Show it plainly by default whenever the primary input can't hover. */
@media (hover: none) {
  .product-card__wishlist {
    opacity: 1;
    transform: none;
  }
}
.product-card__body {
  flex: 1 1 auto;
  padding: var(--space-2) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--storefront-card-gap);
}

.product-card__name {
  font-family: var(--font-heading);
  font-size: clamp(1rem, 0.94rem + 0.28vw, 1.2rem);
  line-height: 1.25;
  color: var(--color-heading);
  min-block-size: 2.5em;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
}

[dir=rtl] .product-card__name {
  line-height: 1.45;
  min-block-size: 3.1em;
}

.product-card__desc {
  display: none;
}

.product-card__meta {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.product-card__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-block-size: 1.5em;
  margin-block-start: auto;
  font-size: clamp(0.875rem, 0.84rem + 0.18vw, 1rem);
  font-weight: 650;
  color: var(--color-heading);
}

.product-card__price .is-sale {
  /* Matches the reference's own pricing strategy exactly: the sale price is not a distinct
     (e.g. red) color — it's the same heading/foreground color as a regular price. Hierarchy
     comes only from the struck-through, muted .was-price beside it. */
  color: var(--color-heading);
  font-weight: 750;
  white-space: nowrap;
}

.product-card__price .was-price {
  font-weight: 400;
  font-size: clamp(0.72rem, 0.7rem + 0.1vw, 0.8rem);
  color: var(--color-text-muted);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: currentColor;
  white-space: nowrap;
}

.product-card__actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-2);
  padding: 0 var(--space-3) var(--space-2);
}

.product-card__actions .btn {
  min-height: 2.75rem;
}

@media (max-width: 767px) {
  /* "ADD TO CART"/"VIEW DETAILS"/"OUT OF STOCK" at the base .btn--sm size (12px, uppercase,
     0.04em letter-spacing) measure wider than the ~77px of label room left on a 2-column mobile
     grid card (~139px wide at 320px) — narrows that gap without shrinking the button's touch
     target (min-height stays 2.75rem/44px above) or making the label illegible. */
  .product-card__actions .btn {
    padding-inline: var(--space-2);
    font-size: 0.7rem;
  }
}
.media-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background: radial-gradient(120% 100% at 50% 15%, var(--color-champagne) 0%, var(--color-ivory) 75%);
}

.media-placeholder svg {
  width: 46%;
  height: 46%;
  max-width: 7.5rem;
  max-height: 7.5rem;
  filter: drop-shadow(0 6px 10px rgba(38, 35, 32, 0.06));
}

/* Product grid */
.product-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  align-items: stretch;
  gap: var(--storefront-grid-gap);
}

@media (min-width: 640px) {
  .product-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--storefront-grid-gap);
  }
}
@media (min-width: 1100px) {
  .product-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
/* Shared by the Product Details page (product.cshtml) and the Gift Set page (giftSet.cshtml) —
   both render the same swipeable/thumbnail packshot gallery and the same mobile sticky
   Add-to-Cart bar, so this lives here rather than under pages/. */
.product-gallery {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* The primary packshot on the PDP — a horizontally-scrollable, scroll-snap track (one slide per
   image, `contain` not `cover` so the full bottle is always in frame, never stretched/clipped).
   Native touch/trackpad scrolling on this element IS the mobile swipeable carousel — no JS
   required for that part; product-gallery.js only adds the desktop-oriented extras (thumbs,
   arrows, wheel support, keyboard nav) on top. Explicit heights step up with the layout's own
   breakpoints, same as before — never a CSS `aspect-ratio`. */
.product-gallery__main {
  position: relative;
  height: 21rem;
  border-radius: var(--radius-lg);
  background: var(--color-bg-alt);
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.product-gallery__main::-webkit-scrollbar {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .product-gallery__main {
    scroll-behavior: auto;
  }
}
@media (min-width: 640px) {
  .product-gallery__main {
    height: 27rem;
  }
}
@media (min-width: 1100px) {
  .product-gallery__main {
    height: 34rem;
  }
}
.product-gallery__slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  scroll-snap-align: start;
  /* Deliberately NOT scroll-snap-stop: always — each slide is exactly one container-width wide,
     so there is no "skip past on a fast fling" case for it to guard against, and some engines
     interpret it as also blocking any non-gesture (i.e. programmatic scrollTo) jump entirely,
     which broke the thumbnail/arrow/keyboard navigation below outright. */
  display: flex;
  align-items: center;
  justify-content: center;
}

.product-gallery__slide img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Prev/next arrows overlay the track itself (there's no room for a header row above it like the
   homepage carousels get) — hidden until there's more than one slide to move between (see the
   `images.Count > 1` guard in product.cshtml, which is the only place these render at all). */
.product-gallery__controls {
  position: absolute;
  inset-block: 0;
  inset-inline: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--space-3);
  pointer-events: none;
  z-index: 1;
}

.product-gallery__arrow {
  display: none !important;
}

.product-gallery__arrow:hover {
  border-color: var(--color-accent);
  background: var(--color-bg-alt);
}

.product-gallery__arrow:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  pointer-events: none;
}

.product-gallery__arrow svg {
  width: 1.1rem;
  height: 1.1rem;
}

/* Thumbnail rail — desktop/tablet only; mobile gets pagination dots instead (see
   .product-gallery__dots below) so a narrow screen isn't asked to horizontally scroll two
   separate strips at once. */
.product-gallery__thumbs {
  display: none;
  gap: var(--space-2);
  overflow-x: auto;
}

@media (min-width: 768px) {
  .product-gallery__thumbs {
    display: flex;
  }
}
.product-gallery__thumb {
  flex: 0 0 auto;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 2px solid transparent;
  transition: border-color var(--transition-fast), transform var(--transition-fast);
}

.product-gallery__thumb:hover {
  transform: translateY(-2px);
}

.product-gallery__thumb[aria-current=true] {
  border-color: var(--color-accent);
}

.product-gallery__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Pagination dots — mobile only, mirrors the thumb rail's aria-current sync but at a size that
   fits a narrow screen without competing with the swipeable image strip above it. */
.product-gallery__dots {
  display: flex;
  justify-content: center;
  gap: var(--space-2);
}

@media (min-width: 768px) {
  .product-gallery__dots {
    display: none;
  }
}
.product-gallery__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--color-border-strong);
  transition: background-color var(--transition-fast), transform var(--transition-fast);
}

.product-gallery__dot[aria-current=true] {
  background: var(--color-accent);
  transform: scale(1.3);
}

/* ====================================================================== */
/* Sticky mobile CTA                                                      */
/* ====================================================================== */
.sticky-cta {
  position: sticky;
  inset-block-end: 0;
  z-index: 90;
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-block-start: 1px solid var(--color-border);
  box-shadow: 0 -12px 30px rgba(38, 35, 32, 0.08);
}

@media (min-width: 960px) {
  .sticky-cta {
    display: none;
  }
}
/* Once the site-wide mobile cart bar is showing (cart has items), this page-level bar steps
   aside instead of stacking two fixed bottom bars on mobile PDP. */
.sticky-cta.is-yielding {
  display: none;
}

/* ====================================================================== */
/* Notes / fragrance profile — presented as storytelling, not a data table */
/* ====================================================================== */
.notes-list {
  display: grid;
  gap: var(--space-5);
  position: relative;
  padding-block-start: var(--space-3);
}

@media (min-width: 640px) {
  .notes-list {
    grid-template-columns: repeat(3, 1fr);
  }
}
.notes-list > div {
  position: relative;
  padding-inline-start: var(--space-4);
  border-inline-start: 2px solid var(--color-accent-soft);
}

.notes-list dt {
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-accent);
  font-weight: 700;
  margin-block-end: var(--space-1);
}

[dir=rtl] .notes-list dt {
  letter-spacing: 0;
  text-transform: none;
}

.notes-list dd {
  color: var(--color-text);
  font-size: var(--text-md);
  font-family: var(--font-heading);
}

/* Shared with orderDetailsPage.cshtml's key/value order summary rows, in addition to the PDP's
   own spec table — genuinely reusable, not product-page-exclusive. */
.spec-list {
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.spec-list__row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-2);
  border-block-end: 1px solid var(--color-border);
}

.spec-list__row dt {
  color: var(--color-text-muted);
}

.spec-list__row dd {
  font-weight: 600;
  color: var(--color-heading);
}

/* ====================================================================== */
/* Horizontal carousel — "Top Releases" style commercial showcase          */
/* Reused by ProductCarousel.cshtml, the Frequently Bought Together rail,  */
/* and the homepage's review carousel — genuinely shared, not page-bound.  */
/* ====================================================================== */
.carousel-shell {
  position: relative;
}

.carousel {
  display: flex;
  align-items: stretch;
  gap: var(--space-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-block-end: var(--space-2);
  -ms-overflow-style: none;
  scrollbar-width: none;
  /* animations.js drives scrollLeft directly every frame during autoplay/arrow/dot transitions —
     hinting the compositor avoids layout thrashing on longer product/brand rails. */
  will-change: scroll-position;
}

.carousel::-webkit-scrollbar {
  display: none;
}

/* Card width per breakpoint is a percentage of the track (minus the gaps it'll sit next to),
   never a fixed rem value — a fixed width leaves a fractional, partially-clipped card peeking
   at the edge whenever the container doesn't divide evenly into that width. Percentage sizing
   guarantees exactly N complete cards are visible; any further cards start fully off-screen
   (reachable via scroll/next), never half-visible. */
.carousel > * {
  flex: 0 0 auto;
  min-width: 0;
  /* Mobile: exactly 1 complete card — no partial peek of a second one. Reachable further cards
     scroll fully into view via swipe/the prev-next controls, never sitting half-visible. */
  width: 100%;
  scroll-snap-align: start;
}

@media (min-width: 640px) {
  .carousel > * {
    /* Tablet (640-899px): 2 complete cards. */
    width: calc((100% - var(--space-4)) / 2);
  }
}
@media (min-width: 900px) {
  .carousel > * {
    /* Small laptop / large tablet (900-1279px): 3 complete cards. */
    width: calc((100% - 2 * var(--space-4)) / 3);
  }
}
@media (min-width: 1280px) {
  .carousel > * {
    /* Laptop/desktop at the container's full 1280px max-width: 4 complete cards. */
    width: calc((100% - 3 * var(--space-4)) / 4);
  }
}
.carousel__controls {
  display: none;
  align-items: center;
  gap: var(--space-2);
  justify-content: flex-end;
  margin-block-end: var(--space-4);
}

@media (min-width: 768px) {
  .carousel__controls {
    display: flex;
  }
}
.carousel__control {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  color: var(--color-heading);
  transition: border-color var(--transition-fast), background-color var(--transition-fast), transform var(--transition-fast);
}

.carousel__control:hover {
  border-color: var(--color-accent);
  background: var(--color-bg-alt);
  transform: translateY(-1px);
}

.carousel__control:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  pointer-events: none;
}

.carousel__control svg {
  width: 1.1rem;
  height: 1.1rem;
}

[dir=rtl] .carousel__control--prev svg,
[dir=rtl] .carousel__control--next svg {
  transform: scaleX(-1);
}

@media (prefers-reduced-motion: reduce) {
  .carousel {
    scroll-behavior: auto;
  }
}
/**
 * Loading system: page-navigation progress bar, button spinners, skeleton shimmer, and
 * form/panel loading overlays. All driven by classes toggled from wwwroot/scripts/main/loading.js —
 * nothing here changes existing markup contracts (data-* hooks are untouched).
 */
/* ------------------------------------------------------------------------ */
/* Page progress bar — slim, fades out once the page has loaded, never      */
/* blocks (loading.js also enforces a hard timeout).                        */
/* ------------------------------------------------------------------------ */
.page-progress {
  position: fixed;
  inset-inline-start: 0;
  inset-block-start: 0;
  height: 3px;
  width: 0%;
  z-index: 2000;
  background: linear-gradient(90deg, var(--color-gold-soft), var(--color-gold-deep));
  transition: width 900ms var(--ease-luxury), opacity 300ms ease;
  opacity: 1;
  pointer-events: none;
}

.page-progress.is-active {
  width: 78%;
}

.page-progress.is-complete {
  width: 100%;
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .page-progress {
    transition: opacity 200ms ease;
  }
}
/* ------------------------------------------------------------------------ */
/* Button loading spinner — a real element (not a pseudo-element) so its    */
/* color always matches the button's actual current text color exactly.    */
/* ------------------------------------------------------------------------ */
@keyframes dkhon-spin {
  to {
    transform: rotate(360deg);
  }
}
.btn__spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-inline-end: 0.55em;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-inline-start-color: transparent;
  vertical-align: -0.15em;
  animation: dkhon-spin 700ms linear infinite;
}

.btn.is-loading {
  cursor: progress;
}

.icon-btn.is-loading {
  position: relative;
  cursor: progress;
}

.icon-btn.is-loading svg {
  opacity: 0;
}

.icon-btn.is-loading .btn__spinner {
  position: absolute;
  inset: 0;
  margin: auto;
}

@media (prefers-reduced-motion: reduce) {
  .btn__spinner {
    animation: none;
    opacity: 0.65;
  }
}
/* ------------------------------------------------------------------------ */
/* Skeleton shimmer — warm-neutral (not cold grey), used for the cart       */
/* drawer while it awaits a real async cart read (Firestore for signed-in   */
/* users).                                                                   */
/* ------------------------------------------------------------------------ */
@keyframes dkhon-shimmer {
  0% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0 50%;
  }
}
.skeleton-row {
  height: 5.25rem;
  border-radius: var(--radius-md);
  background: linear-gradient(90deg, var(--color-skeleton-base) 25%, var(--color-skeleton-shine) 37%, var(--color-skeleton-base) 63%);
  background-size: 400% 100%;
  animation: dkhon-shimmer 1.4s ease infinite;
}

.skeleton-row + .skeleton-row {
  margin-block-start: var(--space-3);
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-row {
    animation: none;
  }
}
/* ------------------------------------------------------------------------ */
/* Form loading overlay — a subtle dimming of an in-flight auth-card/form   */
/* so the whole panel visibly reads as busy, on top of the button spinner.  */
/* ------------------------------------------------------------------------ */
.auth-card.is-loading,
[data-checkout-form].is-loading {
  position: relative;
  pointer-events: none;
}

.auth-card.is-loading::after,
[data-checkout-form].is-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--color-surface) 55%, transparent);
  border-radius: inherit;
  transition: opacity var(--transition-fast);
}

/* ------------------------------------------------------------------------ */
/* Cart drawer loading state                                                 */
/* ------------------------------------------------------------------------ */
.cart-drawer__panel.is-loading [data-cart-drawer-footer],
.cart-drawer__panel.is-loading [data-cart-drawer-empty] {
  display: none;
}

/* Locked only while the curtain is actively covering the page — removed the instant the curtain
   starts its reveal (see page-transition.js), and in every failure path too, so a JS error can
   never leave the page permanently unscrollable. */
html.curtain-lock,
html.curtain-lock body {
  overflow: hidden;
}

/* ------------------------------------------------------------------------ */
/* Full-screen page curtain — visible by default (no JS needed to show it,  */
/* so there's no white-flash race on first load), driven entirely by        */
/* classes toggled from page-transition.js. Shows the Umbraco Page Loader   */
/* Image (or a plain covering background if none is configured — never a   */
/* hardcoded fallback image/text) and, on reveal, a clip-path mask closes   */
/* it away from the bottom up, matching the reference loader's own exit.    */
/* ------------------------------------------------------------------------ */
.page-curtain {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--color-espresso);
  pointer-events: none;
  clip-path: inset(0 0 0 0);
  transition: clip-path 1100ms var(--ease-luxury);
}

.page-curtain__content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  max-width: min(70vw, 22rem);
  max-height: 40vh;
}

.page-curtain__image {
  display: block;
  max-width: 100%;
  max-height: 40vh;
  width: auto;
  height: auto;
  object-fit: contain;
  animation: dkhon-curtain-image-in 1000ms var(--ease-luxury) 150ms both;
}

/* Rises into place on entrance — matches the reference loader's own "wordmark rises up" quality
   (1s duration, 150ms delay); 40px keeps the rise proportional to DKHON's own small clamp(...)
   loader image instead of the reference's much taller full-width wordmark. */
@keyframes dkhon-curtain-image-in {
  from {
    opacity: 0;
    transform: translateY(40px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
/* Thin gradient progress indicator beneath the loader image — same gold gradient tokens as the
   top-of-page scroll-progress bar, echoing the reference loader's own accent bar. Not tied to
   real asset-load progress (the reference's isn't either): a fixed-duration fill timed to land
   just under the loader's own hard fallback, so it visually "completes" as the page exits. */
.page-curtain__bar {
  position: relative;
  margin-block-start: var(--space-1);
  width: min(11rem, 60%);
  height: 4px;
  border-radius: 999px;
  overflow: hidden;
  background: color-mix(in srgb, var(--color-ivory) 18%, transparent);
}

.page-curtain__bar::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--color-scroll-progress-gold));
  transform: scaleX(0);
  transform-origin: left center;
  animation: dkhon-curtain-bar-fill 2400ms var(--ease-luxury) 150ms forwards;
}

[dir=rtl] .page-curtain__bar::after {
  transform-origin: right center;
}

@keyframes dkhon-curtain-bar-fill {
  to {
    transform: scaleX(1);
  }
}
/* Fully covering (in place, opaque) is the plain, unconditional default above — no attribute/
   class/JS is needed to show it, so a page that never runs any JS (or whose JS fails) simply
   never uncovers a stuck black screen. "revealed" — applied by page-transition.js once the page
   is actually ready (or its safety timeout) — slides the entire curtain upward off-screen,
   revealing the page from the bottom up; the page underneath was already fully rendered the
   whole time, so nothing needs to "catch up" as the curtain lifts. */
.page-curtain.is-revealed {
  clip-path: inset(0 0 100% 0);
  pointer-events: none;
}

/* Re-covering for a new in-site navigation must be instant, never an animated slide back down —
   page-transition.js applies this only for the single frame needed to force that, then removes
   it so the *next* reveal still animates normally. */
.page-curtain.is-recovering {
  transition: none;
}

/* Fully removed from the render tree once the exit transition has actually finished (see
   page-transition.js's transitionend/timeout handling) — keeps it out of layout/paint entirely
   for the rest of the page's life instead of leaving a zero-opacity full-viewport fixed element
   sitting in the composited layer tree. */
.page-curtain[hidden] {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .page-curtain {
    clip-path: inset(0 0 0 0);
    transition: opacity 200ms ease;
  }
  .page-curtain.is-revealed {
    clip-path: inset(0 0 0 0);
    opacity: 0;
  }
  .page-curtain__image {
    animation: none;
  }
  .page-curtain__bar::after {
    animation: none;
    transform: scaleX(1);
  }
}
.toast-stack {
  position: fixed;
  inset-block-end: calc(var(--space-5) + env(safe-area-inset-bottom, 0px));
  inset-inline-end: var(--space-5);
  z-index: 700;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  pointer-events: none;
  max-width: min(22rem, 100vw - var(--space-8));
}

.toast {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-radius: 999px;
  background: var(--color-espresso);
  color: var(--color-ivory);
  font-size: var(--text-sm);
  font-weight: 600;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.toast.is-visible {
  opacity: 1;
  transform: none;
}

.toast svg {
  width: 1.1rem;
  height: 1.1rem;
  color: var(--color-gold-soft);
  flex-shrink: 0;
}

.toast--error {
  background: var(--color-danger);
}

.toast--error svg {
  color: var(--color-ivory);
}

@media (max-width: 639px) {
  .toast-stack {
    inset-inline: var(--space-4);
    align-items: center;
  }
  .toast {
    width: 100%;
    justify-content: center;
  }
}
@media (prefers-reduced-motion: reduce) {
  .toast {
    transition: opacity var(--transition-fast);
    transform: none;
  }
}
/* ====================================================================== */
/* Top scroll-progress bar — fixed above the header, grows as the visitor */
/* scrolls the page. scroll-progress.js only ever writes one CSS custom   */
/* property (--scroll-progress, 0-1); every other detail (thin height,    */
/* scaleX transform instead of animating width, RTL-aware direction) is   */
/* plain CSS. position:fixed takes it out of flow entirely, so it can     */
/* never shift layout or grow the header's own reserved offset.           */
/*                                                                        */
/* Grouped with Back to Top below — both share the site's one deliberate  */
/* gold accent token as a matched pair of scroll-progress indicators.     */
/* ====================================================================== */
.scroll-progress {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  /* 3px mobile / 4px above (see the min-width block below) — position:fixed with no effect on
     any sibling's layout means this can never push page content down regardless of height. */
  height: 3px;
  z-index: 800;
  pointer-events: none;
  transform: scaleX(var(--scroll-progress, 0));
  transform-origin: left center;
  /* The centralized gold accent token (see tokens.css) — not a duplicated hex — shared with the
     Back to Top ring below as the site's one deliberate gold, so this line reads as refined gold
     against the white header/page rather than the monochrome black/navy the rest of the palette
     uses. A flat fill reads thin/hard at only 3-4px tall; this top-to-bottom sheen (a lighter
     *mix* of the same token, not a second hex) is the "soft highlight" — no blur, no glow.
     transform (scaleX), never width, keeps this off the layout/paint-cost path. */
  background: linear-gradient(to bottom, color-mix(in srgb, var(--color-scroll-progress-gold) 100%, white 30%), var(--color-scroll-progress-gold) 70%);
  /* Rounds only the inline-end corners — the bar is pinned to zero width at its inline-start edge
     (see transform-origin above / the RTL override below) and grows toward inline-end, so only
     the tip that's actually advancing needs a soft edge. Logical properties keep this correct
     under RTL automatically, with no separate override needed. */
  border-start-end-radius: 999px;
  border-end-end-radius: 999px;
  transition: transform 100ms linear;
}

@media (min-width: 768px) {
  .scroll-progress {
    height: 4px;
  }
}
[dir=rtl] .scroll-progress {
  transform-origin: right center;
}

@media (prefers-reduced-motion: reduce) {
  .scroll-progress {
    transition: none;
  }
}
/* ====================================================================== */
/* Back to top — fixed circular button with a gold scroll-progress ring. */
/* inset-inline-start (opposite side from .toast-stack's inset-inline-end)*/
/* keeps it clear of toast notifications; z-index 450 sits below the      */
/* header (500) and drawers/mobile-nav (600) so an open panel still       */
/* correctly covers it. back-to-top.js toggles [data-visible] and writes  */
/* --back-to-top-progress (0-1) as inline style; everything else is CSS.  */
/* ====================================================================== */
.back-to-top[hidden] {
  display: none;
}

.back-to-top {
  position: fixed;
  inset-block-end: var(--space-5);
  inset-inline-start: var(--space-5);
  z-index: 450;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 3.5rem (56px) at every breakpoint sits inside both the desktop (54-60px) and mobile
     (50-56px) target ranges at once — one size, no breakpoint override needed. */
  width: 3.5rem;
  height: 3.5rem;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-heading);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
  transition: opacity var(--transition-base), transform var(--transition-base), box-shadow var(--transition-fast);
  --back-to-top-progress: 0;
}

.back-to-top[data-visible=true] {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.back-to-top:hover,
.back-to-top:focus-visible {
  box-shadow: var(--shadow-lg);
  transform: translateY(-2px);
}

.back-to-top[data-visible=true]:hover,
.back-to-top[data-visible=true]:focus-visible {
  transform: translateY(-2px);
}

.back-to-top:focus-visible {
  outline: 2px solid var(--color-secondary);
  outline-offset: 2px;
}

.back-to-top__icon {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-lg);
  height: var(--icon-lg);
  transition: transform var(--transition-fast);
}

.back-to-top__icon svg {
  width: 100%;
  height: 100%;
}

.back-to-top:hover .back-to-top__icon {
  transform: translateY(-1px);
}

.back-to-top__ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.back-to-top__ring-track {
  fill: none;
  stroke: var(--color-border);
  stroke-width: 2;
}

.back-to-top__ring-progress {
  fill: none;
  stroke: var(--color-scroll-progress-gold);
  stroke-width: 2;
  stroke-linecap: round;
  /* circumference = 2 * PI * r(20) ≈ 125.66 — back-to-top.js only ever writes the
     custom property below; the dasharray/offset math lives entirely in CSS. */
  stroke-dasharray: 125.66;
  stroke-dashoffset: calc(125.66 * (1 - var(--back-to-top-progress)));
  transition: stroke-dashoffset 100ms linear;
}

@media (max-width: 959px) {
  .back-to-top {
    /* Clears .cart-bar (mobile-only, fixed to inset-block-end: 0) unconditionally, since JS
       cannot cheaply know at all times whether the bar is currently showing. */
    inset-block-end: calc(4.75rem + var(--space-3));
  }
}
@media (prefers-reduced-motion: reduce) {
  .back-to-top {
    transition: opacity var(--transition-fast);
  }
  .back-to-top:hover,
  .back-to-top:focus-visible,
  .back-to-top[data-visible=true]:hover,
  .back-to-top[data-visible=true]:focus-visible {
    transform: none;
  }
  .back-to-top__icon,
  .back-to-top:hover .back-to-top__icon {
    transition: none;
    transform: none;
  }
  .back-to-top__ring-progress {
    transition: none;
  }
}
/* ====================================================================== */
/* Cart line-item row + quantity stepper — shared by the cart page and the */
/* cart drawer (see partials/_cart-drawer.scss for the drawer shell).      */
/* ====================================================================== */
.cart-row {
  display: grid;
  grid-template-columns: 4.5rem 1fr auto;
  gap: var(--space-3);
  align-items: center;
  padding-block: var(--space-3);
  border-block-end: 1px solid var(--color-border);
  background-color: transparent;
  transition: background-color 1.2s ease;
}

/* Briefly pulses the row for the product that was just added, so it's obvious which item the
   drawer is showing — the class is removed by JS ~1.6s after the drawer opens. */
.cart-row--highlight {
  background-color: var(--color-bg-warm);
}

.cart-row__media {
  width: 4.5rem;
  height: 4.5rem;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-bg-alt);
}

.cart-row__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cart-row__name {
  font-family: var(--font-heading);
  color: var(--color-heading);
}

.cart-row__price {
  font-weight: 700;
  color: var(--color-heading);
  white-space: nowrap;
}

.cart-row__remove {
  align-self: flex-start;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-decoration: underline;
  text-align: start;
  transition: color var(--transition-fast);
}

.cart-row__remove:hover {
  color: var(--color-accent);
}

/* Compact, intrinsic-width control — deliberately NOT width:100%/flex:1. Its parent (.stack, a
   flex column) defaults to align-items:stretch, which used to stretch this into a long pill
   spanning the whole row; align-self:flex-start + width:fit-content opt it out of that stretch
   regardless of which layout it's embedded in (cart drawer, cart page, or a future reuse).
   Three equal-width cells (button/qty/button) read as one balanced [ + | qty | - ] unit — the
   qty cell's own border-inline draws the two divider lines, using logical (not physical) sides
   so the same markup mirrors correctly in Arabic RTL without swapping the +/- semantics. */
.qty-stepper {
  display: inline-flex;
  align-items: stretch;
  align-self: flex-start;
  width: fit-content;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-surface);
}

.qty-stepper button,
.qty-stepper span {
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.qty-stepper button {
  font-size: var(--text-md);
  line-height: 1;
  transition: background-color var(--transition-fast);
}

.qty-stepper button:hover {
  background: var(--color-bg-alt);
}

.qty-stepper button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  pointer-events: none;
}

.qty-stepper span {
  font-weight: 600;
  border-inline: 1px solid var(--color-border);
}

/* ====================================================================== */
/* Mobile sticky cart bar (site-wide)                                     */
/* ====================================================================== */
.cart-bar {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 95;
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  padding-block-end: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
  background: rgba(255, 255, 255, 0.97);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-block-start: 1px solid var(--color-border);
  box-shadow: 0 -12px 30px rgba(38, 35, 32, 0.1);
  transform: translateY(100%);
  transition: transform var(--transition-base);
}

.cart-bar[data-visible=true] {
  transform: none;
}

@media (max-width: 959px) {
  .cart-bar {
    display: flex;
  }
}
.cart-bar__info {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}

.cart-bar__count {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.cart-bar__total {
  font-weight: 700;
  color: var(--color-heading);
}

.cart-bar .btn {
  flex-shrink: 0;
}

/* At a real phone width, the original single row (info column + two full-text buttons, all
   fighting for space via justify-content: space-between) had no way to fit "Items: N" / a price
   / "Your Cart" / "Proceed to Checkout" without overlapping or clipping -- worst in Arabic, where
   the same labels typically run longer. Stacked layout below removes the squeeze structurally
   (info as its own compact row, the buttons as their own full-width row) instead of shrinking
   text until it happens to fit. Left untouched above 639px, where the single row already has
   enough room -- this component only ever renders under 960px anyway (see the display:flex rule
   above), so there's no desktop variant of it to preserve either way.
   Targets the markup's existing ".cluster" wrapper directly (CartDrawer.cshtml) rather than
   introducing a new class -- its own inline `flex-wrap: nowrap` only ever governed whether the
   two buttons could drop to a second line within that div; it doesn't conflict with any property
   set here (flex-direction, width, flex-basis), so there's no cascade fight to route around. */
@media (max-width: 639px) {
  .cart-bar {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
    padding-block: var(--space-2) calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
  }
  .cart-bar__info {
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
  }
  .cart-bar > .cluster {
    width: 100%;
  }
  .cart-bar > .cluster .btn {
    flex: 1 1 0;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}
/* Narrower still (roughly a 320-360px phone): even at equal flex:1 widths, "Proceed to
   Checkout"/its Arabic equivalent starts truncating with ellipsis rather than reading in full --
   stacking the two buttons is more legible here than a shortened label on the bar's single most
   important action. */
@media (max-width: 359px) {
  .cart-bar > .cluster {
    flex-direction: column;
    align-items: stretch;
  }
  .cart-bar > .cluster .btn {
    /* Resets the 639px breakpoint's flex: 1 1 0 -- with flex-direction now column, that flex-basis
       of 0 applies to the main axis (now height, not width), so both buttons were shrinking to
       share the row's own auto height evenly instead of each sizing to its own natural
       padding+text height, landing well under a usable tap target (~21px measured). flex: 0 0 auto
       here means each stacked button is exactly as tall as its own content wants. */
    flex: 0 0 auto;
    width: 100%;
  }
}
@media (prefers-reduced-motion: reduce) {
  .cart-bar {
    transition: none;
  }
}
/* Order-summary sidebar — verified via the real markup to be shared across cartPage.cshtml,
   checkoutPage.cshtml, and orderDetailsPage.cshtml, not exclusive to any one page. Grouped here
   as one reusable component rather than duplicated per page. */
.cart-summary {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: clamp(1rem, 3vw, 1.75rem);
  background: var(--color-bg-alt);
  height: fit-content;
}

.free-delivery-progress {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  padding-block: var(--space-2);
  overflow-wrap: break-word;
}

.free-delivery-progress:empty {
  display: none;
}

.coupon-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-block: var(--space-3);
  border-block: 1px solid var(--color-border);
}

.coupon-field label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.coupon-field__row {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.coupon-field__row input {
  flex: 1 1 8rem;
  min-width: 0;
}

.coupon-field__message {
  font-size: var(--text-xs);
  color: var(--color-accent);
  overflow-wrap: break-word;
}

.coupon-field__message.is-error {
  color: var(--color-danger);
}

.checkout-summary__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: var(--space-2);
  font-size: var(--text-sm);
  border-block-end: 1px solid var(--color-border);
}

.checkout-summary__name {
  flex: 1 1 auto;
  min-width: 0;
}

.checkout-summary__remove {
  flex: 0 0 auto;
  font-size: var(--text-md);
  line-height: 1;
  color: var(--color-text-muted);
  padding: var(--space-1);
  transition: color var(--transition-fast);
}

.checkout-summary__remove:hover {
  color: var(--color-danger, #a3352a);
}

.checkout-summary__total-row[hidden] {
  display: none;
}

.checkout-summary__total-row {
  display: flex;
  justify-content: space-between;
  padding-block: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.checkout-summary__total-row--grand {
  font-weight: 700;
  font-size: var(--text-md);
  color: var(--color-heading);
  border-block-start: 1px solid var(--color-border);
  margin-block-start: var(--space-2);
  padding-block-start: var(--space-3);
}

/* Checkout's own two-column responsive grid (also reused, unmodified, by cartPage.cshtml and
   orderDetailsPage.cshtml wherever they need the same order-summary layout shell). */
.checkout-grid {
  display: grid;
  gap: var(--storefront-content-gap);
  align-items: start;
}

.checkout-grid > *,
.checkout-grid .auth-card,
.checkout-grid .form-grid,
.checkout-grid .form-field {
  min-width: 0;
}

.checkout-grid .form-field input,
.checkout-grid .form-field select,
.checkout-grid .form-field textarea,
.checkout-grid .phone-input {
  width: 100%;
  min-width: 0;
  max-width: 100%;
}

/* Below the two-column breakpoint, the Order Summary must not sit buried below the entire
   payment form. `display: contents` on the form removes only its own box from rendering —
   the form element, its name-based submission, and its event listeners are all untouched —
   so its direct children (each already their own card) become ordinary siblings of
   .cart-summary inside the same single-column grid, letting `order` interleave them:
   heading (outside this grid) -> contact -> address -> gift (if shown) -> Order Summary ->
   payment method -> submit. No DOM duplication, so there is exactly one Order Summary and
   one set of form fields — just reordered visually. */
@media (max-width: 991px) {
  .checkout-grid form[data-checkout-form] {
    display: contents;
  }
  .checkout-grid [data-checkout-section=contact] {
    order: 1;
  }
  .checkout-grid [data-checkout-section=address] {
    order: 2;
  }
  .checkout-grid [data-gift-checkout-section] {
    order: 3;
  }
  .checkout-grid .cart-summary {
    order: 4;
  }
  .checkout-grid [data-payment-methods-card] {
    order: 5;
  }
  .checkout-grid [data-checkout-message] {
    order: 6;
  }
  .checkout-grid [data-checkout-submit] {
    order: 7;
  }
}
@media (min-width: 992px) {
  .checkout-grid {
    grid-template-columns: 1.3fr 1fr;
  }
}
/* Once an order is successfully created, [data-checkout-page] gets the "checkout-complete"
   class (see checkout-form.js). This hides the form/summary/empty-cart state declaratively —
   a single class flip that can't race with async cart-changed re-renders, unlike toggling
   `hidden` on several elements from multiple async code paths. */
[data-checkout-page].checkout-complete [data-checkout-grid],
[data-checkout-page].checkout-complete [data-checkout-empty] {
  display: none !important;
}

/* ====================================================================== */
/* Payment method selection — cart drawer / cart page / checkout.         */
/* ====================================================================== */
.payment-methods {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-block: var(--space-2);
}

.payment-method {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: 0.625rem var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--transition-fast);
}

.payment-method:has(input:checked) {
  border-color: var(--color-accent);
  background: var(--color-bg-alt);
}

.payment-method input {
  margin-block-start: 3px;
}

.payment-method__label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--text-sm);
}

.payment-method__note {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.payment-method[data-disabled=true] {
  opacity: 0.5;
  cursor: not-allowed;
}

.gift-cod-explanation {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg-alt);
  border-radius: var(--radius-md);
  margin-block-end: var(--space-2);
}

.cliq-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-alt);
}

/* Root-cause fix: an author-stylesheet class rule with equal specificity to the browser's
   own `[hidden] { display: none }` default wins the cascade tie (author beats user-agent),
   so `.cliq-panel { display: flex }` above was silently defeating the `hidden` attribute —
   the panel stayed visible regardless of the payment-method JS toggle. This restores it. */
.cliq-panel[hidden] {
  display: none;
}

.cliq-panel__row[hidden] {
  display: none;
}

.cliq-panel__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--text-sm);
}

.cliq-panel__row dt {
  color: var(--color-text-muted);
}

.cliq-panel__row dd {
  margin: 0;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.cliq-panel__amount {
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--color-accent);
}

.cliq-panel__copy-btn {
  font-size: var(--text-xs);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-2);
  background: var(--color-surface);
}

.cliq-panel__instructions {
  font-size: var(--text-sm);
  white-space: pre-line;
}

.cliq-panel__copy-feedback {
  font-size: var(--text-xs);
  color: var(--color-accent);
}

.cliq-panel__qr {
  align-self: center;
  max-width: 10rem;
}

.cliq-panel__qr img {
  width: 100%;
  height: auto;
}

.cliq-panel__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-sm);
}

.cliq-panel__field input {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  font: inherit;
}

@media (max-width: 767px) {
  /* Same >=16px mobile-zoom-prevention rule as .form-field — the CliQ sender-name field inherits
     font-size from its wrapping label (14px) via `font: inherit`, so the override has to target
     the input itself, not the label text. */
  .cliq-panel__field input {
    font-size: var(--text-base);
  }
  /* The CliQ block's own rows were badly cramped at 320-390px: .cliq-panel__row lays out
     dt/dd with justify-content: space-between on the desktop-sized row above, which forces a
     collision once dd itself also has to fit a value AND a copy button (amount/reference/
     confirmation phone) in the shrinking leftover width — the actual cause of the reported
     wrapping, not just "the fonts are big". Stacking label above value+button removes that
     squeeze entirely instead of just shrinking type to paper over it. */
  .cliq-panel {
    gap: var(--space-1);
    padding: var(--space-3);
  }
  .cliq-panel__row {
    flex-direction: column;
    align-items: stretch;
    gap: 0.125rem;
    padding-block: 0.3rem;
  }
  .cliq-panel__row:not(:last-child) {
    border-block-end: 1px solid var(--color-border);
  }
  .cliq-panel__row dt {
    font-size: var(--text-xs);
  }
  .cliq-panel__row dd {
    /* flex-wrap: wrap, not nowrap: a long value (the reference code, the confirmation phone)
       sitting next to a copy button as flex siblings otherwise shrinks the value's own span
       below its natural width, wrapping the value text itself mid-string -- exactly the "ugly
       word wrapping" this pass is meant to remove. Wrapping at the ROW level instead means the
       button drops to its own line below the value on the rare row where both truly can't fit,
       which reads as a clean two-line group, never a broken value. */
    flex-wrap: wrap;
    row-gap: 0.2rem;
    justify-content: space-between;
    font-size: var(--text-sm);
  }
  .cliq-panel__row dd > span {
    white-space: nowrap;
  }
  .cliq-panel__amount {
    /* Still the single largest, boldest, accent-colored value in the block — visually the most
       important line — just no longer the full desktop --text-lg (22px). */
    font-size: var(--text-md);
  }
  .cliq-panel__copy-btn {
    /* Slightly more vertical padding than the desktop 2px, not less — a comfortable tap target
       now that it's no longer fighting a cramped row for width, while the reduced horizontal
       padding keeps it from reading as an oversized pill next to a short value. */
    padding: 0.4rem 0.6rem;
    flex-shrink: 0;
  }
  .cliq-panel__instructions {
    font-size: var(--text-xs);
  }
}
.cliq-panel__error {
  font-size: var(--text-sm);
  color: var(--color-danger, #a3352a);
}

/* ====================================================================== */
/* Order history / detail row — rendered client-side by orders.js on both */
/* orderHistoryPage.cshtml and orderDetailsPage.cshtml, so it lives here   */
/* as a shared component rather than under either page's own file.        */
/* ====================================================================== */
.order-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin-block-end: var(--space-3);
  transition: border-color var(--transition-fast);
}

.order-row:hover {
  border-color: var(--color-accent);
}

@media (min-width: 640px) {
  .order-row {
    grid-template-columns: 1fr 1fr 1fr auto;
    align-items: center;
  }
}
.order-row__number {
  font-weight: 700;
  color: var(--color-heading);
  /* A technical code ("DKH-260721-A1B2C3"), not language content — stays LTR regardless of the
     surrounding page direction, matching every other technical value on the site (email/phone/
     SKU inputs). Set here rather than as an HTML dir attribute since this text is rendered by
     orders.js's own template string, not this stylesheet's markup. */
  direction: ltr;
  unicode-bidi: isolate;
}

.order-row__date,
.order-row__status {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.order-row__total {
  font-weight: 700;
  color: var(--color-heading);
}

/* ====================================================================== */
/* Filter / sort toolbar — modern pill controls, not default form fields  */
/* Shared across shopPage.cshtml, productCategory.cshtml, collection.cshtml */
/* and brandsPage.cshtml/productBrand.cshtml listing pages.                */
/* ====================================================================== */
.shop-taxonomy {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-end: var(--space-3);
}

.shop-taxonomy a {
  display: inline-flex;
  align-items: center;
  min-height: 2.25rem;
  padding-inline: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  color: var(--color-text-muted);
  background: var(--color-surface);
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  transition: border-color var(--transition-fast), color var(--transition-fast), background-color var(--transition-fast);
}
@media (max-width: 497px) {
  .shop-taxonomy a {
    font-size: 13px;
  }
}

.shop-taxonomy a:hover,
.shop-taxonomy a:focus-visible,
.shop-taxonomy a.is-active {
  border-color: var(--color-accent);
  color: var(--color-white);
  background: var(--color-accent);
}

.shop-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-block-end: var(--space-4);
  padding-block-end: var(--space-3);
  border-block-end: 1px solid var(--color-border);
}
@media (max-width: 497px) {
  .shop-toolbar {
    margin-block-end: var(--space-2);
    padding-block-end: var(--space-2);
  }
}

.shop-toolbar__group {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.pill-select {
  appearance: none;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  /* Right padding has room for the arrow with no clipping at any of the four option labels,
  including the longest ("Price: High to Low" / "السعر من الأعلى إلى الأقل"). */
  padding: 0.55rem 2.4rem 0.55rem 1.1rem;
  font-size: var(--text-sm);
  color: var(--color-text);
  max-width: 100%;
  /* Was a leftover hardcoded old-palette brown (#262320) predating the monochrome rebrand —
  repointed to the current --color-primary so the arrow matches every other icon on the page. */
  background: var(--color-surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23121212' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right 0.9rem center;
  background-size: 0.9rem;
  transition: border-color var(--transition-fast);
  cursor: pointer;
}
@media (max-width: 497px) {
  .pill-select {
    font-size: 12px;
  }
}

@media (max-width: 767px) {
  /* A real <select> at 14px triggers iOS Safari's auto-zoom on focus/open — the toolbar's
  flex-wrap layout absorbs the slightly wider text at 16px without a fixed-width clip. */
}
[dir=rtl] .pill-select {
  padding: 0.55rem 1.1rem 0.55rem 2.4rem;
  background-position: left 0.9rem center;
}

.pill-select:hover {
  border-color: var(--color-accent);
}

.pill-select:focus-visible {
  border-color: var(--color-accent);
  outline: 2px solid var(--color-secondary);
  outline-offset: 2px;
}

.search-field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 0.55rem 1.1rem;
  background: var(--color-surface);
  /*min-width: 14rem;*/
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.search-field:focus-within {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px rgba(169, 133, 82, 0.14);
}

.search-field svg {
  width: 1.1rem;
  height: 1.1rem;
  color: var(--color-text-muted);
}

.search-field input {
  border: 0;
  background: transparent;
  flex: 1;
  outline: none;
}
@media (max-width: 497px) {
  .search-field input {
    font-size: 12px;
  }
}

@media (max-width: 497px) {
  .shop-toolbar {
    flex-wrap: nowrap;
    gap: 8px;
  }
  .shop-toolbar__group {
    flex: 1;
    min-width: 0;
    flex-wrap: nowrap;
  }
  .search-field,
  .pill-select {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
  }
  .search-field input {
    min-width: 0;
    width: 100%;
    font-size: 12px;
  }
  .pill-select {
    font-size: 12px;
  }
}
/* ====================================================================== */
/* Rich text body (policy/about pages)                                    */
/* Shared by policyPage.cshtml and aboutPage.cshtml (both render Umbraco  */
/* rich-text content through this same .policy-body wrapper).             */
/* ====================================================================== */
.policy-article {
  max-width: 44rem;
  margin-inline: auto;
}

.policy-article__header {
  margin-block-end: var(--space-4);
}

.policy-article__updated {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin-block: var(--space-2) 0;
}

.policy-article__intro {
  color: var(--color-text-muted);
  margin-block-start: var(--space-3);
  line-height: var(--leading-relaxed);
}

.policy-article__callout {
  margin-block-start: var(--space-4);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-bg-alt);
  border-inline-start: 3px solid var(--color-accent);
  font-weight: 600;
}

[dir=rtl] .policy-article__callout {
  border-inline-start: none;
  border-inline-end: 3px solid var(--color-accent);
}

.policy-toc {
  margin-block-end: var(--space-6);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.policy-toc__heading {
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  margin-block-end: var(--space-2);
}

.policy-toc ol {
  list-style: decimal;
  padding-inline-start: 1.25rem;
  margin: 0;
  display: grid;
  gap: var(--space-1);
}

[dir=rtl] .policy-toc ol {
  padding-inline-start: 0;
  padding-inline-end: 1.25rem;
}

.policy-toc a {
  color: var(--color-text);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.policy-toc a:hover,
.policy-toc a:focus-visible {
  color: var(--color-accent);
  border-color: currentColor;
}

.policy-body {
  color: var(--color-text);
  line-height: var(--leading-relaxed);
  overflow-wrap: break-word;
}

.policy-body h2,
.policy-body h3 {
  margin-block: var(--space-5) var(--space-2);
}

.policy-body p {
  margin-block-end: var(--space-4);
}

.policy-body ul,
.policy-body ol {
  margin-block-end: var(--space-4);
  padding-inline-start: 1.25rem;
  list-style: disc;
}

[dir=rtl] .policy-body ul,
[dir=rtl] .policy-body ol {
  padding-inline-start: 0;
  padding-inline-end: 1.25rem;
}

/* ====================================================================== */
/* CTA banner                                                             */
/* NOTE: .cta-banner/.cta-banner__actions were not found referenced by     */
/* any current .cshtml/.js file during migration — flagged as currently   */
/* unused CSS in the migration report, kept (not deleted) per "do not     */
/* silently delete CSS".                                                  */
/* ====================================================================== */
.cta-banner {
  display: grid;
  gap: var(--space-5);
  padding: var(--space-8) var(--space-5);
  background: var(--color-bg-alt);
  border-radius: var(--radius-xl);
  text-align: center;
}

.cta-banner__actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* ====================================================================== */
/* Promotional offer banners — light cards, not dark tiles                */
/* NOTE: .promo-grid/.promo-banner were not found referenced by any        */
/* current .cshtml/.js file during migration — flagged as currently        */
/* unused CSS, kept (not deleted) per "do not silently delete CSS".        */
/* ====================================================================== */
.promo-grid {
  display: grid;
  gap: var(--space-5);
}

@media (min-width: 768px) {
  .promo-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
.promo-banner {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-6);
  border-radius: var(--radius-xl);
  border: 1px solid var(--color-border);
  background: var(--color-bg-alt);
  transition: transform var(--transition-base), box-shadow var(--transition-base);
}

.promo-banner:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
}

.promo-banner svg {
  width: 2rem;
  height: 2rem;
  color: var(--color-accent);
  margin-block-end: var(--space-2);
}

.promo-banner h3 {
  font-size: var(--text-xl);
}

.promo-banner p {
  color: var(--color-text-muted);
  max-width: 24rem;
}

.promo-banner .btn {
  margin-block-start: var(--space-2);
  align-self: flex-start;
}

/* ====================================================================== */
/* Audience / niche spotlight banner — quiet editorial block, light bg    */
/* Confirmed shared across aboutPage.cshtml, contactPage.cshtml, and      */
/* Home\FinalCtaSection.cshtml.                                           */
/* ====================================================================== */
.spotlight-banner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 20rem;
  border-radius: var(--radius-xl);
  padding: var(--space-8) var(--space-5);
  background: linear-gradient(155deg, var(--color-champagne) 0%, var(--color-sand) 100%);
  border: 1px solid var(--color-border-strong);
  text-align: center;
}

.spotlight-banner__content {
  position: relative;
  max-width: 30rem;
}

.spotlight-banner__content p {
  color: var(--color-text-muted);
  margin-block: var(--space-3) var(--space-5);
}

/* ====================================================================== */
/* Perfume guide teaser cards                                             */
/* NOTE: .guide-grid/.guide-card were not found referenced by any current  */
/* .cshtml/.js file during migration — flagged as currently unused CSS,   */
/* kept (not deleted) per "do not silently delete CSS".                   */
/* ====================================================================== */
.guide-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(1, 1fr);
}

@media (min-width: 640px) {
  .guide-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1100px) {
  .guide-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
.guide-card {
  position: relative;
  padding: var(--space-6) var(--space-5);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  transition: transform var(--transition-base), box-shadow var(--transition-base), border-color var(--transition-base);
}

.guide-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: var(--color-gold-soft);
}

.guide-card__badge {
  display: inline-flex;
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
  font-weight: 700;
  margin-block-end: var(--space-3);
}

[dir=rtl] .guide-card__badge {
  letter-spacing: 0;
  text-transform: none;
}

.guide-card h3 {
  font-size: var(--text-md);
  margin-block-end: var(--space-2);
}

.guide-card p {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ====================================================================== */
/* Trust section — shared base for TrustSection.cshtml (used on aboutPage) */
/* and Home\TrustSectionHome.cshtml (home-specific overrides layered in    */
/* pages/_homePage.scss's ".homepage .home-section--trust" block).         */
/* ====================================================================== */
.trust-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
}

@media (min-width: 768px) {
  .trust-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
.trust-item {
  text-align: center;
  padding: var(--space-6) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface-warm);
  transition: transform var(--transition-base), border-color var(--transition-base), box-shadow var(--transition-base);
}

.trust-item:hover {
  transform: translateY(-4px);
  border-color: var(--color-gold-soft);
  box-shadow: var(--shadow-gold);
}

.trust-item__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.25rem;
  height: 3.25rem;
  margin-inline: auto;
  margin-block-end: var(--space-4);
  border-radius: 50%;
  background: var(--color-bg-warm);
  color: var(--color-accent-deep);
}

.trust-item__icon svg {
  width: 1.5rem;
  height: 1.5rem;
}

.trust-item h3 {
  font-size: var(--text-base);
  margin-block-end: var(--space-1);
}

/* TrustSection.cshtml now renders one concise line per item with no separate h3 title, so this
   carries the full weight of the message — sized/colored as the primary text, not a muted
   sub-caption. */
.trust-item p {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--color-heading);
  line-height: var(--leading-normal);
}

/* ====================================================================== */
/* Review cards — ReviewCard.cshtml, wrapped by Home\ReviewsSection.cshtml */
/* on the homepage and by product.cshtml's own reviews block on the PDP.  */
/* ====================================================================== */
.review-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  height: 100%;
  padding: var(--space-6) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface-warm);
}

.review-card__header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.review-card__avatar {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--color-bg-warm);
  color: var(--color-accent-deep);
  font-weight: 700;
  font-size: var(--text-sm);
}

.review-card__identity {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.review-card__name {
  font-weight: 600;
  color: var(--color-heading);
}

.review-card__stars {
  color: var(--color-gold, #b8974f);
  font-size: var(--text-sm);
  letter-spacing: 0.05em;
}

.review-card__verified {
  align-self: flex-start;
}

.review-card__text {
  margin: 0;
  color: var(--color-text-muted, inherit);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
}

/* Reviews cap at 3-across on desktop (not the generic 4) — a review card's text needs more
   breathing room than a product tile at the same width. */
@media (min-width: 1280px) {
  .review-carousel > * {
    width: calc((100% - 2 * var(--space-4)) / 3);
  }
}
.reviews-summary {
  margin-block-end: var(--space-4);
  color: var(--color-text-muted, inherit);
  font-size: var(--text-md);
}

.reviews-empty {
  color: var(--color-text-muted, inherit);
}

.reviews-write {
  margin-block-end: var(--space-6);
}

.reviews-write__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: 32rem;
  margin-block-start: var(--space-3);
}

.reviews-write__stars {
  display: flex;
  gap: var(--space-1);
}

.reviews-write__star {
  border: none;
  background: none;
  padding: 0;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--color-border);
  cursor: pointer;
}

.reviews-write__star.is-filled {
  color: var(--color-gold, #b8974f);
}

.reviews-write__text {
  min-height: 6rem;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font: inherit;
  resize: vertical;
}

.reviews-write__actions {
  display: flex;
  gap: var(--space-3);
}

.reviews-write__feedback {
  margin: 0;
  color: var(--color-accent-deep);
}

.reviews-write__feedback.is-error {
  color: var(--color-danger, #a12626);
}

/* ====================================================================== */
/* Feature cards (category / collection / gift set)                       */
/* Confirmed shared between shopPage.cshtml and Home\ShopByGenderSection. */
/* ====================================================================== */
.feature-card {
  position: relative;
  display: block;
  border-radius: var(--radius-lg);
  overflow: hidden;
  height: 26rem;
  background: var(--color-bg-warm);
  border: 1px solid var(--color-border-strong);
  transition: box-shadow var(--transition-base), transform var(--transition-base);
}

/* Collections tiles (grid--2) — a shorter, landscape-leaning editorial tile, previously an
   inline 16/11 ratio in the views; now an explicit height so nothing is ratio-derived. */
.feature-card--wide {
  height: 15rem;
}

@media (min-width: 900px) {
  .feature-card {
    height: 30rem;
  }
  .feature-card--wide {
    height: 25rem;
  }
}
.feature-card:hover {
  box-shadow: var(--shadow-gold);
  transform: translateY(-4px);
}

.feature-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow);
}

.feature-card:hover img {
  transform: scale(1.06);
}

.feature-card__overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  padding: var(--space-5);
  /* Was a leftover hardcoded old-palette brown (rgba(59,42,30,...)) predating the monochrome
     rebrand — repointed to --color-primary so the overlay matches every other dark scrim. */
  background: linear-gradient(180deg, rgba(18, 18, 18, 0) 40%, rgba(18, 18, 18, 0.72) 100%);
}

.feature-card__overlay::before {
  content: "";
  position: absolute;
  inset-inline-start: var(--space-5);
  inset-block-end: calc(var(--space-5) + 2.6em);
  width: 1.75rem;
  height: 2px;
  background: var(--color-gold-soft);
  opacity: 0;
  transform: scaleX(0);
  transform-origin: left;
  transition: opacity var(--transition-base), transform var(--transition-base);
}

[dir=rtl] .feature-card__overlay::before {
  transform-origin: right;
}

.feature-card:hover .feature-card__overlay::before {
  opacity: 1;
  transform: scaleX(1);
}

.feature-card__overlay h3 {
  color: var(--color-white);
  transition: transform var(--transition-base);
}

.feature-card:hover .feature-card__overlay h3 {
  transform: translateY(-4px);
}

/* ====================================================================== */
/* Accordion — smooth height transition via grid-template-rows            */
/* Reused on contactPage.cshtml (FAQ) and product.cshtml (specs/notes).   */
/* ====================================================================== */
.accordion-item {
  border-block-end: 1px solid var(--color-border);
}

.accordion-item__trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--space-4);
  font-weight: 600;
  color: var(--color-heading);
  text-align: start;
  transition: color var(--transition-fast);
}

.accordion-item__trigger:hover {
  color: var(--color-accent);
}

.accordion-item__icon {
  display: inline-flex;
  transition: transform var(--transition-base);
}

.accordion-item[data-open=true] .accordion-item__icon {
  transform: rotate(45deg);
}

.accordion-item__panel {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows var(--transition-base);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.accordion-item__panel > * {
  overflow: hidden;
  min-height: 0;
}

.accordion-item__panel > *:last-child {
  padding-block-end: var(--space-4);
}

.accordion-item[data-open=true] .accordion-item__panel {
  grid-template-rows: 1fr;
}

@media (prefers-reduced-motion: reduce) {
  .accordion-item__panel {
    transition: none;
  }
}
/* =========================
   Partials
========================= */
/* ====================================================================== */
/* Account menu — the header's account icon becomes a small dropdown      */
/* (name/email + orders + wishlist + sign out) once signed in; for a      */
/* guest the icon stays a plain link to the account/login page.           */
/* ====================================================================== */
.account-menu {
  position: relative;
}

/* Once signed in, the account icon grows into a small pill showing the first name (desktop and
   wider tablets only — see the max-width rule below); a guest, or a narrow viewport, keeps the
   plain circular icon-only button untouched. */
.icon-btn--labeled {
  width: auto;
  padding-inline: 0.65rem;
  border-radius: 999px;
  gap: 0.4rem;
}

.account-menu__label {
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  max-width: 8rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Reserves roughly a first-name's worth of width from first paint, so the header never visibly
   jumps once auth resolves — the skeleton shrinks to nothing (`hidden`) at the same moment the
   real name (or nothing, for a guest) takes over, both inside the same .icon-btn--labeled pill. */
.account-menu__label--skeleton {
  width: 3.25rem;
  height: 0.8em;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--color-skeleton-base) 25%, var(--color-skeleton-shine) 37%, var(--color-skeleton-base) 63%);
  background-size: 400% 100%;
  animation: dkhon-shimmer 1.4s ease infinite;
}

@media (prefers-reduced-motion: reduce) {
  .account-menu__label--skeleton {
    animation: none;
  }
}
/* A cached-from-sessionStorage name shown before auth has actually confirmed it — a hair more
   muted than the confirmed label so it visually reads as provisional, never identical to a
   verified state. auth-ui.js removes this class the instant the real user resolves. */
.account-menu__label.is-resolving {
  opacity: 0.7;
}

@media (max-width: 719px) {
  .account-menu__label,
  .account-menu__label--skeleton {
    display: none;
  }
}
.account-menu__panel {
  position: absolute;
  inset-inline-end: 0;
  inset-block-start: calc(100% + 0.6rem);
  width: 13rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  z-index: 550;
}

.account-menu__panel[hidden] {
  display: none;
}

/* auth-ui.js toggles this panel purely via the hidden property (no open/close class), so a
   transition (which needs a prior frame to interpolate from) can't drive its entrance — but a
   keyframe animation restarts automatically every time an element newly matches the rule it's
   declared under, even coming from display:none. Same technique already used above for
   .icon-btn.is-active's heart-pulse: zero JS changes needed to give this dropdown the same small
   rise-and-fade every other overlay (cart drawer, welcome popup) already has. */
@keyframes dkhon-dropdown-in {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@media not (prefers-reduced-motion: reduce) {
  .account-menu__panel:not([hidden]) {
    animation: dkhon-dropdown-in 200ms var(--ease-premium);
  }
}
.account-menu__user {
  padding: 0.4rem 0.5rem 0.6rem;
  margin-block-end: 0.4rem;
  border-block-end: 1px solid var(--color-border);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-heading);
  overflow-wrap: anywhere;
}

.account-menu__panel a,
.account-menu__logout {
  padding: 0.5rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-text);
  text-align: start;
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.account-menu__panel a:hover,
.account-menu__logout:hover {
  color: var(--color-accent-deep);
  background: var(--color-bg-alt);
}

.mobile-nav__logout {
  padding: 0.6rem 0.5rem;
  text-align: start;
  font-size: var(--text-sm);
  color: var(--color-text);
}

/* ====================================================================== */
/* Site header — three deliberate bands so the header has real structure  */
/* and presence instead of one thin translucent row:                     */
/*   1. .site-header__announce — commented out at the Razor level        */
/*      (Header.cshtml) — see that file's "temporarily disabled" comment */
/*   2. .site-header__bar       — logo, search pill, account/cart/etc.   */
/*   3. .site-header__category-row — four plain direct links today (All  */
/*      Perfumes/Men/Women/Best Sellers). The full 10-item mega-menu     */
/*      taxonomy and its hover/click/focus submenu panels are commented  */
/*      out at the Razor level in Header.cshtml, not deleted — the       */
/*      .category-submenu rules below stay defined, unused, for when     */
/*      that's restored.                                                 */
/* ====================================================================== */
.site-header {
  position: fixed;
  inset-inline: 0;
  inset-block-start: 0;
  z-index: 500;
  /* Matches the reference site's own header strategy exactly: header/utility-bar/menu-drawer/
     submenus are all its plain light "scheme-1" (white), not a dark band — depth and hierarchy
     come from the hairline border below and restrained hover tints, not a black-dominant header. */
  background: var(--color-page);
  border-block-end: 1px solid var(--color-border);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-base), background-color var(--transition-base);
}

/* Once scrolled: a touch of translucency + backdrop blur ("frosted glass") layered on top of the
   plain opaque header, plus the deeper shadow already here — subtle, not a redesign, and it's
   only additive on top of the existing solid background (which stays the real fallback for the
   many browsers/print contexts backdrop-filter isn't available in). */
.site-header--scrolled {
  background: color-mix(in srgb, var(--color-page) 92%, transparent);
  box-shadow: var(--shadow-md);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .site-header--scrolled {
    backdrop-filter: blur(var(--blur-amount-sm));
    -webkit-backdrop-filter: blur(var(--blur-amount-sm));
  }
}
/* A small extra row-padding reduction once scrolled — the header settles a few px shorter, on
   top of the frosted-glass treatment above, rather than staying a fixed height throughout the
   visit. animations.js's setupHeaderHeightSync() already re-measures the header via a
   ResizeObserver whenever its own box changes, so the page's reserved top padding stays in sync
   automatically; nothing else needs to know this happened. */
.site-header--scrolled .site-header__bar {
  padding-block: var(--space-1);
}

.site-header__announce {
  position: relative;
  overflow: hidden;
  /* Fixed (not min-height) — every slide inside is absolutely positioned so none of them
     contributes to the container's natural height; this is the single source of the bar's
     compact size. Sized for one short line at this font-size, with a small safety margin. */
  height: 2rem;
  background: #242321;
  color: var(--color-text-on-dark);
  font-size: clamp(0.7rem, 0.66rem + 0.16vw, 0.78rem);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: 0.025em;
  text-align: center;
}

[dir=rtl] .site-header__announce {
  letter-spacing: 0;
}

/* Each slide stacks in the same box (absolutely positioned, one per message) and only ever one
   carries ".is-active" — announcement-rotator.js swaps that class on an interval, sliding the
   outgoing slide up and out (".is-exiting") while the incoming one slides up into place from
   below, matching the "smooth upward transition" requirement without changing the bar's own
   compact height. Default (neither class) sits just below the visible box, ready to enter. */
.site-header__announce-item {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.35rem var(--container-padding);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  transform: translateY(100%);
  opacity: 0;
  transition: transform 0.5s ease, opacity 0.5s ease;
}

.site-header__announce-item span {
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: min(100%, 46rem);
}

.site-header__announce-item.is-active {
  transform: translateY(0);
  opacity: 1;
}

.site-header__announce-item.is-exiting {
  transform: translateY(-100%);
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .site-header__announce-item {
    transition: none;
  }
}
.site-header__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  row-gap: var(--space-2);
  gap: var(--space-2);
  /* Positioning context for the desktop expand-in-place search field (see .site-header__search's
     own >=720px rule) — it's anchored to a JS-measured offset from this box's own edge, not
     .site-header's full-viewport-width fixed box, so the offset math lines up with the visible
     bar content (which is centered/max-width constrained via .container). */
  position: relative;
  /* Compact premium header: a slim, fixed padding-block (not the logo's own box) is what keeps
     the bar short — .site-header__logo below now owns an explicit per-breakpoint height instead
     of leaving the bar purely "sized by its tallest child," which is what let the bar balloon to
     the logo's full intrinsic size before. Still no min-height/height on the bar itself, so
     animations.js's setupHeaderHeightSync() keeps measuring the real, now much shorter, height. */
  /*padding-block: var(--space-2);*/
  /* Lets .site-header--scrolled's own reduced padding-block (below) animate smoothly in both
     directions — declared here (the base state), not only on the scrolled variant, so reverting
     to the base padding on scroll-up transitions too instead of snapping back instantly. */
  transition: padding-block var(--transition-base);
}

/* Explicit per-breakpoint height on the WRAPPER (not the <img> — see the rule below) is what
   actually controls the logo's rendered size and, with it, the header's overall height. This is
   the one place a literal pixel value belongs: a layout/container concern, not a constraint
   stamped onto the image element itself. */
.site-header__logo {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  height: 5rem;
  transition: transform var(--transition-fast);
}

@media (min-width: 768px) {
  .site-header__logo {
    height: 5rem;
  }
}
@media (min-width: 960px) {
  .site-header__logo {
    height: 6.5rem;
  }
}
.site-header__logo:hover {
  transform: scale(1.03);
}

/* The actual logo <img> carries NO width/min-width/max-width and no fixed/max/min-height of its
   own — only `height: 100%` (tracking the wrapper above, never a hardcoded number) and `width:
   auto`, so the browser derives the other dimension purely from the image's own real ratio. A
   single dimension constrained + the other left free is what guarantees this can never stretch or
   squash the mark, regardless of the wrapper's height at any breakpoint. */
.site-header__logo img {
  display: block;
  width: auto;
  height: 100%;
  object-fit: contain;
}

.site-header__logo-text {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--color-heading);
}

.site-header__search {
  display: none;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  max-width: 22rem;
  margin-inline: var(--space-6);
  padding: 0.6rem 1.2rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-bg-alt);
  transition: border-color var(--transition-fast), background-color var(--transition-fast), box-shadow var(--transition-fast);
}

.site-header__search:hover,
.site-header__search:focus-within {
  border-color: var(--color-accent-soft);
  background: var(--color-surface);
}

.site-header__search:focus-within {
  box-shadow: 0 0 0 3px rgba(18, 18, 18, 0.12);
}

.site-header__search svg {
  width: 1.1rem;
  height: 1.1rem;
  color: var(--color-accent);
  flex-shrink: 0;
}

.site-header__search input {
  flex: 1;
  border: 0;
  background: transparent;
  outline: none;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
}

@media (max-width: 767px) {
  /* Covers the icon-toggled search field at every width below 768px (<720px as the fixed mobile
     strip, 720-767px as the same icon-toggled inline field) — both are still "mobile" per this
     pass's 767/768 split, and 14px triggers iOS Safari's auto-zoom on focus either way. True
     desktop (768px+) keeps the existing 14px, unchanged. */
  .site-header__search input {
    font-size: 13px;
  }
}
.site-header__search input::placeholder {
  color: var(--color-text-muted);
}

@media (min-width: 720px) {
  /* Desktop: the icon (.site-header__search-toggle, in .site-header__actions) never moves and the
     rest of the header never reflows — this field is taken out of flex flow entirely and expands
     in place, anchored via the --header-search-anchor custom property (set by header-search.js
     from the toggle icon's own measured position) rather than by pushing any flex sibling. Closed
     by default (max-width/opacity/visibility all collapsed); .is-open animates it open smoothly.
     Mobile's own always-visible-when-open behavior (<720px, further down) is untouched. */
  .site-header__search {
    display: flex;
    position: absolute;
    inset-inline-end: var(--header-search-anchor, 100%);
    top: 50%;
    transform: translateY(-50%);
    margin-inline: 0;
    flex: none;
    max-width: 0;
    padding-inline: 0;
    overflow: hidden;
    white-space: nowrap;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    /* --transition-base/--transition-fast already carry their own easing curve (see tokens.css) —
       appending another timing-function after them would make each list item invalid and drop
       the whole declaration, which is exactly what happened here before this fix. */
    transition: max-width var(--transition-base), opacity var(--transition-fast), padding-inline var(--transition-base), visibility var(--transition-base);
  }
  .header-search.is-open .site-header__search {
    max-width: min(22rem, 100vw - 15rem);
    padding-inline: 1.2rem;
    margin-inline-end: var(--space-2);
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
}
.site-header__actions {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  /* --space-1 (4px) read as visually cramped between the icon set — --space-2 gives the
     cart/wishlist/account/search icons a touch more breathing room without pushing the compact
     header back past its target height on the narrowest phones. */
  gap: var(--space-1);
}

/* Below ~400px there isn't room for the logo + the language switcher + all four action icons on
   one line without wrapping to a second row, which is what was inflating the compact header back
   past its target height on the narrowest phones. The switcher is never actually unavailable —
   it's simply not duplicated in the bar at this width; the mobile drawer (opened by the menu
   button that's still right here) renders its own full copy of the exact same component. */
.site-header__actions > .lang-switch {
  display: none;
}

@media (min-width: 400px) {
  .site-header__actions > .lang-switch {
    display: inline-flex;
  }
  /* Mirror image of the rule above: once the header bar's own switcher is visible again
     (>=400px), the mobile drawer's copy becomes a second, redundant language control on the
     same screen the moment the drawer is opened — hide it there and leave the header bar as
     the single source. Below 400px (rule above hides the bar's copy) this stays visible, since
     the drawer is then the only place a visitor can switch language at all. */
  .mobile-nav .lang-switch {
    display: none;
  }
}
.site-header__search-toggle {
  display: inline-flex;
}

/* ====================================================================== */
/* Header live search — expandable panel replacing the old search-icon    */
/* redirect. .site-header__search above stays the real, always-working   */
/* <form>; everything below only adds the dropdown/overlay presentation   */
/* and the mobile expand/collapse state (see header-search.js).          */
/* ====================================================================== */
.header-search {
  /* Deliberately not position:relative — the desktop expand-in-place field and its results panel
     (see their own >=720px rules) anchor against .site-header__bar instead, since .header-search
     itself sits in normal flex flow between the logo and .site-header__actions and (once its own
     children go absolute/hidden) collapses to ~0 width, which is not a useful positioning
     reference. Mobile's full-screen open state (further down) sets its own position: fixed
     explicitly and doesn't depend on this either way. */
}

.header-search__close {
  display: none;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 50%;
  color: var(--color-text-muted);
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.header-search__close svg {
  width: 1rem;
  height: 1rem;
}

.header-search__close:hover {
  color: var(--color-heading);
  background: var(--color-border);
}

.header-search.is-open .header-search__close {
  display: inline-flex;
}

@media (min-width: 720px) {
  /* Desktop only starts showing the clear (X) button once the field actually has something typed
     into it (see .header-search--has-value below and header-search.js) — otherwise it's just the
     search icon plus an empty field. Comes after (and so overrides) the "always show once open"
     rule directly above, which mobile still relies on unchanged. */
  .header-search.is-open .header-search__close {
    display: none;
  }
  .header-search.is-open.header-search--has-value .header-search__close {
    display: inline-flex;
  }
}
.header-search__panel {
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(100% + 0.6rem);
  max-height: 26rem;
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-2);
  z-index: 550;
}

@media (min-width: 720px) {
  /* Anchored the same way the expanded field itself is (see .site-header__search's own >=720px
     rule and header-search.js) so the results dropdown lines up under the field rather than
     stretching the full header width. Mobile's own position:static override (further down)
     already takes precedence there regardless. */
  .header-search__panel {
    inset-inline-start: auto;
    inset-inline-end: var(--header-search-anchor, 100%);
    width: min(22rem, 100vw - 15rem);
  }
}
.header-search__panel[hidden] {
  display: none;
}

.header-search__message {
  padding: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-align: center;
}

.header-search__result {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  border-radius: var(--radius-md);
  color: var(--color-text);
  transition: background-color var(--transition-fast);
}

.header-search__result:hover,
.header-search__result.is-active {
  background: var(--color-bg-alt);
}

.header-search__result-thumb {
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-bg-alt);
}

.header-search__result-thumb > * {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.header-search__result-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.header-search__result-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-heading);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.header-search__result-price {
  font-size: var(--text-xs);
  margin: 0;
  min-block-size: 0;
}

.header-search__result-badges {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.2rem;
  flex-shrink: 0;
}

.header-search__result-badges .badge {
  font-size: 0.55rem;
  padding: 0.15rem 0.4rem;
}

/* Below the toggle breakpoint, the search icon expands the whole field + results panel into a
   fixed, full-width strip directly under the header (--site-header-height is kept in sync with
   the header's real measured height by animations.js's setupHeaderHeightSync) instead of a
   dropdown anchored to a collapsed, normally-hidden flex item. */
@media (max-width: 719.98px) {
  .header-search.is-open {
    position: fixed;
    inset-inline: 0;
    inset-block-start: var(--site-header-height);
    z-index: 480;
    background: var(--color-page);
    box-shadow: var(--shadow-md);
    padding: var(--space-3);
  }
  .header-search.is-open .site-header__search {
    display: flex;
    margin-inline: 0;
    max-width: none;
  }
  .header-search.is-open .header-search__panel {
    position: static;
    inset: auto;
    margin-block-start: var(--space-2);
    border: 0;
    box-shadow: none;
    max-height: calc(100dvh - var(--site-header-height) - 6rem);
  }
}
/* Mobile-first default: the hamburger is the only way into the nav below 960px. Above that,
   the category row takes over and the hamburger hides. */
.site-header__menu-toggle {
  display: inline-flex;
}

@media (min-width: 960px) {
  .site-header__menu-toggle {
    display: none;
  }
}
/* ====================================================================== */
/* Category row — the header's third band. Today: four plain direct links */
/* (All Perfumes/Men/Women/Best Sellers) — clicking any item navigates    */
/* immediately, no dropdown/mega-menu on hover, click, focus, keyboard,   */
/* or mobile tap (see Header.cshtml). The .category-submenu rules below   */
/* stay defined for the disabled mega-menu code they still belong to —    */
/* none of the four links today render a .category-submenu element, so   */
/* these rules simply have nothing to select and have no visual effect.  */
/* ====================================================================== */
.site-header__category-row {
  display: none;
}

@media (min-width: 960px) {
  .site-header__category-row {
    display: block;
    position: relative;
    background: var(--color-bg-alt);
    border-block-start: 1px solid var(--color-border);
  }
}
.category-row__list {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(var(--space-4), 2.4vw, var(--space-7));
  padding-block: var(--space-2);
}

.category-row__item {
  position: relative;
}

.category-row__item > a,
.category-row__trigger {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.75rem;
  font-size: 15px;
  line-height: var(--leading-tight);
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--color-text);
  padding-block: var(--space-1);
  transition: color var(--transition-fast);
}

.category-row__item > a::after,
.category-row__trigger::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: -1px;
  height: 1.5px;
  background: var(--color-accent);
  /* Grows from the center outward, not left-to-right — reads as a more deliberate, considered
     reveal than a directional sweep, and (being direction-neutral) needs no separate RTL rule. */
  transform-origin: center;
  transform: scaleX(0);
  transition: transform var(--transition-base);
}

.category-row__item > a:hover,
.category-row__item > a:focus-visible,
.category-row__trigger:hover,
.category-row__trigger:focus-visible {
  color: var(--color-accent-deep);
}

.category-row__item > a:hover::after,
.category-row__item > a:focus-visible::after,
.category-row__trigger:hover::after,
.category-row__trigger:focus-visible::after {
  transform: scaleX(1);
}

.category-row__item > a:focus-visible,
.category-row__trigger:focus-visible {
  outline: 2px solid var(--color-secondary);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

/* Persistent thin underline for whichever of the four direct links matches the current page/
   query state (see Header.cshtml's activeNavKey) — same ::after element hover/focus already
   animate, just held open rather than needing a second visual treatment. */
.category-row__item > a.is-active::after,
.category-row__trigger.is-active::after {
  transform: scaleX(1);
}

.category-row__item > a.is-all {
  color: var(--color-gold-deep);
  font-weight: 700;
}

/* ====================================================================== */
/* Category submenu — compact, per-item panel: title + description + its */
/* own 2-4 curated links + a "View all" CTA. Anchored under its own       */
/* trigger, not the full row, so there is never empty leftover space.    */
/* ====================================================================== */
.category-submenu {
  position: absolute;
  inset-inline-start: 50%;
  /* Flush against the trigger's own box — NOT calc(100% + Xrem). A gap here is dead space no
     element covers, so the pointer briefly hovers nothing while crossing it and :hover/
     [data-menu-open] on .category-row__item is lost before the panel is reached. The panel's
     own padding below already reads as "breathing room" without an uncovered gap in between. */
  inset-block-start: 100%;
  transform: translate(-50%, -6px);
  width: 16rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-4);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  filter: blur(var(--blur-amount-sm));
  transition: opacity var(--transition-base), transform var(--transition-base), visibility var(--transition-base), filter var(--transition-base);
  z-index: 550;
}

[dir=rtl] .category-submenu {
  transform: translate(50%, -6px);
}

/* The two right-most items would otherwise overflow the viewport since a
   center-anchored panel needs room on both sides — anchor those to the end
   of the trigger instead so they open inward. */
.category-row__item:nth-last-child(-n+2) .category-submenu {
  inset-inline-start: auto;
  inset-inline-end: 0;
  transform: translateY(-6px);
}

/* [data-menu-open="true"] is set by header-menu.js's desktop menu controller, which owns the
   actual open/close timing (including a short defensive close delay for diagonal pointer
   movement) — kept alongside :hover/:focus-within rather than replacing them, so the menu still
   opens correctly (if slightly less politely) even if that script fails to load. */
.category-row__item[data-menu-open=true] .category-submenu {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  filter: none;
  transform: translate(-50%, 0);
}

[dir=rtl] .category-row__item[data-menu-open=true] .category-submenu {
  transform: translate(50%, 0);
}

.category-row__item:nth-last-child(-n+2)[data-menu-open=true] .category-submenu {
  transform: translateY(0);
}

.category-row__item[data-menu-open=true] .header-menu__chevron {
  transform: rotate(180deg);
}

.category-submenu--simple {
  width: 13rem;
  padding: var(--space-2);
}

.category-submenu--simple > a {
  display: flex;
  align-items: center;
  min-height: 2.75rem;
  padding-inline: var(--space-3);
  border-radius: var(--radius-md);
  color: var(--color-heading);
  font-size: var(--text-sm);
  font-weight: 600;
}

.category-submenu--simple > a:hover,
.category-submenu--simple > a:focus-visible,
.category-submenu--simple > a.is-active {
  background: var(--color-bg-warm);
  color: var(--color-accent-deep);
}

.category-submenu__title {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding-block-end: var(--space-3);
  margin-block-end: var(--space-2);
  border-block-end: 1px solid var(--color-border);
}

.category-submenu__title:hover .category-submenu__title-text {
  color: var(--color-accent-deep);
}

.category-submenu__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--color-bg-warm);
  color: var(--color-accent-deep);
}

.category-submenu__icon svg {
  width: 1.15rem;
  height: 1.15rem;
}

.category-submenu__title-text {
  display: block;
  font-family: var(--font-heading);
  font-size: var(--text-md);
  color: var(--color-heading);
  margin-block-end: 0.2em;
  transition: color var(--transition-fast);
}

.category-submenu__desc {
  display: block;
  font-size: 0.7rem;
  color: var(--color-text-muted);
  line-height: var(--leading-normal);
}

.category-submenu__links {
  display: flex;
  flex-direction: column;
}

.category-submenu__links a {
  padding: 0.45rem 0.4rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-text);
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.category-submenu__links a:hover {
  color: var(--color-accent-deep);
  background: var(--color-bg-alt);
}

.category-submenu__view-all {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--color-border);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-accent-deep);
  transition: color var(--transition-fast);
}

[dir=rtl] .category-submenu__view-all {
  letter-spacing: 0;
  text-transform: none;
}

.category-submenu__view-all svg {
  width: 0.9rem;
  height: 0.9rem;
  /* margin-inline-start (not transform: translateX) so the same rule nudges the arrow "forward"
     correctly in both directions with no [dir="rtl"] override — the icon itself already carries
     its own RTL mirror via .icon-flip, so composing a second transform on top of that here would
     need direction-aware math; a logical margin sidesteps it entirely. */
  transition: margin-inline-start var(--transition-fast);
}

.category-submenu__view-all:hover {
  color: var(--color-gold-deep);
}

.category-submenu__view-all:hover svg {
  margin-inline-start: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .category-submenu,
  .category-row__item > a::after,
  .category-submenu__view-all svg {
    transition: none;
  }
}
/* ====================================================================== */
/* Mobile nav drawer                                                      */
/* ====================================================================== */
.mobile-nav {
  position: fixed;
  inset: 0;
  z-index: 600;
  visibility: hidden;
  pointer-events: none;
}

.mobile-nav[data-open=true] {
  visibility: visible;
  pointer-events: auto;
}

.mobile-nav__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(18, 18, 18, 0.4);
  opacity: 0;
  transition: opacity var(--transition-base);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .mobile-nav__backdrop {
    backdrop-filter: blur(var(--blur-amount-sm));
    -webkit-backdrop-filter: blur(var(--blur-amount-sm));
  }
}
.mobile-nav[data-open=true] .mobile-nav__backdrop {
  opacity: 1;
}

.mobile-nav__panel {
  position: absolute;
  inset-block: 0;
  width: min(26rem, 92vw);
  background: linear-gradient(160deg, var(--color-porcelain) 0%, var(--color-ivory) 100%);
  padding: var(--space-3) var(--space-5);
  transition: transform var(--transition-slow);
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  text-align: start;
}

/* Anchored via inset-inline-end (logical, not a hardcoded left/right per html[dir]) so this
   always opens from wherever its own trigger — the hamburger button, the last item in
   .site-header__actions' plain unoverridden flex row — actually sits: physical right in LTR,
   physical left in RTL. A previous version hardcoded "LTR opens left / RTL opens right" purely
   from dir, which happened to put the drawer on the OPPOSITE side from the burger button in both
   languages (the actions cluster — and therefore the burger — sits at the flex row's inline-end,
   not the inline-start). translateX is a physical transform (always along the literal screen
   +X axis), so its sign must still flip under RTL even though the anchor itself doesn't need a
   direction-specific override. */
.mobile-nav__panel {
  inset-inline-end: 0;
  border-inline-start: 1px solid var(--color-border);
  transform: translateX(100%);
}

[dir=rtl] .mobile-nav__panel {
  transform: translateX(-100%);
}

.mobile-nav[data-open=true] .mobile-nav__panel {
  transform: translateX(0);
}

.mobile-nav__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

/* Same wrapper-driven sizing principle as .site-header__logo: the literal height lives on the
   wrapper, never on the <img> itself. */
.mobile-nav__logo {
  display: inline-flex;
  align-items: center;
  height: 5rem;
}

.mobile-nav__logo img {
  display: block;
  width: auto;
  height: 100%;
  object-fit: contain;
}

.mobile-nav__close {
  flex-shrink: 0;
}

.mobile-nav__search {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.6rem 1.1rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-bg-alt);
}

.mobile-nav__search svg {
  width: 1.1rem;
  height: 1.1rem;
  color: var(--color-accent);
  flex-shrink: 0;
}

.mobile-nav__search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  outline: none;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
}

@media (max-width: 767px) {
  /* The hamburger-drawer search is mobile-only already, but pin the fix here too rather than
     relying on that — 14px triggers iOS Safari's auto-zoom on focus. */
  .mobile-nav__search input {
    font-size: var(--text-base);
  }
}
.mobile-nav__links {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.mobile-nav__account-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-block: var(--space-4);
  border-block: 1px solid var(--color-border);
}

.mobile-nav__account-links a {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-heading);
}

.mobile-nav__account-links svg {
  width: 1.2rem;
  height: 1.2rem;
  color: var(--color-accent);
  flex-shrink: 0;
}

.mobile-nav__links a {
  display: flex;
  align-items: center;
  /* Touch target height is unchanged (2.75rem/44px) -- only the type shrank, from the full
     --text-lg (22px) desktop-dropdown scale down to --text-md (18px), which is still clearly the
     largest, most prominent text in the drawer relative to the account links/submenu items below
     it (both --text-sm/14px), just no longer oversized for a plain single-word nav list. */
  min-height: 2.75rem;
  padding-inline: var(--space-1);
  font-family: var(--font-heading);
  font-size: var(--text-md);
  color: var(--color-heading);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.mobile-nav[data-open=true] .mobile-nav__links a {
  opacity: 1;
  transform: none;
}

.mobile-nav__whatsapp {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  transition: color var(--transition-fast);
}

.mobile-nav__whatsapp:hover {
  color: var(--color-accent);
}

.mobile-nav__whatsapp svg {
  width: 1.1rem;
  height: 1.1rem;
}

/* Each category gets its own accordion row inside the drawer, independently expanding in place
   to reveal that category's own submenu content — instead of navigating away or needing a
   second overlay. Reuses the accordion-item's grid-template-rows height trick. */
.mobile-nav__disclosure {
  display: flex;
  flex-direction: column;
}

.mobile-nav__disclosure-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  /* Matches .mobile-nav__links a's own --text-md (18px) -- "Perfumes" is a peer of Home/
     Collections/Gifts in the same list, not a bigger heading, so it reads at the same size as
     every other top-level item around it. */
  min-height: 2.75rem;
  padding-inline: var(--space-1);
  font-family: var(--font-heading);
  font-size: var(--text-md);
  color: var(--color-heading);
}

.mobile-nav__disclosure-trigger .header-menu__chevron {
  width: 1rem;
  height: 1rem;
}

.mobile-nav__disclosure[data-open=true] .header-menu__chevron {
  transform: rotate(180deg);
}

.mobile-nav__disclosure-panel {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows var(--transition-base);
}

.mobile-nav__disclosure[data-open=true] .mobile-nav__disclosure-panel {
  grid-template-rows: 1fr;
}

.mobile-nav__disclosure-panel > div {
  overflow: hidden;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  padding-inline-start: var(--space-4);
}

/* The actual fix for the "empty box when closed" bug: this padding was previously unconditional,
   so even with the grid row above collapsed to a 0 track, the child's own top/bottom padding
   still forced it to render at ~12px tall (overflow:hidden clips overflowing CONTENT, never an
   element's own padding) -- a visible empty strip under the trigger even while "closed". Scoping
   the padding to the open state only means a closed panel now has zero intrinsic height, not just
   a zero-height track fighting a non-zero child. */
.mobile-nav__disclosure[data-open=true] .mobile-nav__disclosure-panel > div {
  padding-block: var(--space-1) var(--space-2);
}

.mobile-nav__disclosure-panel a {
  min-height: 2.75rem;
  padding-inline: var(--space-3);
  border-inline-start: 1px solid var(--color-border);
  font-family: var(--font-body);
  font-size: var(--text-sm);
}

.mobile-nav__links .is-active {
  color: var(--color-accent-deep);
  font-weight: 700;
}

/* Inside the mobile drawer, the same per-category submenu partial renders as a plain static
   block (no absolute positioning, no hover-gated visibility) with generous tap targets —
   same partial and content as the desktop hover panel, different container context. */
.mobile-nav__disclosure-panel .category-submenu {
  position: static;
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
  width: auto;
  border: 0;
  box-shadow: none;
  padding: 0;
  background: transparent;
}

.mobile-nav__disclosure-panel .category-submenu__links a:active {
  background: var(--color-bg-warm);
}

@media (prefers-reduced-motion: reduce) {
  .mobile-nav__disclosure-panel {
    transition: none;
  }
}
.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(1) {
  transition-delay: 80ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(2) {
  transition-delay: 120ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(3) {
  transition-delay: 160ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(4) {
  transition-delay: 200ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(5) {
  transition-delay: 240ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(6) {
  transition-delay: 280ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(7) {
  transition-delay: 320ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(8) {
  transition-delay: 360ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(9) {
  transition-delay: 400ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(10) {
  transition-delay: 440ms;
}

.mobile-nav[data-open=true] .mobile-nav__links a:nth-child(11) {
  transition-delay: 480ms;
}

@media (prefers-reduced-motion: reduce) {
  .mobile-nav__panel,
  .mobile-nav__backdrop,
  .mobile-nav__links a {
    transition: none;
  }
  .mobile-nav__links a {
    opacity: 1;
    transform: none;
  }
}
/* Language switcher — a small, self-sized pill, never a full-width bar. .mobile-nav__panel is a
   `flex-direction: column` container, whose default `align-items: stretch` would otherwise force
   this (and every other direct child) to fill the drawer's entire cross-axis width regardless of
   its own `inline-flex` sizing — align-self overrides that stretch so it always hugs its own
   content, in the header bar (a row, unaffected either way) and the mobile drawer alike. */
.lang-switch {
  display: inline-flex;
  align-items: center;
  align-self: center;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 0.15rem;
  gap: 0.15rem;
  transition: border-color var(--transition-base);
}

.lang-switch a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1;
  /* --color-text-muted measured at ~4.11:1 here against white — below WCAG's 4.5:1 minimum; a
     solid darker gray scoped to just this element clears it (6.69:1). */
  color: #5c5c5c;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.lang-switch a[aria-current=true] {
  background: var(--color-primary);
  color: var(--color-white);
}

/* ====================================================================== */
/* Footer — clean, light, quiet luxury                                   */
/* Home-specific overrides (.site-footer--home) live in pages/_homePage.scss */
/* ====================================================================== */
.site-footer {
  /* Matches the reference's own footer exactly: white, identical to the body — no distinct dark
     footer band. */
  background: var(--color-footer);
  color: var(--color-text);
  border-block-start: 1px solid var(--color-border);
}

.site-footer a {
  color: var(--color-text-muted);
  transition: color var(--transition-fast);
}

.site-footer a:hover {
  color: var(--color-accent);
}

.site-footer__strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  justify-content: center;
  padding-block: var(--space-6);
  font-size: var(--text-sm);
  font-weight: 600;
}

.site-footer__strip > div {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text);
}

.site-footer__strip svg {
  width: 1.15rem;
  height: 1.15rem;
  color: var(--color-accent);
}

.site-footer__grid {
  display: grid;
  /* minmax(0, 1fr), not the implicit auto-sized single column this leaves on mobile without it —
     grid items default to min-width:auto, so an auto column grows to fit whichever child's own
     intrinsic content (a link, the newsletter input, a long word) is widest, even past the
     viewport. minmax(0, ...) is what actually lets the column (and everything in it) shrink to
     fit instead. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--storefront-content-gap);
}

@media (min-width: 768px) {
  .site-footer__grid {
    grid-template-columns: 1.3fr 1fr 1fr 1.2fr;
  }
}
/* Was only 2.5rem (40px) — clearly too small next to the enlarged header logo above, so bumped
   the same way (explicit width target; height stays fully auto). */
.site-footer__logo img {
  display: block;
  max-width: 100%;
  object-fit: contain;
}
@media (max-width: 479px) {
  .site-footer__logo img {
    height: 5rem;
  }
}

.site-footer__brand-text {
  margin-block-start: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  max-width: 22rem;
}

/* Was <h4> — with no <h3> anywhere between the last page heading and the footer on any page,
   that skipped a level (axe/Lighthouse's heading-order rule); <h3> is the correct next level down
   and was already the immediately-following level everywhere these columns appear. */
.site-footer h3 {
  color: var(--color-heading);
  font-size: var(--text-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-block-end: var(--space-3);
}

[dir=rtl] .site-footer h3 {
  letter-spacing: 0;
  text-transform: none;
}

.site-footer__links {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.site-footer__newsletter {
  display: flex;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
}

.site-footer__newsletter input {
  flex: 1;
  /* Flex items default to min-width:auto, so without this an <input>'s own intrinsic width
     (not just its styled flex:1 basis) can refuse to shrink below its content's natural size —
     forcing this row (and the single-column mobile footer grid it sits in) wider than the
     viewport on narrow screens/longer English button labels. */
  min-width: 0;
  padding: 0.6rem 0.9rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-white);
  color: var(--color-text);
  transition: border-color var(--transition-fast);
}

.site-footer__newsletter input:focus {
  border-color: var(--color-accent);
  outline: none;
}

.site-footer__newsletter input::placeholder {
  color: var(--color-text-muted);
}

.site-footer__bottom {
  border-block-start: 1px solid var(--color-border);
  margin-block-start: var(--space-5);
  padding-block: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-xs);
  /* --color-text-muted (rgba(18,18,18,.55), ~4.05:1 on this footer's off-white background) fails
     WCAG's 4.5:1 minimum at this small size — a solid darker gray scoped to just this element
     clears it (6.3:1) without touching the shared variable everywhere else it's used. */
  color: #5c5c5c;
}

.social-icons {
  display: flex;
  gap: var(--space-3);
  padding-block: var(--space-3);
}

.social-icons a {
  display: inline-flex;
  width: 2.25rem;
  height: 2.25rem;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  color: var(--color-text);
  transition: border-color var(--transition-fast), transform var(--transition-fast), color var(--transition-fast);
}

.social-icons a svg {
  width: 1.1rem;
  height: 1.1rem;
}

.social-icons a:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  transform: translateY(-2px);
}

/* ====================================================================== */
/* Hero — white/ivory, editorial, quiet. No dark scrims, no mist/glow.    */
/* Hero.cshtml — shared by the homepage's cinematic hero (.hero--cinematic) */
/* and the compact page-title band on Shop/Men/Women/category/collection.  */
/* ====================================================================== */
.hero {
  position: relative;
  background: linear-gradient(165deg, var(--color-ivory) 0%, var(--color-champagne) 100%);
  padding-block: var(--space-9) var(--space-8);
  overflow: hidden;
}

.hero::before {
  content: "";
  position: absolute;
  inset-block-start: -10%;
  inset-inline-end: -6%;
  width: 26rem;
  height: 26rem;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(169, 121, 63, 0.14) 0%, transparent 70%);
  pointer-events: none;
}

.hero__grid {
  display: grid;
  gap: var(--space-7);
  align-items: center;
}

@media (min-width: 900px) {
  .hero__grid--has-image {
    grid-template-columns: 1fr 1fr;
  }
}
.hero__content {
  position: relative;
  max-width: 34rem;
}

.hero__content h1 {
  font-size: var(--text-hero);
  line-height: 1.05;
}

.hero__content h1 .hero-line {
  display: block;
  overflow: hidden;
}

.hero__content h1 .hero-line span {
  display: block;
}

.hero__content p {
  color: var(--color-text-muted);
  margin-block: var(--space-4) var(--space-6);
  font-size: var(--text-md);
  max-width: 32rem;
}

.hero__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.hero__visual {
  position: relative;
  height: 26rem;
  border-radius: var(--radius-xl);
  overflow: hidden;
  background: var(--color-champagne);
  box-shadow: var(--shadow-lg);
}

.hero__visual img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Compact hero used on category/collection/shop pages — a quiet ivory
   title band, no imagery, no scrim. */
.hero--compact {
  padding-block: var(--space-7);
}

.hero--compact .hero__content {
  max-width: 34rem;
}

.hero--compact .hero__content h1 {
  font-size: var(--text-3xl);
}

/* ====================================================================== */
/* Cinematic full-bleed hero (homepage only, Dkhon/Hero.cshtml) —          */
/* .hero--cinematic overrides the quiet ivory/compact hero above with a   */
/* full-width, near-full-viewport image hero. The Umbraco heroImage       */
/* property (never a hardcoded path) covers the entire section; headline/ */
/* subtitle/CTAs are layered over a dark scrim using only the site's      */
/* monochrome palette — no separate small image card, no beige void.      */
/* ====================================================================== */
.hero--cinematic {
  background: var(--color-primary);
  padding-block: 0;
  margin-block: 0;
  /* Header + hero together always sum to exactly one viewport — not "hero is its own 100vh on
     top of the header". --site-header-height is the real, measured (both header rows) height,
     kept in sync on every resize by setupHeaderHeightSync() in animations.js; the fixed header
     already reserves that same amount of space above via .page-shell's own padding-top (see
     layout.css), so the section immediately following the hero starts right at one viewport
     height, reachable with the very first scroll. dvh is the real, current viewport (updates as
     mobile browser chrome hides/shows); svh floors the min-height at the *smallest* that chrome
     state can make it, so the hero never grows taller than one screen even mid-transition. */
  height: calc(100dvh - var(--site-header-height));
  min-height: calc(100svh - var(--site-header-height));
  display: flex;
  align-items: center;
}

.hero--cinematic::before {
  content: none;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Favors the upper-middle frame (where a product/model composition typically sits) rather than
     a hard center crop — still responsive/fluid, never a fixed pixel crop. */
  object-position: center 35%;
}

/* The hero video's subject (the perfume bottle) sits fixed at roughly x=80%/y=62% of the actual
   1920x1080 frame — measured directly by sampling per-column/per-row luminance of the decoded
   video across its full 12s duration (canvas getImageData at t=0.2s through 11.8s; the bottle is
   the only lit subject against a near-black background, so its position shows up as a stable
   brightness peak in columns ~15-19 of 20). That peak barely moves across the whole clip, so one
   fixed object-position per tier is enough — no need to key it to playback time.
   This section's height is set to fill the viewport (see .hero--cinematic), so at every real
   breakpoint the video's 16:9 frame is narrower than the section — height renders in full and only
   width gets cropped — except on wide/short desktop windows, where cropping flips to a few percent
   off the top/bottom instead; the object-position below stays safe either way since the bottle's
   full brightness band (x 75%-100%) is within a few points of true right-edge alignment. Mobile
   crops hardest (as little as ~30% of the frame's width fits), so it aligns furthest right to
   guarantee the bottle is never cut; tablet and desktop show progressively more of the frame, so
   they ease back toward the bottle's true measured center instead of over-cropping the empty left
   background for no reason. */
.hero__image--video {
  object-position: 100% 58%;
}

@media (min-width: 768px) {
  .hero__image--video {
    /* 768px is the tightest point in this tier (~44% of frame width visible) — still tight enough
       that anything short of full right-alignment clips the brightest measured column (the bottle's
       core). Stays at 100% through the tier's low end; only eases back once the crop is generous
       enough (see 1025px) that the true measured center is safe again. */
    object-position: 97% 58%;
  }
}
@media (min-width: 1025px) {
  .hero__image--video {
    object-position: 78% 58%;
  }
}
.hero__media-fallback {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(165deg, var(--color-surface-alt) 0%, var(--color-page) 100%);
}

.hero__media-fallback svg {
  width: 40%;
  max-width: 14rem;
  height: auto;
  opacity: 0.5;
}

.hero__scrim {
  position: absolute;
  inset: 0;
  /* Slightly lighter than before at every stop (was 0.8/0.35/0.05) — still enough contrast for
     white text sitting at the bottom edge, but the perfume itself (typically centered/upper in
     the frame) now reads clearly through the mid/top of the image instead of sitting under a
     heavy wash. */
  background: linear-gradient(0deg, rgba(18, 18, 18, 0.68) 0%, rgba(18, 18, 18, 0.28) 45%, rgba(18, 18, 18, 0.02) 75%);
}

.hero--cinematic .hero__content {
  position: relative;
  z-index: 1;
  max-width: 46rem;
  /* Symmetric and modest, not the old bottom-heavy padding — the section now centers its content
     vertically (see .hero--cinematic's align-items) rather than anchoring it to the bottom edge,
     and its own height is capped to exactly (one viewport − header), so padding here must never
     be large enough to risk overflowing that on a short/landscape-mobile viewport. */
  padding-block: var(--space-6);
  color: var(--color-white);
}

.hero--cinematic .hero__heading {
  /* Mobile gets its own, much shallower slope than the desktop formula below. The previous mobile
     clamp (2.2rem + 2vw) still landed at ~42-43px across the whole 320-767px band — a real
     reduction from the old 59px, but still reading as oversized/dominant on a real phone (still
     capped, correctly, well before 768px). Lower floor/ceiling here (~34-36px across 320-390px,
     capping at 38.4px near the 768px handoff) for a genuinely compact first fold. */
  font-size: clamp(1.6rem, 1.3rem + 4vw, 2.4rem);
  line-height: 1.05;
  color: var(--color-white);
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.25);
}

@media (min-width: 768px) {
  .hero--cinematic .hero__heading {
    /* clamp(), not the shared --text-hero token — this scales continuously with viewport width
       (including well past 1440px on a genuinely wide desktop) rather than jumping at fixed
       breakpoints, and is scoped to the cinematic homepage hero only so it never also inflates
       the quiet .hero--compact title band used on shop/category/collection pages. */
    font-size: clamp(2.75rem, 3rem + 3vw, 6rem);
  }
}
/* Arabic gets its own, slightly gentler scale and looser line-height — Cairo (the Arabic display
   face, see --font-heading-ar) is a bold, connected sans at a much heavier optical weight than
   Cormorant Garamond's serif strokes at the same font-size, so the English clamp's 6rem ceiling
   and tight 1.05 line-height (tuned for a serif's ascenders/descenders) read as oversized and
   crowded rather than premium once applied to Arabic script. Never changes the actual copy or
   layout, only how the existing CMS text is set. */
[dir=rtl] .hero--cinematic .hero__heading {
  line-height: 1.35;
}

@media (min-width: 768px) {
  [dir=rtl] .hero--cinematic .hero__heading {
    font-size: clamp(2.5rem, 2.6rem + 2.2vw, 4.5rem);
  }
}
.hero--cinematic .hero__subtitle {
  color: rgba(255, 255, 255, 0.88);
  margin-block: var(--space-5) var(--space-7);
  font-size: clamp(var(--text-md), 0.9rem + 0.6vw, var(--text-xl));
  max-width: 34rem;
}

/* Same rationale as the heading above — Cairo's looser natural rhythm reads clearer with a
   little more breathing room than Latin text needs at the same size. */
[dir=rtl] .hero--cinematic .hero__subtitle {
  line-height: 1.7;
}

/* The one CTA variant that needs a scoped flip here: .btn--outline's default black text/border
   would be unreadable directly on the dark scrim. */
.hero--cinematic .btn--outline {
  color: var(--color-white);
  border-color: var(--color-white);
}

.hero--cinematic .btn--outline:hover {
  background: var(--color-white);
  color: var(--color-primary);
}

.eyebrow--on-media {
  color: var(--color-white);
}

.eyebrow--on-media::before {
  background: var(--color-white);
}

.public-page-header {
  display: flex;
  align-items: center;
  min-height: clamp(5.25rem, 10vw, 9rem);
  padding-block: var(--storefront-page-header-space);
  overflow: hidden;
  background: radial-gradient(circle at 82% 18%, rgba(184, 145, 47, 0.09), transparent 34%), linear-gradient(135deg, #fbfaf7 0%, #f3f0e9 100%);
  border-block-end: 1px solid rgba(18, 18, 18, 0.06);
}

[dir=rtl] .public-page-header {
  background: radial-gradient(circle at 18% 18%, rgba(184, 145, 47, 0.09), transparent 34%), linear-gradient(225deg, #fbfaf7 0%, #f3f0e9 100%);
}

.public-page-header__content {
  /*max-width: 48rem;*/
}

.public-page-header h1 {
  font-size: clamp(2rem, 1.55rem + 2vw, 3.5rem);
  line-height: 1.08;
}

.public-page-header p {
  max-width: 38rem;
  margin-block-start: var(--space-2);
  color: var(--color-text-muted);
  font-size: clamp(0.875rem, 0.83rem + 0.22vw, 1.05rem);
}

[dir=rtl] .public-page-header p {
  line-height: 1.8;
}

/* Breadcrumbs.cshtml + ProductBreadcrumbs.cshtml */
.breadcrumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-block-end: var(--space-3);
}

.breadcrumbs a {
  color: var(--color-text-muted);
  transition: color var(--transition-fast);
}

.breadcrumbs a:hover {
  color: var(--color-accent);
}

.breadcrumbs__sep {
  display: inline-flex;
  width: 0.7rem;
  height: 0.7rem;
  opacity: 0.6;
}

/* EmptyState.cshtml — also reused as-is by ComingSoon.cshtml */
.empty-state {
  text-align: center;
  padding: clamp(2rem, 5vw, 4rem) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  color: var(--color-text-muted);
  background: var(--color-bg-alt);
}

.empty-state h3 {
  margin-block-end: var(--space-2);
  color: var(--color-heading);
}

/* LoadingState.cshtml — shown by default while a page waits on Firebase auth resolution + an
   API call (order history/details), so the real end state never flashes a wrong intermediate one. */
.loading-state {
  padding: var(--space-5);
}

/* ====================================================================== */
/* Gift option panel — Product Details, near the product actions. Height  */
/* is never fixed (the collapsed state hides the form entirely via        */
/* [hidden], not max-height/overflow tricks that could clip Arabic text). */
/* GiftOptionPanel.cshtml                                                 */
/* ====================================================================== */
.gift-panel {
  /* No margin-block-start here — this only ever sits inside product.cshtml's .stack, whose own
     flex `gap` (1rem) already provides the space before it; the two used to stack to 2rem. */
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-alt);
  overflow: hidden;
}

.gift-panel.is-active {
  border-color: var(--color-accent);
}

.gift-panel__toggle {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  background: none;
  border: none;
  text-align: start;
  cursor: pointer;
}

.gift-panel__icon {
  display: inline-flex;
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-accent);
}

.gift-panel__icon svg {
  width: 1.1rem;
  height: 1.1rem;
}

.gift-panel__copy {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  gap: 2px;
  min-width: 0;
}

.gift-panel__title {
  font-size: var(--text-sm);
  font-weight: 600;
}

.gift-panel__desc {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.gift-panel__action {
  flex: 0 0 auto;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-accent);
  white-space: nowrap;
}

.gift-panel__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: 0 var(--space-4) var(--space-4);
}

.gift-panel__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-sm);
}

.gift-panel__field input,
.gift-panel__field textarea {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  font: inherit;
  color: var(--color-text);
}

.gift-panel__field textarea {
  resize: vertical;
  min-height: 4.5rem;
}

@media (max-width: 767px) {
  /* Same >=16px mobile-zoom-prevention rule as .form-field above — the gift-recipient field
     inherits font-size from its wrapping label (14px) via `font: inherit`, so the override has
     to target the input/textarea themselves, not the label text. */
  .gift-panel__field input,
  .gift-panel__field textarea {
    font-size: var(--text-base);
  }
}
.gift-panel__count {
  align-self: flex-end;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.gift-panel__checkbox {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.gift-panel__error {
  font-size: var(--text-sm);
  color: var(--color-danger, #a3352a);
}

.gift-panel__hint {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* CartDrawer.cshtml — slide-in cart drawer, same pattern as the mobile nav drawer */
.cart-drawer {
  position: fixed;
  inset: 0;
  z-index: 600;
  visibility: hidden;
  pointer-events: none;
}

.cart-drawer[data-open=true] {
  visibility: visible;
  pointer-events: auto;
}

.cart-drawer__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(18, 18, 18, 0.4);
  opacity: 0;
  transition: opacity var(--transition-base);
}

.cart-drawer[data-open=true] .cart-drawer__backdrop {
  opacity: 1;
}

/* inset-inline-end: 0 (logical, not physical) is what makes this drawer open from the correct
   side in both directions with zero extra work — it resolves to the physical right in LTR and
   the physical left in RTL automatically, matching wherever the header's cart-icon trigger
   itself sits (plain unoverridden flexbox, so it already moves to the same side). Do not add a
   [dir="rtl"] override for the anchor here again — a previous version of this rule did exactly
   that (re-pinning to inset-inline-start, which resolves to the *same* physical right in RTL as
   the base rule's inset-inline-end), which silently cancelled the free mirroring and left the
   panel opening from the wrong side, out of sync with its own trigger button. */
.cart-drawer__panel {
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  width: min(26rem, 90vw);
  background: var(--color-surface);
  padding: var(--space-5);
  transform: translateX(100%);
  transition: transform var(--transition-slow);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* translateX is a physical transform (always moves along the literal +X screen axis, never
   direction-aware) — the sign must flip here because the anchor above resolves to the opposite
   physical side under RTL: translateX(100%) pushes a right-anchored (LTR) panel further right,
   off-screen; translateX(-100%) pushes the now-left-anchored (RTL) panel further left, off-screen.
   This is the only RTL override this component needs. */
[dir=rtl] .cart-drawer__panel {
  transform: translateX(-100%);
}

.cart-drawer[data-open=true] .cart-drawer__panel {
  transform: translateX(0);
}

@media (prefers-reduced-motion: reduce) {
  .cart-drawer__panel,
  .cart-drawer__backdrop {
    transition: none;
  }
}
/* SuccessPanel.cshtml */
.success-panel {
  text-align: center;
  max-width: 32rem;
  margin-inline: auto;
  padding: var(--space-8) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);
}

/* Entrance animation — only plays once the panel is actually shown (not hidden), since
   [hidden] elements never run animations, so this can't fire on page load before success. */
[data-checkout-confirmation]:not([hidden]) {
  animation: success-panel-in var(--transition-slow) var(--ease-luxury) forwards;
}

@keyframes success-panel-in {
  from {
    opacity: 0;
    transform: translateY(18px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}
.success-panel__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4rem;
  height: 4rem;
  margin-inline: auto;
  margin-block-end: var(--space-5);
  border-radius: 50%;
  background: var(--color-bg-warm);
  color: var(--color-accent-deep);
  animation: success-icon-pop 480ms var(--ease-luxury) 120ms both;
}

@keyframes success-icon-pop {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
@media (prefers-reduced-motion: reduce) {
  [data-checkout-confirmation]:not([hidden]),
  .success-panel__icon {
    animation: none;
  }
}
.success-panel__icon svg {
  width: 1.75rem;
  height: 1.75rem;
}

.success-panel h2 {
  margin-block-end: var(--space-2);
}

.success-panel > p {
  color: var(--color-text-muted);
}

.success-panel__order {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-5) var(--space-6);
  text-align: start;
  margin-block: var(--space-6);
  padding-block: var(--space-5);
  padding-inline: var(--space-2);
  border-block: 1px solid var(--color-border);
}

.success-panel__order-item--wide {
  grid-column: 1/-1;
}

.success-panel__order dt {
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-block-end: var(--space-1);
}

[dir=rtl] .success-panel__order dt {
  letter-spacing: 0;
  text-transform: none;
}

.success-panel__order dd {
  font-weight: 700;
  font-size: var(--text-md);
  color: var(--color-heading);
}

/* ====================================================================== */
/* Welcome free-delivery popup — a new-visitor-only promotional modal.     */
/* Every visibility rule (auth resolved, not signed in, not dismissed,     */
/* not on checkout) lives in welcome-popup.js; this file only owns how it  */
/* looks/animates once JS decides to open it via [data-open="true"].      */
/* WelcomePopup.cshtml                                                    */
/* ====================================================================== */
.welcome-popup {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  visibility: hidden;
  pointer-events: none;
}

.welcome-popup[hidden] {
  display: none;
}

.welcome-popup[data-open=true] {
  visibility: visible;
  pointer-events: auto;
}

.welcome-popup__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(18, 18, 18, 0.55);
  opacity: 0;
  transition: opacity var(--transition-base);
}

.welcome-popup[data-open=true] .welcome-popup__backdrop {
  opacity: 1;
}

.welcome-popup__panel {
  position: relative;
  z-index: 1;
  width: min(100%, 26rem);
  /* Bounded, never full-viewport — a tall image + long translated copy still scrolls inside the
     panel instead of ever overflowing the viewport on a short mobile screen. Capped well under
     the previous 88vh/40rem on purpose: a shorter panel leaves more of the page implied behind
     it and gives a faster first view, rather than the popup dominating nearly the whole screen
     on a first visit. */
  max-height: min(70vh, 30rem);
  overflow-y: auto;
  background: var(--color-surface);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.welcome-popup[data-open=true] .welcome-popup__panel {
  opacity: 1;
  transform: translateY(0);
}

.welcome-popup__close {
  position: absolute;
  inset-block-start: var(--space-3);
  inset-inline-end: var(--space-3);
  z-index: 2;
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
}

/* Sits over the media when one is configured (readable close button on any photo), and over the
   plain card background otherwise — the same fixed position works for both. */
.welcome-popup__media + .welcome-popup__close,
.welcome-popup__close {
  color: var(--color-heading);
}

.welcome-popup__media {
  /* Fixed, never ratio-derived (no aspect-ratio) — bounded ceiling so a portrait source image
     never dominates the panel, and `cover` (not `contain`) so a wide source image never
     letterboxes; either way nothing is ever stretched out of its own proportions. Shorter than
     before so the media image doesn't eat most of the now-more-compact panel height on its own. */
  height: min(26vh, 9rem);
  overflow: hidden;
  background: var(--color-surface-alt);
}

.welcome-popup__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.welcome-popup__media--fallback {
  display: flex;
  align-items: center;
  justify-content: center;
}

.welcome-popup__media--fallback svg {
  width: 3rem;
  height: 3rem;
  color: var(--color-text-muted);
}

.welcome-popup__body {
  padding: var(--space-5) var(--space-5) var(--space-4);
  text-align: center;
}

.welcome-popup__body h2 {
  font-size: var(--text-xl);
  margin-block-end: var(--space-2);
}

.welcome-popup__desc {
  color: var(--color-text);
}

.welcome-popup__requirement {
  /* --color-text-muted measured at ~4.11:1 here against white — below WCAG's 4.5:1 minimum. */
  color: #5c5c5c;
  font-size: var(--text-sm);
  margin-block: var(--space-3) var(--space-4);
}

.welcome-popup__whatsapp {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-4);
  /* --color-whatsapp (#25d366, the brand's own official green) measured at only ~1.98:1 as body
     text on white — nowhere near WCAG's 4.5:1. This deeper shade keeps it unmistakably
     "WhatsApp green" while clearing the minimum (5.43:1); the icon below inherits it via
     currentColor too, so icon and label stay the same shade as each other. */
  color: #0d7a3d;
  font-size: var(--text-sm);
  font-weight: 600;
}

.welcome-popup__whatsapp svg {
  width: var(--icon-sm);
  height: var(--icon-sm);
}

@media (prefers-reduced-motion: reduce) {
  .welcome-popup__backdrop,
  .welcome-popup__panel {
    transition: opacity var(--transition-fast);
  }
  .welcome-popup__panel {
    transform: none;
  }
}
/* ====================================================================== */
/* Brand cards — BrandCard.cshtml. Logos keep their own natural            */
/* proportions (a single max-height/max-width constraint, never a forced   */
/* aspect-ratio) so a wide wordmark and a square emblem both sit centered   */
/* without cropping or distortion.                                         */
/* ====================================================================== */
.brand-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4);
  text-align: center;
  text-decoration: none;
  color: inherit;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  transition: transform var(--transition-base), box-shadow var(--transition-base), border-color var(--transition-base);
}

.brand-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-md);
  border-color: var(--color-accent);
}

.brand-card:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
}

.brand-card__media {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 5.5rem;
}

.brand-card__logo {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

.brand-card__media--text {
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
}

.brand-card__name-visual {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--color-heading);
}

.brand-card__name {
  font-family: var(--font-heading);
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--color-heading);
}

.brand-card__desc {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ShopByBrand.cshtml (standalone /brands/ page) — Home\ShopByBrandSection.cshtml reuses the
   same tabs/panel markup; its own visual overrides are homepage-scoped and live in
   pages/_homePage.scss's ".homepage .home-section--brand" block. */
/* Homepage "Shop by Brand" — tabs above, one panel (carousel) per brand below. Real <button>
   elements drive the tabs (never a non-interactive div), matching the tablist/tabpanel ARIA
   pattern; only the active panel is ever visible or in the tab order. */
.brand-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: center;
  margin-block-end: var(--space-6);
}

.brand-tab {
  font-family: var(--font-heading);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  background: none;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: var(--space-2) var(--space-5);
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast), background-color var(--transition-fast);
}

.brand-tab:hover {
  color: var(--color-heading);
  border-color: var(--color-accent);
}

.brand-tab:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.brand-tab.is-active {
  color: var(--color-white);
  background: var(--color-accent);
  border-color: var(--color-accent);
}

.brand-panel {
  display: none;
}

/* Subtle fade-in on activation (see brand-shop.js's double-rAF class add) rather than an instant
   swap — never a cross-fade against the previous panel (it's already display:none by the time
   this one shows, so there's nothing behind it to flash white against), and no layout shift since
   every panel shares the same .brand-panel__media/.brand-panel__feature box sizing regardless of
   which brand is active. Reuses the shared --transition-base/--ease-premium tokens, so it already
   collapses to 1ms under prefers-reduced-motion via tokens.css, with no extra rule needed here. */
.brand-panel.is-active {
  display: block;
  opacity: 0;
  transition: opacity var(--transition-base) var(--ease-premium);
}

.brand-panel.is-active.brand-panel--visible {
  opacity: 1;
}

/* A brand with no assigned products yet renders EmptyState here instead of ProductCarousel (see
   ShopByBrand.cshtml) — matching that carousel row's own real rendered height (measured directly
   at each breakpoint: ~23rem/26rem/29rem/32rem, including its arrow-controls row, card image,
   name/price/button) keeps switching between a stocked brand and an unstocked one from visibly
   collapsing the section. */
.brand-panel .empty-state {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 23rem;
}

@media (min-width: 640px) {
  .brand-panel .empty-state {
    min-height: 26rem;
  }
}
@media (min-width: 900px) {
  .brand-panel .empty-state {
    min-height: 29rem;
  }
}
@media (min-width: 1100px) {
  .brand-panel .empty-state {
    min-height: 32rem;
  }
}
/* ====================================================================== */
/* Frequently Bought Together — Product Details upsell rail. Reuses the    */
/* site's own .carousel/.carousel-block sizing rules verbatim (1/2/3/4     */
/* complete cards per breakpoint, no clipped peeking card) — only the      */
/* card internals and the summary/CTA row below are new.                   */
/* FbtCard.cshtml + FrequentlyBoughtTogether.cshtml                        */
/* ====================================================================== */
.fbt-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  transition: border-color var(--transition-base);
}

.fbt-card:has([data-fbt-checkbox]:checked) {
  border-color: var(--color-accent);
}

.fbt-card--out-of-stock {
  opacity: 0.7;
}

.fbt-card__select {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-start: var(--space-2);
  z-index: 2;
  display: inline-flex;
  width: 1.4rem;
  height: 1.4rem;
  background: var(--color-white);
  border-radius: var(--radius-sm);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

.fbt-card__select input {
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.fbt-card__select input:disabled {
  cursor: default;
}

.fbt-card__media {
  position: relative;
  display: block;
  height: 10rem;
  background: var(--color-bg-alt);
}

.fbt-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.fbt-card__badges {
  position: absolute;
  inset-block-end: var(--space-2);
  inset-inline-start: var(--space-2);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
}

.fbt-card__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  padding: var(--space-3);
}

.fbt-card__name {
  margin: 0 0 var(--space-1);
  font-size: var(--text-sm);
  line-height: 1.3;
  min-block-size: 2.6em;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
}

.fbt-card__name a {
  color: var(--color-heading);
}

.fbt-card__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: auto;
  font-size: var(--text-sm);
  font-weight: 600;
}

.fbt-card__price .was-price {
  font-weight: 400;
  color: var(--color-text-muted);
  text-decoration: line-through;
}

.fbt__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-5);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-border);
}

.fbt__estimate {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.fbt__estimate-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.fbt__estimate-value {
  font-size: var(--text-lg);
  font-weight: 700;
}

.fbt__note {
  margin-block-start: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.fbt__feedback {
  margin-block-start: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-success, var(--color-accent));
}

.fbt__feedback.is-error {
  color: var(--color-danger, #a3352a);
}

/* =========================
   Pages — one file per real Views/*.cshtml, imported unconditionally so
   every page has a home for future page-specific CSS even where none
   exists yet. Shared styling stays in base/components/partials; these
   files never duplicate it.
========================= */
/* homePage.cshtml — every section here is rendered only by the Views/Partials/Dkhon/Home/*.cshtml
   sub-partials (Hero, Featured, ShopByBrand, ShopByGender, Editorial, NewArrivals, BestSellers,
   TrustSectionHome, Instagram, FinalCta, BrandStory, Reviews), each confirmed via the real
   markup to render only on the homepage — so their styling lives here rather than under
   components/ or partials/, even where they build on a shared component (e.g. .trust-item,
   .brand-panel, .feature-card, .editorial-row are shared bases; the ".homepage ..." overrides
   below are what's actually homepage-exclusive). */
/* ====================================================================== */
/* Scroll storytelling section (Home\BrandStorySection.cshtml)            */
/*                                                                        */
/* Default (always applied, no JS required): a plain, fully visible       */
/* stacked sequence of image+text pairs — a completely normal section,    */
/* safe for no-JS, JS-failure, reduced-motion, and mobile.                */
/*                                                                        */
/* Enhanced (only under html.js, desktop, and NOT reduced-motion — see    */
/* the gated block below): the section becomes a tall (~300vh) pinned     */
/* scrollytelling composition — one sticky visual, scenes cross-fading    */
/* in as data-story-scene[data-story-index] elements receive .is-active   */
/* from story.js.                                                        */
/* ====================================================================== */
.story {
  background: var(--color-page);
}

.story__sticky {
  display: grid;
  gap: var(--space-6);
  padding-block: var(--space-8);
}

.story__media {
  position: relative;
  width: 100%;
  max-width: 100%;
  /* Grid-blowout insurance: a grid/flex item's automatic minimum size otherwise defaults to
     its content's intrinsic size, which — combined with a large real product photo — can force
     this item wider/taller than intended. */
  min-width: 0;
  min-height: 0;
  /* Explicit height, never ratio-derived: capped both by viewport (no oversized sticky visual
     on small screens) and by an absolute rem ceiling (no runaway growth on tall viewports). */
  height: min(60vh, 28rem);
  overflow: hidden;
  background: var(--color-surface-alt);
}

.story__image {
  display: none;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  object-fit: cover;
}

.story__image.is-active {
  display: block;
}

/* Tablet: proportionally smaller and centered rather than full-bleed — the 2-column grid
   below only kicks in at 900px, so between 768-899px the media would otherwise stack full-width
   at the mobile size, dominating the section. */
@media (min-width: 768px) and (max-width: 899px) {
  .story__media {
    max-width: 26rem;
    height: min(50vh, 24rem);
    margin-inline: auto;
  }
}
.story__text-track {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
  padding-inline: var(--container-padding);
}

.story__scene h2 {
  font-size: var(--text-2xl);
  margin-block: var(--space-2) var(--space-3);
}

.story__scene p {
  color: var(--color-text-muted);
  max-width: 34rem;
}

@media (min-width: 900px) {
  .story__sticky {
    grid-template-columns: 1fr 1fr;
    align-items: center;
  }
  .story__media {
    height: min(70vh, 38rem);
  }
}
/* Enhanced pinned/cross-fade experience — desktop only (mobile keeps the light stacked
   sequence above, deliberately, per "lighter animations on mobile"), and only once html.js
   confirms script execution actually succeeded. */
@media (min-width: 900px) and (not (prefers-reduced-motion: reduce)) {
  /* Must never be shorter than the pinned child's own 100vh (.story__sticky below) — with a
     low real scene count (e.g. 1-2 products with real imagery), calc(count * 62vh) alone can
     compute shorter than 100vh, leaving the sticky child's painted box taller than the section
     that's supposed to contain it, so it visually spills into whatever section follows. */
  :where(html.js) .story[data-story-enhanced=true] {
    height: max(100vh, var(--story-scene-count, 5) * 62vh);
    position: relative;
  }
  :where(html.js) .story[data-story-enhanced=true] .story__sticky {
    position: sticky;
    top: 0;
    height: 100vh;
    padding-block: 0;
  }
  /* Pinned state fills its 100vh sticky row exactly — an explicit 100% height, so the media
     can never grow past the viewport regardless of image dimensions or window shape. */
  :where(html.js) .story[data-story-enhanced=true] .story__media {
    height: 100%;
    max-height: 100%;
  }
  :where(html.js) .story[data-story-enhanced=true] .story__image {
    display: block;
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity var(--transition-slow);
  }
  :where(html.js) .story[data-story-enhanced=true] .story__image.is-active {
    opacity: 1;
  }
  :where(html.js) .story[data-story-enhanced=true] .story__text-track {
    position: relative;
    height: 100%;
  }
  :where(html.js) .story[data-story-enhanced=true] .story__scene {
    position: absolute;
    inset-inline: var(--container-padding);
    top: 50%;
    transform: translateY(calc(-50% + 16px));
    opacity: 0;
    transition: opacity var(--transition-slow), transform var(--transition-slow);
    pointer-events: none;
  }
  :where(html.js) .story[data-story-enhanced=true] .story__scene.is-active {
    opacity: 1;
    transform: translateY(-50%);
    pointer-events: auto;
  }
}
/* ====================================================================== */
/* Instagram-style grid (Home\InstagramSection.cshtml)                    */
/* ====================================================================== */
.social-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
}

@media (min-width: 768px) {
  .social-grid {
    grid-template-columns: repeat(6, 1fr);
  }
}
.social-grid__item {
  position: relative;
  height: 7rem;
  overflow: hidden;
  border-radius: var(--radius-sm);
}

/* Heights step with the grid's own 3-col → 6-col change so tiles stay near-square-looking
   without deriving height from width: at 768 a 6-col tile is ~107px wide, at 1100+ ~166-195px. */
@media (min-width: 768px) {
  .social-grid__item {
    height: 8.5rem;
  }
}
@media (min-width: 1100px) {
  .social-grid__item {
    height: 12rem;
  }
}
.social-grid__item::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(38, 35, 32, 0.1);
  opacity: 0;
  transition: opacity var(--transition-base);
}

.social-grid__item:hover::after {
  opacity: 1;
}

.social-grid__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow);
}

.social-grid__item:hover img {
  transform: scale(1.08);
}

/* ====================================================================== */
/* Editorial alternating row — Home\EditorialSection.cshtml and           */
/* Home\NewArrivalsSection.cshtml's spotlight row.                        */
/* ====================================================================== */
.editorial-row {
  display: grid;
  gap: var(--space-6);
  align-items: center;
  margin-block-end: var(--space-8);
}

.editorial-row:last-child {
  margin-block-end: 0;
}

@media (min-width: 900px) {
  .editorial-row {
    grid-template-columns: 1fr 1fr;
  }
  /* Alternate media/content sides using `order` (not `direction`, which would
     also flip text flow) so this stays correct on both LTR and RTL pages. */
  .editorial-row--reverse .editorial-row__media {
    order: 2;
  }
  .editorial-row--reverse .editorial-row__content {
    order: 1;
  }
}
.editorial-row__media {
  position: relative;
  height: 16rem;
  border-radius: var(--radius-xl);
  overflow: hidden;
}

@media (min-width: 640px) {
  .editorial-row__media {
    height: 20rem;
  }
}
@media (min-width: 900px) {
  .editorial-row__media {
    height: 26rem;
  }
}
.editorial-row__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow);
}

.editorial-row:hover .editorial-row__media img {
  transform: scale(1.05);
}

/* ====================================================================== */
/* Homepage — bright editorial system; all visual overrides are page-scoped. */
/* ====================================================================== */
.homepage {
  --home-gold: #b8912f;
  --home-ivory: #faf8f3;
  --home-soft: #f5f2ec;
  --home-ink: #171717;
  --home-muted: rgba(23, 23, 23, 0.62);
  color: var(--home-ink);
  background: var(--color-white);
}

body:has(.homepage) {
  overflow-x: clip;
}

/* Checkout is intentionally outside this storefront-density pass. */
body:has([data-checkout-page]) {
  --storefront-section-space: clamp(2rem, 1.15rem + 4vw, 4.5rem);
  --storefront-section-space-tight: clamp(1.5rem, 1rem + 2vw, 3rem);
  --storefront-heading-gap: clamp(1.25rem, 2.5vw, 2.25rem);
  --storefront-content-gap: clamp(1rem, 2.4vw, 2rem);
  --storefront-grid-gap: clamp(0.75rem, 1.5vw, 1.25rem);
  --storefront-card-gap: clamp(0.4rem, 0.8vw, 0.65rem);
  --storefront-page-header-space: clamp(1.25rem, 0.6rem + 3vw, 3.5rem);
}

.homepage .home-section {
  background: var(--color-white);
}

.homepage .home-section--brand,
.homepage .home-section--trust {
  background: var(--home-ivory);
}

.homepage .home-section__heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-5);
}

.homepage .home-section__heading .section-heading {
  margin-block-end: var(--space-6);
}

.homepage .home-section__heading h2,
.homepage .home-section--instagram h2 {
  color: var(--home-ink);
  font-size: clamp(2.2rem, 1.75rem + 2vw, 4.5rem);
  font-weight: 500;
  letter-spacing: -0.025em;
}
@media (max-width: 479px) {
  .homepage .home-section__heading h2,
  .homepage .home-section--instagram h2 {
    font-size: 30px;
  }
}

[dir=rtl] .homepage .home-section__heading h2,
[dir=rtl] .homepage .home-section--instagram h2 {
  letter-spacing: 0;
  line-height: 1.3;
}

.homepage .home-section__cta {
  margin-block-start: var(--space-6);
  text-align: center;
}

.homepage .home-section__cta--social {
  margin-block-start: var(--space-5);
}

.homepage .home-cta,
.site-footer--home .btn {
  border-radius: 0;
}

/* Homepage cards retain the same premium frame and equal-height structure as every listing. */
.homepage .home-section--featured .product-card,
.homepage .home-section--best-sellers .product-card,
.homepage .home-section--brand .product-card,
.homepage .home-section--new-arrivals .product-card {
  border: 1px solid rgba(18, 18, 18, 0.14);
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: 0 1px 0 rgba(18, 18, 18, 0.03);
}

.homepage .home-section--featured .product-card:hover,
.homepage .home-section--best-sellers .product-card:hover,
.homepage .home-section--brand .product-card:hover,
.homepage .home-section--new-arrivals .product-card:hover {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-sm);
  transform: translateY(-2px);
}

.homepage .home-section .product-card__media {
  height: 11rem;
  background: var(--color-white);
}

.homepage .home-section .product-card__body {
  padding: var(--space-2) var(--space-3);
}

.homepage .home-section .product-card__actions {
  padding: 0 var(--space-3) var(--space-2);
}

.homepage .home-section .product-card__name {
  font-size: clamp(1rem, 0.94rem + 0.28vw, 1.2rem);
}

.homepage .home-section .product-card__price {
  font-size: clamp(0.875rem, 0.84rem + 0.18vw, 1rem);
}

.homepage .home-section .product-card__wishlist {
  border: 0;
}

.homepage .home-section .carousel__control {
  width: 2.5rem;
  height: 2.5rem;
  border: 0;
  border-radius: 0;
  background: var(--home-soft);
}

@media (min-width: 640px) {
  .homepage .home-section .product-card__media {
    height: 14rem;
  }
}
@media (min-width: 1100px) {
  .homepage .home-section .product-card__media {
    height: 17rem;
  }
}
/* Dynamic brand banner/logo feature — compacted: this section previously ran a full
   --storefront-section-space top/bottom (up to 3.5rem each) plus a 3.5rem gap before its
   carousel and a 30rem-tall desktop media box, tall enough to fill most of a laptop viewport by
   itself. Tighter spacing/height here keeps a real, premium-sized image without the excess. */
.homepage .home-section--brand {
  padding-block: clamp(1.25rem, 0.9rem + 1.4vw, 2.25rem);
}

.homepage .home-section--brand .brand-tabs {
  justify-content: flex-start;
  gap: var(--space-3);
  margin-block-end: var(--space-4);
}

/* A bare color-only change on hover/active read as plain text, not an interactive control (no
   border, no background, no shape) -- this restores the base .brand-tab component's own chip
   treatment (see _shop-by-brand.scss), just re-tinted for this section's ivory background, so an
   inactive brand name still reads as a tappable pill and the active one is unmistakably selected. */
.homepage .home-section--brand .brand-tab {
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  color: var(--home-muted);
  background: transparent;
  font-size: var(--text-md);
}
@media (max-width: 497px) {
  .homepage .home-section--brand .brand-tab {
    font-size: 15px;
  }
}

/* --home-gold (#b8912f) measured at only ~2.77:1 as this tab's own text color against the
section's off-white background — well below WCAG's 4.5:1 text minimum. A deeper shade, scoped
to just this text usage (the icon-only usages of --home-gold elsewhere are graphical, not text,
and aren't held to the same ratio), clears it (5.68:1) while staying clearly gold; the same
shade is dark enough to also clear 4.5:1 as a background for white active-tab text. */
.homepage .home-section--brand .brand-tab:hover {
  color: #7a5f1f;
  border-color: #7a5f1f;
}

.homepage .home-section--brand .brand-tab.is-active {
  color: var(--color-white);
  background: #7a5f1f;
  border-color: #7a5f1f;
}

.homepage .brand-panel__feature {
  display: grid;
  gap: var(--space-5);
  align-items: center;
  margin-block-end: var(--space-4);
}

/* No plate/card behind the logo -- transparent so a logo with a transparent source image sits
directly on the section's own ivory background, not inside a separate white box. A banner
photo (.brand-panel__banner, object-fit: cover) still fills this box edge-to-edge on its own
when a brand has one, so this only affects the no-banner/logo-only state. Height trimmed from
the previous 15rem/20rem now that there's no white box needing visual weight to fill -- a
smaller box around a now-larger logo reads as intentional, not empty. */
.homepage .brand-panel__media {
  display: flex;
  height: 11rem;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: transparent;
}

.homepage .brand-panel__banner {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* object-fit: contain + no background painted behind the <img> itself here means the logo's own
transparency always shows through cleanly; this only bounds how much of the
.brand-panel__media box it's allowed to fill, never adds a plate/background behind it. */
.homepage .brand-panel__logo {
  display: block;
  width: auto;
  max-width: 13rem;
  max-height: 5.5rem;
  object-fit: contain;
}

@media (min-width: 900px) {
  .homepage .brand-panel__media {
    height: 14rem;
  }
  .homepage .brand-panel__logo {
    max-width: 16rem;
    max-height: 7rem;
  }
}
/* Must come after the min-width:900px block above -- same specificity as the plain
.brand-panel__logo rules it shares this element with (both classes sit on the same <img>), so
whichever rule is textually last wins the max-width/max-height conflict. Placed last here on
purpose so the feature variant's percentage sizing always wins over the fixed-rem desktop bump,
at every breakpoint, instead of silently reverting to the small fixed size above ~900px. */
.homepage .brand-panel__logo--feature {
  max-width: 82%;
  max-height: 78%;
}

/* This section's own explicit request: no visible focus ring anywhere in it, including the
circular-looking ring a rectangular :focus-visible outline forms around a round brand emblem
logo inside .brand-panel__media. Scoped to just this section -- the site-wide :focus-visible
rule (base/_reset.scss) is untouched everywhere else, including every other homepage section. */
.homepage .home-section--brand a:focus-visible,
.homepage .home-section--brand button:focus-visible {
  outline: none;
}

.homepage .brand-panel__copy h3 {
  margin-block: var(--space-3) var(--space-2);
  font-size: clamp(2rem, 1.5rem + 2vw, 3.5rem);
}

.homepage .brand-panel__copy p {
  max-width: 30rem;
  color: var(--home-muted);
}

.homepage .brand-panel__link {
  display: inline-block;
  margin-block-start: var(--space-4);
  color: var(--home-ink);
  font-weight: 600;
  text-decoration: underline rgba(184, 145, 47, 0.55);
  text-underline-offset: 0.35em;
}

@media (min-width: 900px) {
  /* Media box height for this breakpoint lives with the rest of the brand-panel__media/__logo
  sizing rules above (single source of truth -- this file previously set height:20rem again
  here too, a second, later-in-cascade rule silently overriding the one above it). */
  .homepage .brand-panel__feature {
    grid-template-columns: minmax(0, 1.35fr) minmax(16rem, 0.65fr);
  }
}
/* Connected gender pair with one restrained lower readability wash. */
.homepage .home-gender-grid {
  display: grid;
  gap: var(--space-4);
}

.homepage .home-gender-card {
  height: 23rem;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.homepage .home-gender-card:hover {
  box-shadow: none;
  transform: none;
}

.homepage .home-gender-card .feature-card__overlay {
  padding: var(--space-5);
  background: linear-gradient(180deg, transparent 58%, rgba(18, 18, 18, 0.5));
}

.homepage .home-gender-card .feature-card__overlay::before {
  content: none;
}

.homepage .home-gender-card h3 {
  font-size: clamp(2rem, 1.4rem + 2vw, 3.5rem);
  font-weight: 500;
}

@media (min-width: 768px) {
  .homepage .home-gender-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .homepage .home-gender-card {
    height: 35rem;
  }
}
/* Story uses existing About-page Rich Text and real catalog media. */
.homepage .home-story {
  display: grid;
  gap: var(--storefront-content-gap);
  align-items: center;
}

/* Was 26rem/36rem — object-fit:contain means any real source image narrower than this box's own
aspect ratio shows real blank --home-soft background above/below it; kept object-fit as-is
(not switching to cover, which would change how the image itself is framed/cropped) and just
reduced the box height so there's less of that background to show through. */
.homepage .home-story__media {
  height: 18rem;
  overflow: hidden;
  background: var(--home-soft);
}

.homepage .home-story__media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.homepage .home-story__content h2 {
  margin-block: var(--space-3);
  font-size: clamp(2.4rem, 1.8rem + 2vw, 4.25rem);
  font-weight: 500;
}

.homepage .home-story__richtext {
  max-width: 34rem;
  color: var(--home-muted);
}

.homepage .home-story__richtext > * + * {
  margin-block-start: var(--space-3);
}

@media (min-width: 900px) {
  .homepage .home-story {
    grid-template-columns: minmax(0, 1.15fr) minmax(20rem, 0.85fr);
    gap: var(--space-8);
  }
  .homepage .home-story__media {
    height: 24rem;
  }
}
/* Campaign is the only stronger composition and remains warm rather than dark. */
.homepage .home-section--campaign .editorial-row {
  gap: 0;
  margin: 0;
  background: var(--home-ivory);
}

.homepage .home-section--campaign .editorial-row__media {
  height: 23rem;
  border-radius: 0;
}

.homepage .home-campaign__content {
  padding: var(--space-6) var(--space-5);
}

.homepage .home-campaign__content h2 {
  margin-block: var(--space-3);
  font-size: clamp(2.4rem, 1.8rem + 2.2vw, 4.5rem);
  font-weight: 500;
}

.homepage .home-campaign__content p {
  max-width: 30rem;
  color: var(--home-muted);
}

.homepage .home-campaign__content .home-cta {
  margin-block-start: var(--space-5);
}

@media (min-width: 900px) {
  .homepage .home-section--campaign .editorial-row__media {
    height: 28rem;
  }
  /* Was clamp(3rem, 6vw, 6rem) on every side — up to 12rem of pure padding around one eyebrow,
  one heading, one paragraph, and one button. */
  .homepage .home-campaign__content {
    padding: clamp(1.5rem, 3vw, 3rem);
  }
}
/* Trust, social and closing sections share the same calm visual language. */
.homepage .home-section--trust {
  border-block-start: 1px solid rgba(23, 23, 23, 0.06);
}

.homepage .home-section--trust .trust-item {
  padding: var(--space-3);
  border: 0;
  border-radius: 0;
  background: transparent;
}

.homepage .home-section--trust .trust-item:hover {
  border-color: transparent;
  box-shadow: none;
  transform: none;
}

.homepage .home-section--trust .trust-item__icon {
  width: auto;
  height: auto;
  margin-block-end: var(--space-3);
  border-radius: 0;
  color: var(--home-gold);
  background: transparent;
}

.homepage .home-section--trust .trust-item p {
  font-size: var(--text-sm);
  font-weight: 500;
}

.homepage .home-section--instagram .social-grid {
  gap: var(--space-3);
}

.homepage .home-section--instagram .social-grid__item {
  height: 8.5rem;
  border-radius: 0;
}

.homepage .home-section--instagram .social-grid__item::after {
  background: rgba(255, 255, 255, 0.12);
}

.homepage .home-final-cta {
  /* Was min-height:24rem + --space-8 (5rem) padding on top of the outer .section's own
  padding-block (up to 3.5rem) — for an eyebrow, one heading, one line, and one button, that
  stacked to roughly 17rem of combined padding/floor. Kept substantial, just not empty. */
  min-height: 14rem;
  padding: var(--space-6) var(--space-5);
  border: 0;
  border-radius: 0;
  background: var(--home-ivory);
}

.homepage .home-final-cta h2 {
  font-size: clamp(2.5rem, 1.8rem + 2.5vw, 4.75rem);
  font-weight: 500;
}

.homepage .home-final-cta .btn {
  color: var(--color-white);
  background: var(--home-ink);
  border-color: var(--home-ink);
}

@media (max-width: 767px) {
  /* These five clamp()s all still sit on their vw-driven curve at 768px+ (each one's preferred
  value already exceeds its own floor by then, confirmed rule-by-rule), so lowering just the
  floor only changes anything below that — at 320-375px each one currently renders at its floor
  and is visibly larger/taller than every other homepage section heading (which use the
  ~2.2rem-floored .home-section__heading h2 clamp below), causing 2-3 line wraps ("The Art of
  Gifting", "Discover Your Signature Scent") or an oversized single word ("Men's Perfumes",
  brand names) for no reason beyond an unmatched floor. */
  .homepage .home-story__content h2 {
    font-size: clamp(2.1rem, 1.8rem + 2vw, 4.25rem);
  }
  .homepage .home-campaign__content h2 {
    font-size: clamp(2rem, 1.8rem + 2.2vw, 4.5rem);
  }
  .homepage .home-final-cta h2 {
    font-size: clamp(2.1rem, 1.8rem + 2.5vw, 4.75rem);
  }
  .homepage .home-gender-card h3 {
    font-size: clamp(1.75rem, 1.4rem + 2vw, 3.5rem);
  }
  .homepage .brand-panel__copy h3 {
    font-size: clamp(1.75rem, 1.5rem + 2vw, 3.5rem);
  }
}
@media (min-width: 768px) {
  .homepage .home-section--instagram .social-grid__item {
    height: 9rem;
  }
}
@media (min-width: 1100px) {
  .homepage .home-section--instagram .social-grid__item {
    height: 12rem;
  }
}
/* Complete footer, light and homepage-specific. */
.site-footer--home {
  border-block-start: 0;
  background: #faf8f3;
}

.site-footer--home .site-footer__strip {
  border-block-end-color: rgba(23, 23, 23, 0.07);
}

.site-footer--home .site-footer__grid {
  gap: var(--storefront-content-gap);
}

.site-footer--home .site-footer__socials,
.site-footer--home .site-footer__newsletter-title {
  margin-block-start: var(--space-5);
}

.site-footer--home .site-footer__newsletter-copy {
  font-size: var(--text-sm);
}

.site-footer--home .social-icons a,
.site-footer--home .site-footer__bottom {
  border: 0;
}

@media (min-width: 768px) and (max-width: 1099px) {
  .site-footer--home .site-footer__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 1100px) {
  .site-footer--home .site-footer__grid {
    grid-template-columns: 1.3fr 1fr 1fr 1.2fr;
  }
}
/* Homepage motion is progressive enhancement: server-rendered content is visible until the
home-only controller successfully prepares every target and adds body.home-motion-enabled. */
body.home-motion-enabled .homepage [data-home-motion],
body.home-motion-enabled .site-footer--home [data-home-motion] {
  opacity: 0;
  transform: translate3d(0, var(--home-motion-distance, 40px), 0);
  transition: opacity var(--home-motion-duration, 680ms) var(--ease-premium) var(--home-motion-delay, 0ms), transform var(--home-motion-duration, 680ms) var(--ease-premium) var(--home-motion-delay, 0ms), clip-path var(--home-motion-duration, 680ms) var(--ease-premium) var(--home-motion-delay, 0ms);
  will-change: opacity, transform;
}

body.home-motion-enabled .homepage [data-home-motion=left],
body.home-motion-enabled .site-footer--home [data-home-motion=left] {
  transform: translate3d(calc(var(--home-motion-distance, 68px) * -1), var(--home-motion-y, 0px), 0);
}

body.home-motion-enabled .homepage [data-home-motion=right],
body.home-motion-enabled .homepage [data-home-motion=literal-right],
body.home-motion-enabled .site-footer--home [data-home-motion=right] {
  transform: translate3d(var(--home-motion-distance, 68px), var(--home-motion-y, 0px), 0);
}

body.home-motion-enabled .homepage [data-home-motion=scale] {
  transform: scale(var(--home-motion-scale, 1.035));
}

body.home-motion-enabled .homepage [data-home-motion=rank] {
  transform: translate3d(0, var(--home-motion-distance, 40px), 0) scale(var(--home-motion-scale, 1.035));
}

body.home-motion-enabled .homepage [data-home-motion=campaign] {
  opacity: 0.2;
  clip-path: inset(8% 0 8% 0);
  transform: scale(var(--home-motion-scale, 1.035));
}

body.home-motion-enabled .homepage [data-home-motion].is-in-view,
body.home-motion-enabled .site-footer--home [data-home-motion].is-in-view {
  opacity: 1;
  clip-path: inset(0);
  transform: translate3d(0, 0, 0) scale(1);
  will-change: auto;
}

@media (max-width: 767px) {
  body.home-motion-enabled .homepage [data-home-motion=literal-right] {
    --home-motion-distance: 48px !important;
  }
  body.home-motion-enabled .homepage [data-home-motion=campaign] {
    clip-path: none;
    transform: translate3d(0, 32px, 0);
  }
}
@media (prefers-reduced-motion: reduce) {
  body.home-motion-enabled .homepage [data-home-motion],
  body.home-motion-enabled .site-footer--home [data-home-motion] {
    opacity: 1;
    clip-path: none;
    transform: none;
    transition: none;
  }
}
/* Views/shopPage.cshtml
   No page-specific styles today — this page is built entirely from shared
   components/partials (components/_shop-toolbar.scss for the filter/sort
   toolbar, components/_feature-card.scss and base/_useful_classes.scss's
   .grid for the collection tiles, components/_product-card.scss for
   listings). Kept as its own file so future Shop-only CSS has a home
   without hunting through shared component files. */
/* Views/productCategory.cshtml
   No page-specific styles today — the filter/sort toolbar
   (components/_shop-toolbar.scss) and product grid (components/_product-card.scss)
   are the same shared listing components used on Shop/Collection/Brand pages.
   Kept as its own file so future Category-listing-only CSS has a home
   without hunting through shared component files. */
/* Views/collection.cshtml
   No page-specific styles today — the filter/sort toolbar
   (components/_shop-toolbar.scss) and product grid (components/_product-card.scss)
   are the same shared listing components used on Shop/Category/Brand pages.
   Kept as its own file so future Collection-listing-only CSS has a home
   without hunting through shared component files. */
/* product.cshtml (PDP) — quantity stepper row, action-button stack, and the brand caption link.
   Verified via the real markup: not reused by giftSet.cshtml or any other page (which share the
   gallery/sticky-cta shell instead — see components/_product-gallery.scss). */
/* Product Details quantity row + the Add to Cart / Buy Now action stack below it. Both action
   buttons stay full-width at every breakpoint (not just mobile) — the wishlist icon button is the
   one exception, sized to its own natural square footprint and centered rather than stretched. */
.product-qty {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.product-qty__label {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.product-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
}

.product-actions .icon-btn {
  align-self: center;
}

/* Product Details' brand line — a small caption above the product name, linking to that brand's
   own page. */
.product-brand-link {
  display: inline-block;
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  text-decoration: none;
  margin-block-end: var(--space-1);
}

.product-brand-link:hover {
  color: var(--color-accent);
  text-decoration: underline;
}

/* productBrand.cshtml — single brand page's intro block (logo + optional website link), above
   its own product grid. Verified via the real markup: not used anywhere else. */
.brand-intro {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-5);
  margin-block-end: var(--space-6);
}

/* Sized down from the previous 12rem/4rem cap, which rendered close to full-width on a small
   screen -- aspect ratio is untouched (object-fit: contain, no source media change), just a
   smaller, consistent footprint with real breathing room around it at every size. */
.brand-intro__logo {
  display: block;
  max-width: 7rem;
  max-height: 2.75rem;
  width: auto;
  height: auto;
  object-fit: contain;
}

@media (min-width: 640px) {
  .brand-intro__logo {
    max-width: 9rem;
    max-height: 3.25rem;
  }
}
.brand-intro__website {
  font-size: var(--text-sm);
  color: var(--color-accent);
  text-decoration: underline;
}

/* Views/brandsPage.cshtml
   No page-specific styles today — the listing grid (base/_useful_classes.scss's
   .grid) and each tile (partials/_brand-card.scss) are shared components.
   Kept as its own file so future Brands-listing-only CSS has a home without
   hunting through shared component files. */
/* Views/giftSet.cshtml
   No page-specific styles today — the packshot gallery and mobile sticky CTA
   (components/_product-gallery.scss) are the same shared shell used by
   product.cshtml (the PDP). Kept as its own file so future Gift-Set-only CSS
   has a home without hunting through shared component files. */
/* Views/cartPage.cshtml
   No page-specific styles today — the empty-cart state (partials/_empty-state.scss),
   line items and qty stepper (components/_cart.scss), and order-summary sidebar
   (components/_order-summary.scss: .checkout-grid, .cart-summary,
   .checkout-summary__*, .coupon-field*) are all shared with checkoutPage.cshtml
   and orderDetailsPage.cshtml. Kept as its own file so future Cart-only CSS has
   a home without hunting through shared component files. */
/* Views/checkoutPage.cshtml
   No page-specific styles today — the two-column layout and order-summary
   sidebar (components/_order-summary.scss: .checkout-grid, .cart-summary,
   .checkout-summary__*, .coupon-field*) are shared with cartPage.cshtml and
   orderDetailsPage.cshtml; the contact/address form (components/_forms.scss)
   and payment method / CliQ panel (components/_payment-methods.scss) are
   shared with the cart drawer and cart page. Kept as its own file so future
   Checkout-only CSS has a home without hunting through shared component files. */
/* accountPage.cshtml — auth panel (guest state) + account dashboard (signed-in state).
   Verified via the real markup: not reused by any other page. */
/* ====================================================================== */
/* Auth panel (account page, guest state)                                 */
/* ====================================================================== */
.auth-grid {
  display: grid;
  gap: var(--storefront-content-gap);
  max-width: 26rem;
  margin-inline: auto;
}

.auth-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: clamp(1rem, 3vw, 1.75rem);
  background: var(--color-surface);
}

.auth-card h2 {
  font-size: var(--text-lg);
  margin-block-end: var(--space-1);
}

.auth-card > p {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin-block-end: var(--space-4);
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-block: var(--space-4);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

[dir=rtl] .auth-divider {
  letter-spacing: 0;
  text-transform: none;
}

.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border);
}

.auth-switch {
  margin-block-start: var(--space-5);
  text-align: center;
  font-size: var(--text-sm);
}

.auth-switch a,
.auth-switch button {
  color: var(--color-accent);
  font-weight: 600;
}

/* ====================================================================== */
/* Account dashboard                                                       */
/* ====================================================================== */
.account-grid {
  display: grid;
  gap: var(--space-6);
}

@media (min-width: 900px) {
  .account-grid {
    grid-template-columns: 20rem 1fr;
  }
}
.account-summary {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: clamp(1rem, 3vw, 1.75rem);
  background: var(--color-surface);
  height: fit-content;
}

.account-summary dl {
  display: grid;
  gap: var(--space-3);
  margin-block: var(--space-4) var(--space-5);
}

.account-summary dt {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}

[dir=rtl] .account-summary dt {
  letter-spacing: 0;
  text-transform: none;
}

.account-summary dd {
  font-weight: 600;
  color: var(--color-heading);
}

.account-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.account-links a,
.account-links button {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  font-weight: 600;
  text-align: start;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.account-links a:hover,
.account-links button:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* Views/wishlistPage.cshtml
   No page-specific styles today — the empty-wishlist state
   (partials/_empty-state.scss) and product grid/cards (components/_product-card.scss,
   components/_badges.scss) are the same shared listing components used on
   Shop/Category/Collection. Kept as its own file so future Wishlist-only CSS
   has a home without hunting through shared component files. */
/* Views/orderHistoryPage.cshtml
   No page-specific styles today — the loading/skeleton state
   (partials/_loading-state.scss, components/_loading.scss), empty state
   (partials/_empty-state.scss), and each order row (rendered client-side by
   orders.js — see components/_order-row.scss) are shared with
   orderDetailsPage.cshtml. Kept as its own file so future Order-History-only
   CSS has a home without hunting through shared component files. */
/* Views/orderDetailsPage.cshtml
   No page-specific styles today — the loading/empty states
   (partials/_loading-state.scss, partials/_empty-state.scss), the order
   summary block (components/_order-summary.scss: .checkout-grid,
   .cart-summary, .checkout-summary__*), and the spec-list key/value rows
   (components/_notes-spec.scss) are all shared with cartPage.cshtml,
   checkoutPage.cshtml, and product.cshtml. Kept as its own file so future
   Order-Details-only CSS has a home without hunting through shared
   component files. */
/* Views/paymentSuccessPage.cshtml
   No page-specific styles — this template only renders the shared
   Dkhon/ComingSoon partial (partials/_empty-state.scss's .empty-state).
   Kept as its own file for architectural completeness. */
/* Views/paymentFailedPage.cshtml
   No page-specific styles — this template only renders the shared
   Dkhon/ComingSoon partial (partials/_empty-state.scss's .empty-state).
   Kept as its own file for architectural completeness. */
/* Views/aboutPage.cshtml
   No page-specific styles today — the rich-text body (components/_rich-text.scss),
   trust grid (components/_trust.scss), and spotlight banner
   (components/_banners.scss) are shared with policyPage.cshtml and
   contactPage.cshtml. Kept as its own file so future About-only CSS has a
   home without hunting through shared component files. */
/* Views/contactPage.cshtml
   No page-specific styles today besides the FAQ section below -- the layout
   utilities (.two-col, .stack, .eyebrow in base/), social icons
   (partials/_footer.scss), and spotlight banner (components/_banners.scss)
   are all shared elsewhere. Kept as its own file so future Contact-only CSS
   has a home without hunting through shared component files. */
/* ====================================================================== */
/* FAQ section -- scoped to ".faq-section" (Contact page only) so none of  */
/* this touches product.cshtml's specs/delivery-returns accordion, which   */
/* shares the same base .accordion-item component from                    */
/* components/_accordion.scss. That shared file is left untouched.        */
/* ====================================================================== */
.faq-section h2 {
  text-align: center;
  margin-block-end: var(--space-6);
}

/* The list's own 48rem max-width (set inline in the markup) isn't centered
   on its own -- it has no inline margin, so it hugs the start edge of the
   already-centered, much wider ".container" around it. This is what reads
   as "not centered/balanced" on a real page. */
.faq-section__list {
  margin-inline: auto;
}

.faq-section .accordion-item {
  border-block-end: 1px solid var(--color-border);
  transition: background-color var(--transition-fast);
}

.faq-section .accordion-item:first-child {
  border-block-start: 1px solid var(--color-border);
}

.faq-section .accordion-item[data-open=true] {
  background: var(--color-bg-warm);
}

.faq-section .accordion-item__trigger {
  gap: var(--space-4);
  padding: var(--space-4) var(--space-3);
  font-size: var(--text-md);
  cursor: pointer;
}

.faq-section .accordion-item__trigger span:first-child {
  line-height: 1.4;
}

/* Fixed-size circular chip so the icon never shifts or changes size between
   states -- the shared component's own icon has no intrinsic box, which is
   part of why it read as inconsistent. Drawn with two pseudo-element bars
   (a proper plus/minus toggle) instead of the shared component's "rotate
   the whole plus 45deg" trick, which turns a plus into an "x" rather than a
   minus -- this cancels that rotation and replaces it with a clean
   horizontal-bar-only "minus" on open. */
.faq-section .accordion-item__icon {
  position: relative;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  transform: none;
}

.faq-section .accordion-item__icon svg {
  display: none;
}

.faq-section .accordion-item__icon::before,
.faq-section .accordion-item__icon::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  background: currentColor;
  transition: transform var(--transition-base), opacity var(--transition-base);
}

.faq-section .accordion-item__icon::before {
  width: 0.6rem;
  height: 2px;
}

.faq-section .accordion-item__icon::after {
  width: 2px;
  height: 0.6rem;
}

.faq-section .accordion-item[data-open=true] .accordion-item__icon {
  transform: none;
  border-color: var(--color-border-strong);
}

.faq-section .accordion-item[data-open=true] .accordion-item__icon::after {
  transform: scaleY(0);
  opacity: 0;
}

.faq-section .accordion-item__panel {
  padding-inline: var(--space-3);
  transition: grid-template-rows var(--transition-base), opacity var(--transition-base);
  opacity: 0;
}

.faq-section .accordion-item[data-open=true] .accordion-item__panel {
  opacity: 1;
}

.faq-section .accordion-item__panel p {
  line-height: 1.7;
}

/* The shared component's own unconditional padding-block-end on the panel's
   last child (components/_accordion.scss) persists even at the closed
   "0fr" grid-row height -- padding is part of the box model and isn't
   zeroed out by grid-template-rows alone (min-height:0 lets the CONTENT
   shrink to 0, not the padding sitting around it), so a visible sliver of
   the answer's padding+line-box was still rendering through overflow:hidden
   while "closed". Same failure mode already fixed elsewhere in this
   codebase for the mobile-nav disclosure panel: zero every panel child's
   vertical padding by default, in this ".faq-section" scope only, and
   apply it back solely once the item is actually open. */
.faq-section .accordion-item__panel > *:last-child {
  padding-block-end: 0;
}

.faq-section .accordion-item[data-open=true] .accordion-item__panel > *:last-child {
  padding-block-end: var(--space-4);
}

.faq-section .accordion-item[data-open=true] .accordion-item__panel p {
  padding-block-start: var(--space-1);
}

@media (prefers-reduced-motion: reduce) {
  .faq-section .accordion-item__icon::before,
  .faq-section .accordion-item__icon::after {
    transition: none;
  }
  .faq-section .accordion-item__panel {
    transition: none;
  }
}
/* Views/policyPage.cshtml
   No page-specific styles today — the article header, table of contents, and
   rich-text body (all in components/_rich-text.scss: .policy-article*,
   .policy-toc*, .policy-body) are shared with aboutPage.cshtml. Kept as its
   own file so future Policy-only CSS has a home without hunting through
   shared component files. */
/* Views/websiteRoot.cshtml
   No page-specific styles — this template only ever renders in the rare
   fallback case where no homePage child content exists yet (it otherwise
   redirects straight to homePage.cshtml), and its fallback markup uses only
   base/_useful_classes.scss's .container/.section and base/_typography.scss's
   plain h1/p. Kept as its own file for architectural completeness. */
/* =========================
   RTL overrides — imported LAST, after every layer above (including
   pages/), to exactly preserve this project's original cascade order:
   rtl.css was the final <link> in _layout.cshtml, loaded after
   components.css. Moving these rules any earlier risks a silent RTL
   regression if a later-loaded rule happens to target the same selector.
========================= */
/* Directional icon flips that logical CSS properties cannot express on their own.
   Everything else (scroll reveal, hover lift, accordion, mobile nav stagger) is
   built with CSS logical properties (inset-inline-*, margin-inline-*,
   border-inline-*) and direction-agnostic transforms (translateY/scale), so it
   flips correctly under [dir="rtl"] without needing overrides here.

   Deliberately kept as its own file, imported LAST in styles.scss (after every
   other layer, including pages/) — matching this stylesheet's original load
   order (rtl.css was the last <link> in _layout.cshtml, after components.css).
   That exact position in the cascade is what these overrides currently rely on
   to win; moving them earlier risks a silent RTL regression if a later-loaded
   rule happens to target the same selector. */
[dir=rtl] .icon-flip {
  transform: scaleX(-1);
}

[dir=rtl] .hero__ctas,
[dir=rtl] .cta-banner__actions,
[dir=rtl] .product-card__actions {
  direction: rtl;
}

[dir=rtl] .product-card__price {
  direction: rtl;
  justify-content: flex-start;
}

[dir=rtl] .spec-list__row,
[dir=rtl] .site-footer__bottom {
  direction: rtl;
}
