/* ==========================================================================
   White Moon Jewels — base elements + layout primitives
   --------------------------------------------------------------------------
   Depends on wm-tokens.css. Everything here is a primitive that more than one
   section needs; anything used by exactly one section lives in that section's
   own {% stylesheet %} block so it ships only on pages that render it.

   Naming is `wm-<block>__<element>` / `--<modifier>`. It is deliberately our
   own — no class name is taken from the reference theme.
   ========================================================================== */

/* ---- element base ------------------------------------------------------ */

*,
*::before,
*::after { box-sizing: border-box; }

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

/* ---- page scrollbar ----------------------------------------------------
   Matched to the reference: a 5px rail, light track, mid-grey thumb, always
   visible (the reference only fades its thumb behind an opt-in class it does
   not use). Styled on the root directly rather than behind a utility class, so
   no class name is borrowed and every page gets it.

   Nothing here reserves layout space — the 5px rail is already inside
   documentElement.clientWidth, exactly as measured on the reference. */
html { scroll-behavior: smooth; }

html::-webkit-scrollbar { width: var(--wm-scrollbar-w); }
html::-webkit-scrollbar-track { background: var(--wm-scrollbar-track); }
html::-webkit-scrollbar-thumb { background: var(--wm-scrollbar-thumb); }

/* Firefox only. `scrollbar-width` is NOT Firefox-only any more — Chrome honours
   it too, and when it is set Chrome IGNORES ::-webkit-scrollbar entirely. Setting
   it unscoped forced the rail to thin's 10px instead of the reference's 5px
   (measured: scrollbarPx 10 vs 5), which then shifted the whole page grid ~3px.
   Gating on the absence of the webkit pseudo-element keeps Chrome on the 5px
   rail while Firefox still gets a thin, correctly-coloured bar. */
@supports not selector(::-webkit-scrollbar) {
  html {
    scrollbar-width: thin;
    scrollbar-color: var(--wm-scrollbar-thumb) var(--wm-scrollbar-track);
  }
}

/* The reference sets this on <body>; it stops a full-bleed section that
   overshoots the viewport from producing a horizontal bar. */
body { overflow-x: hidden; }

/* Smooth scrolling is a vestibular trigger, so it is opt-out here even though
   the reference leaves it on unconditionally. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--wm-bg);
  color: var(--wm-text);
  font-family: var(--wm-font-body);
  font-weight: var(--wm-weight-body);
  font-size: var(--wm-text-base);
  line-height: var(--wm-leading-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--wm-font-heading);
  font-weight: var(--wm-weight-heading);
  line-height: var(--wm-leading-heading);
  color: var(--wm-heading);
  text-transform: none;
}

h1 { font-size: var(--wm-h1); line-height: var(--wm-leading-tight); }
h2 { font-size: var(--wm-h2); }
h3 { font-size: var(--wm-h3); }
h4 { font-size: var(--wm-h4); }
h5 { font-size: var(--wm-h5); }
h6 { font-size: var(--wm-h6); }

/* The signature italic-serif emphasis inside a heading. Used as
   <h1>Diamonds <em>grown by light</em></h1> — a real <em>, so the emphasis is
   in the document, not only in the styling. */
h1 em, h2 em, h3 em,
.wm-em {
  font-style: var(--wm-heading-em-style);
  font-size: var(--wm-heading-em-size);
  font-weight: inherit;
  line-height: 1;
}

p { margin: 0 0 var(--wm-space-4); }
p:last-child { margin-bottom: 0; }

a { color: inherit; text-decoration: none; }
/* Buttons are excluded ON PURPOSE, and this is not cosmetic nit-picking.
   `a:hover` is specificity 0,1,1; `.wm-btn--solid` / `.wm-btn--quiet` are 0,1,0.
   So a plain link-colour rule OUTRANKED every button's own colour and repainted
   its label --wm-link (#956e38) on hover: the solid button went gold-text on a
   gold fill — effectively invisible — and the quiet button's label AND its
   currentColor underline both turned gold instead of staying white.

   The reference changes only the BACKGROUND on button hover and never the
   label, so excluding buttons here is what reproduces it. Doing this with
   :not() rather than re-declaring a hover colour on each modifier keeps one
   rule authoritative: a button owns its own colour, at rest and on hover. */
a:not(.wm-btn):hover { color: var(--wm-link); }

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

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

/* A visible focus ring that survives a dark background. Never removed —
   :focus-visible keeps it off mouse clicks, which is the only reason anyone
   wants it gone. */
:focus-visible {
  outline: 2px solid var(--wm-accent);
  outline-offset: 2px;
}

.wm-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-to-content {
  position: absolute; top: -100px; left: var(--wm-space-4);
  z-index: var(--wm-z-modal);
  padding: var(--wm-space-3) var(--wm-space-5);
  background: var(--wm-ink); color: #fff;
}
.skip-to-content:focus { top: var(--wm-space-4); }

/* ---- layout primitives ------------------------------------------------- */

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

/* Full-bleed media inside a container, without a wrapper element. */
.wm-container--flush { padding-inline: 0; }

/* Section headings are NOT --wm-h2.
   RESEARCH.md §1 records --h2-font-size: 2.25rem, but that is EURUS's token —
   White Moon's own layer overrides it with `.section h2`, and that is what every
   section heading on the reference actually renders at:

     .section h2 { font-size: clamp(2.4rem, 5.2vw, 4.4rem) }

   Measured: 49.712px @956 and 70.4px @1920 (capped). Against our 2.25rem (36px)
   every section heading was roughly half size. The weight/leading/tracking come
   from the reference's global h1–h5 rule; they are applied here rather than to
   the bare element so PDP and collection headings keep the existing scale.

   Sections whose heading has its own measured size (manifesto, bespoke,
   newsletter) override this from their own stylesheet, exactly as the reference
   does. */
.wm-section h2 {
  font-size: clamp(2.4rem, 5.2vw, 4.4rem);
  font-weight: 300;
  line-height: 1.04;
  letter-spacing: .004em;
}

/* ── Loading skeleton ──────────────────────────────────────────────────────
   Put `data-wm-skel` on any box that reserves space for an image before that
   image arrives. It sweeps until wm-card.js adds `.is-loaded`.

   Shared rather than copied per component on purpose: a product card and a PDP
   gallery showing two subtly different shimmers reads as two different loading
   states rather than as one house style. The gradient is the one
   wm-ring-builder.liquid already used for its tiles.

   THE SKELETON MUST SIT BEHIND THE IMAGE, never over it. Every user of this
   paints it as the container's own background, so an opaque photo covers it the
   instant it decodes. That is what makes the JS an optimisation rather than a
   dependency: if the script never runs, the sweep simply continues unseen
   beneath a loaded image, and nothing is ever hidden from the shopper.

   ONLY `background-image` IS TOUCHED, never the shorthand. Each user keeps
   whatever background-COLOUR it wants and this composes on top: the gallery
   stage rests on bone, while the ring builder's shape tiles rest on nothing at
   all — they are transparent PNGs drawn with object-fit:contain, so a bone
   square behind them would be a visible grey block on a white panel. Using
   `background:` here would have imposed one resting colour on every user. */
[data-wm-skel] {
  background-image: linear-gradient(100deg,
                      var(--wm-bone, #f4f3f0) 30%,
                      var(--wm-paper, #fff) 50%,
                      var(--wm-bone, #f4f3f0) 70%);
  background-size: 200% 100%;
  animation: wm-skel 1.2s linear infinite;
}
/* Stopping matters at grid scale — two dozen cards each compositing an infinite
   gradient is real work for something nobody can see any more. */
[data-wm-skel].is-loaded {
  background-image: none;
  animation: none;
}
@keyframes wm-skel { to { background-position: -200% 0; } }

/* A shimmer that cannot be stopped is worse than none for anyone who asked the
   OS to stop moving things. The reserved space is unaffected. */
@media (prefers-reduced-motion: reduce) {
  [data-wm-skel] { animation: none; background-image: none; }
}

.wm-section { padding-block: var(--wm-section-y); }
.wm-section--tight { padding-block: calc(var(--wm-section-y) / 2); }
.wm-section--flush { padding-block: 0; }

/* ── PDP editorial stack: one boundary between bands, not two ──────────────
   Every .wm-section pays its own vertical padding, so two stacked bands each
   contribute their full share and the gap between them comes out at DOUBLE
   what either one asked for. On the homepage that is fine — those bands are
   full-bleed set pieces that want the air. Under a PDP it is not: specs,
   highlights, the 4Cs and the related-products row are a continuous editorial
   run, and at --wm-section-y (96px at desktop) the space between two headings
   reached ~192px. The page read as mostly empty scroll.

   Measured on the reference band PDP at 1440, its bands run 30–60px a side,
   and the second of two consecutive education blocks drops its top padding to
   ZERO — the block above already opened the gap, so the pair share it. These
   rules reproduce that.

   Kept here rather than in each section's own stylesheet because a rule about
   what happens BETWEEN two sections belongs to neither of them, and because
   each section must stay correct standalone: a template that places the 4Cs
   without highlights above it still gets that band's own edge and padding.

   ORDER IS LOAD-BEARING. Both selectors below are (0,2,0), so the collapse
   rule must come second or the general one would win and re-open the gap.

   The :not() chain matters just as much. `.wm-section--specs + .wm-section` is
   (0,2,0) and beats a band's OWN `.wm-section--highlights` (0,1,0) rule, so
   without these exclusions this would silently overwrite the measured padding
   each editorial band sets for itself — which it did on the first pass, pulling
   the highlights band's 54px top down to 32px. This rule is only meant for a
   section that closes the stack WITHOUT a rhythm of its own: in practice the
   related-products carousel, which carries a bare `.wm-section` because it is
   shared with the homepage and cannot be retuned for the PDP alone. */
.wm-section--specs + .wm-section:not(.wm-section--highlights):not(.wm-section--fourcs),
.wm-section--highlights + .wm-section:not(.wm-section--specs):not(.wm-section--fourcs),
.wm-section--fourcs + .wm-section:not(.wm-section--specs):not(.wm-section--highlights) { padding-top: clamp(1.2rem, 2.2vw, 2rem); }

/* The education run proper — highlights straight into the 4Cs. The border goes
   with the padding: a hairline with no air above the heading reads as a broken
   edge rather than as a divider, and the two bands share a background anyway,
   so there is nothing for it to separate. */
.wm-section--highlights + .wm-section--fourcs { padding-top: 0; border-top: 0; }
.wm-section--bone { background: var(--wm-bone); }
.wm-section--mist { background: var(--wm-mist); }
.wm-section--paper { background: var(--wm-paper); }
.wm-section--night { background: var(--wm-night); color: #fff; }
.wm-section--night h1,
.wm-section--night h2,
.wm-section--night h3 { color: #fff; }

/* The section header pattern. Two variants exist on the reference: centred
   (title only) and a row with a trailing link ("Most loved this season" →
   "View all"). Both are this one block. */
.wm-head {
  display: flex;
  flex-direction: column;
  gap: var(--wm-space-2);
  align-items: center;
  text-align: center;
  margin-bottom: var(--wm-space-7);
}
.wm-head--row {
  flex-direction: row;
  align-items: flex-end;
  justify-content: space-between;
  text-align: left;
}
.wm-head__eyebrow {
  font-size: var(--wm-text-xs);
  letter-spacing: var(--wm-tracking-wide);
  text-transform: uppercase;
  color: var(--wm-accent);
  font-weight: 400;
}
.wm-head__link {
  font-size: var(--wm-text-sm);
  letter-spacing: .04em;
  text-transform: uppercase;
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
  white-space: nowrap;
}

@media (max-width: 768px) {
  .wm-head { margin-bottom: var(--wm-space-5); }
  .wm-head--row { align-items: center; }

  /* "View all" and friends sit in a heading row, so they are only as tall as
     their own text — measured 21px, well under a finger. The hit area is grown
     with a pseudo-element instead of padding so the underline stays tight
     against the text and the heading row does not change height. */
  .wm-head__link { position: relative; }
  .wm-head__link::after {
    content: "";
    position: absolute;
    left: 0; right: 0; top: 50%;
    height: 44px;
    transform: translateY(-50%);
  }
}

/* A responsive grid driven by a column count the section sets. Collapses on a
   ladder rather than by `auto-fill`, because the reference's column counts per
   breakpoint are deliberate (4 → 3 → 2) and auto-fill would not reproduce them. */
.wm-grid {
  display: grid;
  grid-template-columns: repeat(var(--wm-cols, 4), minmax(0, 1fr));
  gap: var(--wm-space-5);
  /* The collection and search grids are <ul>s, so without this the browser
     paints a disc marker beside every card and indents the whole grid by the
     default 40px list padding. `role="list"` on the markup keeps the list
     semantics that `list-style: none` otherwise strips in Safari/VoiceOver. */
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (max-width: 1024px) { .wm-grid { grid-template-columns: repeat(var(--wm-cols-md, 3), minmax(0, 1fr)); } }
@media (max-width: 768px)  { .wm-grid { grid-template-columns: repeat(var(--wm-cols-sm, 2), minmax(0, 1fr)); gap: var(--wm-space-4); } }

/* ---- buttons ----------------------------------------------------------- */

.wm-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--wm-space-2);
  min-height: 48px;                       /* a comfortable touch target */
  /* Measured on the reference button: 11.52px, .2em tracking, 16.8/33.6 padding, square. */
  padding: 1.05rem 2.1rem;
  /* The reference button has NO border — a transparent 1px one here made every
     solid button 2px taller and wider than the reference's 212x53. Only the
     outline variant declares one. */
  border: 0;
  border-radius: var(--wm-radius);
  font-size: .72rem;
  font-weight: 400;
  letter-spacing: .2em;
  text-transform: uppercase;
  text-align: center;
  /* Reference easing: a slow ease-out-quint on colour, a quick one on the lift.
     box-shadow is in the list because --solid carries an inset highlight. */
  transition: background .35s cubic-bezier(.23, 1, .32, 1),
              color .35s cubic-bezier(.23, 1, .32, 1),
              border-color .35s cubic-bezier(.23, 1, .32, 1),
              box-shadow .35s cubic-bezier(.23, 1, .32, 1),
              transform .16s cubic-bezier(.23, 1, .32, 1);
}

/* The inset hairline is what stops the solid button reading as a flat black
   rectangle against a photo — measured as inset 0 1px #ffffff12. */
.wm-btn--solid { background: var(--wm-ink); color: #f4f3f0; box-shadow: inset 0 1px rgb(255 255 255 / .07); }
/* Measured: the reference changes ONLY the background on hover — the label stays
   bone and the button does not lift. */
.wm-btn--solid:hover { background: var(--wm-accent); }

.wm-btn--outline { border: var(--wm-border) solid currentColor; color: var(--wm-ink); }
.wm-btn--outline:hover { background: var(--wm-ink); color: #fff; transform: translateY(-1px); }

.wm-btn--gold { background: var(--wm-accent); color: var(--wm-ink); }
.wm-btn--gold:hover { background: var(--wm-ink); color: #f4f3f0; transform: translateY(-1px); }

/* The quiet button's underline is a pseudo-element, not a border, because the
   reference ANIMATES it — and the animation runs the opposite way to the usual
   convention: the rule is full width at rest and RETRACTS to half on hover,
   from the left. Measured: transform: scaleX(.5) over .45s on --wm-ease, with
   the bar at 55% opacity so it reads as a hairline rather than a rule.
   A border-bottom cannot do this; scaleX on a positioned ::after can, and it
   animates on the compositor. */
.wm-btn--quiet {
  padding: 1.05rem .2rem;
  border: 0;
  letter-spacing: .2em;
  position: relative;
}
.wm-btn--quiet::after {
  content: "";
  position: absolute;
  left: .2rem; right: .2rem;
  bottom: .7rem;
  height: 1px;
  background: currentColor;
  opacity: .55;
  transform-origin: left;
  transition: transform .45s var(--wm-ease);
}
.wm-btn--quiet:hover::after,
.wm-btn--quiet:focus-visible::after { transform: scaleX(.5); }

.wm-btn[aria-disabled="true"],
.wm-btn:disabled { opacity: .45; pointer-events: none; }

/* ---- quantity stepper --------------------------------------------------
   A primitive, not a PDP component. Three places render it — the PDP form, the
   cart drawer line and the cart page line — but the base rules only ever lived
   in wm-main-product.liquid's scoped {% stylesheet %}, so they shipped on the
   product template and NOWHERE else. Off the PDP the stepper fell back to a raw
   <input type=number>: no border, no shared height, and the native spinner
   arrows visible next to our own - / + buttons.

   The drawer's own sheet is the tell — it declares .wm-qty--sm sizing but never
   the box it is resizing, because on the PDP that box already existed.

   The `--sm` modifier stays with each consumer; only the shape lives here. */

.wm-qty { display: inline-flex; align-items: center; border: 1px solid var(--wm-line); }
.wm-qty__btn { width: 52px; height: 54px; font-size: 18px; color: var(--wm-ink); }
.wm-qty__btn:hover { background: var(--wm-bone); }
.wm-qty__input {
  width: 50px; height: 54px;
  border: 0; border-inline: 1px solid var(--wm-line);
  text-align: center; font: inherit; color: inherit;
  background: transparent;
  -moz-appearance: textfield;
}
.wm-qty__input::-webkit-outer-spin-button,
.wm-qty__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Both cart surfaces render the compact size; the drawer used to own this rule
   alone, which meant the cart page inherited the 54px PDP box instead. */
.wm-qty--sm .wm-qty__btn { width: 30px; height: 32px; font-size: 15px; }
.wm-qty--sm .wm-qty__input { width: 34px; height: 32px; }

/* ---- scroll reveal ----------------------------------------------------- */

/* The reference animates entrances with a `.reveal` → `.in` class pair, which
   is the right shape: the transition is described in CSS and JS only toggles a
   class. Ours matches that contract so wm-reveal.js stays a dozen lines.
   `.wm-reveal` starts hidden ONLY when JS is running — see the html.no-js
   guard below, so a failed script never leaves the page blank. */
.wm-js .wm-reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--wm-dur-image) var(--wm-ease),
              transform var(--wm-dur-image) var(--wm-ease);
  will-change: opacity, transform;
}
.wm-js .wm-reveal.wm-in {
  opacity: 1;
  transform: none;
}
/* Stagger children without a per-item style attribute. */
.wm-js .wm-reveal--stagger > * {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--wm-dur-image) var(--wm-ease),
              transform var(--wm-dur-image) var(--wm-ease);
}
.wm-js .wm-reveal--stagger.wm-in > * { opacity: 1; transform: none; }
.wm-js .wm-reveal--stagger.wm-in > *:nth-child(2) { transition-delay: 70ms; }
.wm-js .wm-reveal--stagger.wm-in > *:nth-child(3) { transition-delay: 140ms; }
.wm-js .wm-reveal--stagger.wm-in > *:nth-child(4) { transition-delay: 210ms; }
.wm-js .wm-reveal--stagger.wm-in > *:nth-child(5) { transition-delay: 280ms; }
.wm-js .wm-reveal--stagger.wm-in > *:nth-child(6) { transition-delay: 350ms; }

/* ---- media ------------------------------------------------------------- */

.wm-media {
  position: relative;
  overflow: hidden;
  background: var(--wm-bone);
}
.wm-media::before {
  content: "";
  display: block;
  padding-top: var(--wm-ratio, var(--wm-card-ratio));
}
.wm-media > img,
.wm-media > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--wm-dur-image) var(--wm-ease);
}
.wm-media--zoom:hover > img { transform: scale(var(--wm-media-zoom)); }

/* An empty picker should read as intentional space, not a broken image. */
.wm-media--placeholder { background: var(--wm-mist); }

/* --------------------------------------------------------------------------
   Collapse a section whose body gated itself off.

   The platform emits the <section> wrapper — with its padding — even when the
   section renders nothing, which left a ~200px empty band above the hero on
   /pages/about once main-page hid itself there.

   NOT scoped to `.section`. It was, and that quietly missed every handle-gated
   wm-story-* section, because those wrappers carry `wm-section` instead — and
   `wm-section` is 96px of vertical padding. Once templates/page.json became the
   one shared page template holding both the About story and the contact
   section, twelve gated-off wrappers stacked ~1150px of emptiness onto
   /pages/contact between the breadcrumb and the map. Any empty section wrapper
   is a mistake regardless of which class it happens to carry.

   `:not(:has(*))` and not `:empty`: the Liquid guard leaves a newline behind,
   and a single whitespace text node defeats :empty. :has(*) asks only about
   ELEMENT children, so whitespace is ignored. If :has is unsupported the whole
   selector is dropped and the gap simply comes back — no other damage.
   -------------------------------------------------------------------------- */
.shopify-section:not(:has(*)) { display: none; padding: 0; }

/* --------------------------------------------------------------------------
   Story-page section rhythm.

   .wm-section pads BOTH edges, so two stacked ones put --wm-section-y twice
   into the same seam — 192px of empty band at desktop, which is what the long
   editorial run on /pages/about was showing between every block.

   Dropping the following section's top padding leaves exactly one interval, so
   the rhythm down the page matches the rhythm inside each section. Same shape
   as the PDP collapse above; scoped to --story so it cannot reach the homepage
   or any other stack.
   -------------------------------------------------------------------------- */
.wm-section--story + .wm-section--story { padding-top: 0; }

/* --------------------------------------------------------------------------
   Lazy-image loading state. Behaviour lives in wm-lazy.js.

   Gated on `wm-lazy-js`, which that file sets on <html>. If it fails to load,
   none of these rules match and every image renders normally — the fade must
   never be able to strand an image at opacity 0.

   Scoped to <main>: chrome (header, mega-menu, footer, cart drawer) is lazy
   too but should not fade. The shimmer sits on the PARENT via :has(), so no
   wrapper markup is needed and the placeholder is not hidden by the image's
   own opacity transition.
   -------------------------------------------------------------------------- */
@keyframes wm-shimmer { from { background-position: 150% 0; } to { background-position: -50% 0; } }

.wm-lazy-js .wm-main img[loading="lazy"] {
  opacity: 0;
  transition: opacity .55s var(--wm-ease);
}
.wm-lazy-js .wm-main img[loading="lazy"].wm-loaded { opacity: 1; }

.wm-lazy-js .wm-main *:has(> img[loading="lazy"]:not(.wm-loaded)) {
  background-image: linear-gradient(100deg,
    var(--wm-bone) 34%, var(--wm-mist) 50%, var(--wm-bone) 66%);
  background-size: 300% 100%;
  background-repeat: no-repeat;
  animation: wm-shimmer 1.5s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .wm-lazy-js .wm-main img[loading="lazy"] { transition: none; }
  .wm-lazy-js .wm-main *:has(> img[loading="lazy"]:not(.wm-loaded)) {
    animation: none; background-image: none; background-color: var(--wm-bone);
  }
}
