/* ==========================================================================
   Maison Orris — cinematic scroll story

   The layer rules below are deliberately the reference page's rules: same
   z-order, same anchoring, same transform origins, same media-query
   breakpoints. Only the plates and the palette are ours. Reproducing the
   grammar is the point — a stack of good pictures does not become a place
   unless it is sliced and driven the way a real depth stack is.

   Nothing animates on its own: every value is a custom property written from
   one rAF loop, so there are no CSS transitions to fight mid-scrub.
   ========================================================================== */

:root {
  /* ---- palette ---- */
  --paper: #f6efe4;
  --ink: #17120e;
  --shadow: rgba(24, 16, 8, 0.34);
  /* Cool haze over a warm subject. A warm veil over warm limestone collapses
     figure and ground into beige; the reference holds warm stone against
     cyan for exactly this reason. */
  --veil-tint: 96, 150, 178;
  --stage-bg: #b9b3a6; /* sky average — shows for one frame before decode */

  /* ---- type ---- */
  --display: "Bodoni Moda", Didot, Georgia, serif;
  --ui: Jost, "Futura", "Avenir Next", system-ui, -apple-system, sans-serif;

  --rail-pad: clamp(20px, 3vw, 48px);

  /* ---- scene (written by script.js) ---- */
  --back-x: 0px;
  --back-y: 0px;
  --back-scale: 0.78;
  --ridge-y: 10vh;
  --ridge-scale: 0.8;
  --valley-y: 20vh;
  --blur-px: 0px;
  --valley-blur-px: 0px;
  --dim: 1;
  --valley-sat: 1;

  /* THE DEFOCUS IS A FRACTION OF THE FRAME, NOT A NUMBER OF PIXELS.
     14px of blur on a 1440px frame is 0.97% of the width. The same 14px on a
     393px phone is 3.6% — nearly four times as much blur, over a composition
     that is already showing far less of each plate. So the phone was not
     getting the desktop's look, it was getting a much heavier one, and paying
     for it: blur is the most expensive thing on this page per pixel, and the
     act-two plates cover 21 device-megapixels on a phone at dpr 3.

     Measured, full-page cold sweep at 4x CPU on a 393x852 frame: 6 long frames
     / 723ms with the flat radius, 4 / 549ms without any blur at all — and the
     frame-gap count over 40ms went 6 -> 2. Scaling the radius with the frame
     recovers nearly all of that and is what the composition wanted anyway. */
  --blur-k: 1;

  --veil-z: 0;
  --veil-top: 0;
  --veil-mid: 0;
  --veil-bottom: 0;

  --title-y: 0px;
  --title-scale: 1;
  --title-opacity: 1;

  /* All four are fractions of the VALLEY PLATE, measured off it:
     the pool's near water sits at 70% of the plate height on the axis, and
     the plate's own vanishing line is at 50.8% of its width. --flacon-reflect is printed
     by build_assets.py — it is the share of the flacon plate that is the
     reflection below the waterline. */
  --waterline: 30%;
  --flacon-x: 50.8%;
  /* The product was reading as a detail: at 1857x888 the flacon came out 57px
     wide, 3.1% of the frame. Everything about its footing derives from this one
     number (`bottom` subtracts the reflection fraction of it) so it can be
     raised without unplanting it. 30% against the original 21.5% is +39% of
     linear size. The button below the copy overlaps its stopper by about 45px at
     1857x888, which is fine and was checked by looking: .intro-cta is an outline
     pill with no fill, so the glass reads straight through it. */
  --flacon-h: 30%;
  --flacon-reflect: 0.3826;
  --flacon-y: 0px;
  --flacon-opacity: 1;

  --arch-x: -50%;
  --arch-y: 0px;
  --arch-bottom: -4vh;
  --arch-width: 146vh;
  --title-top: 14vh;
  --arch-scale: 1.02;

  --wall-l-x: -50%;
  --wall-r-x: -50%;
  --wall-y: 0px;
  --wall-scale: 1;

  --corners-opacity: 0;
  --corners-x: -50%;
  --corners-y: -50%;
  --corners-scale: 1.06;

  /* Both written by script.js: the arch's opening moves under width, bottom
     and a scale about 50% 48% all at once, so where the copy has to sit is
     solved per frame. A constant here is right at exactly one scroll
     position and one breakpoint.
       --intro-dy  where the hero copy's TOP goes
       --panel-dy  where act two's panel's TOP goes
     Both are the highest row of the opening that block's own width clears,
     so the flacon standing in the water below stays uncovered. */
  --intro-dy: 0px;
  --panel-dy: 0px;

  --intro-y: 0px;
  --intro-opacity: 1;

  --panel-flacon-opacity: 0;
  --panel-flacon-y: 58px;
  --panel-garden-opacity: 0;
  --panel-garden-y: calc(-50% + 58px);

  font-family: var(--ui);
  color: var(--paper);
  background: #100c09;
  letter-spacing: 0;
}

* {
  box-sizing: border-box;
}

html {
  min-height: 100%;
  background: #100c09;
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100%;
  margin: 0;
  overflow-x: clip;
  background: #100c09;
}

button {
  margin: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
}

:focus-visible {
  /* cream on the hero, ink on the paper bands — a single colour scored
     1.00:1 against one of the two surfaces whichever way it was set */
  outline: 2px solid var(--paper);
  outline-offset: 3px;
  border-radius: 4px;
}

.band :focus-visible,
.quickview :focus-visible,
.site-header.is-solid :focus-visible {
  outline-color: var(--ink);
}

.atelier-band :focus-visible,
.visit-band :focus-visible,
.site-footer :focus-visible,
.menu :focus-visible {
  outline-color: var(--paper);
}

.site-shell {
  min-height: 100vh;
}

/* ==========================================================================
   Scroll rig
   ========================================================================== */

.cinema {
  position: relative;
  /* the garden act closes at 2700; the rest is exit room before the document
     below takes over. The collection used to live inside this sticky stage
     and needed another 1000px of scroll — it is a real section now. */
  height: calc(100vh + 3000px);
}

.stage {
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 620px;
  overflow: hidden;
  isolation: isolate;
  background: var(--stage-bg);
}

.world,
.back-stack,
.ground,
.layer,
.veil,
.site-header,
.hero-title,
.intro-copy,
.story-panel {
  position: absolute;
}

.world {
  inset: 0;
  overflow: hidden;
}

/* ==========================================================================
   Scene layers
   ========================================================================== */

.layer {
  display: block;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

.sky {
  z-index: 0;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(calc(var(--blur-px) * var(--blur-k))) brightness(var(--dim));
}

.back-stack {
  z-index: 1;
  top: 0;
  bottom: 0;
  left: -3vw;
  right: -3vw;
  transform: translate3d(var(--back-x), var(--back-y), 0) scale(var(--back-scale));
  transform-origin: 50% 100%;
  will-change: transform;
}

.ridge,
.ground {
  top: auto;
  bottom: 0;
  left: 48%;
  right: auto;
  width: 112%;
}

/* Far ranges. The reference screen-blends its own far plate because that
   plate is a DARK green massif and screening it over sky gives a ghost.
   Ours is already a pale backlit limestone ridge — screened it blows out to
   near-white paper. Same intent, normal blend, slight transparency. */
.ridge {
  z-index: 1;
  height: auto;
  opacity: 0.88;
  filter: blur(calc(var(--blur-px) * var(--blur-k))) brightness(var(--dim));
  transform: translate3d(-50%, calc(var(--ridge-y) - 110px), 0) scale(var(--ridge-scale));
}

/* The valley plate's own box. Its aspect is pinned to the plate so anything
   positioned inside it in percent lands on the same spot in the photograph
   at every viewport. */
.ground {
  z-index: 3;
  aspect-ratio: 4400 / 2456;
  height: auto;
  transform: translate3d(-50%, var(--valley-y), 0) scale(0.86);
  transform-origin: 50% 100%;
}

/* The place. Its sky is keyed out, so the real sky layer shows through above
   the ridgeline — no gradient-masked plate edge to give the stack away.
   Its blur is driven by act two ONLY: act three is the garden's own beat and
   the garden has to stay sharp while the sky behind it goes soft. */
.valley {
  inset: 0;
  width: 100%;
  height: 100%;
  filter: blur(calc(var(--valley-blur-px) * var(--blur-k))) brightness(var(--dim)) saturate(var(--valley-sat));
}

.hero-title {
  z-index: 3;
  left: 50%;
  top: var(--title-top);
  width: min(94vw, 1780px);
  margin: 0;
  color: var(--paper);
  font-family: var(--display);
  /* Written by script.js, which solves it against the room between the header
     and the span's crown. A CSS clamp cannot see the crown. */
  font-size: var(--title-size, clamp(3rem, min(15vw, 24vh), 15rem));
  font-weight: 400;
  line-height: 0.82;
  letter-spacing: 0.02em;
  text-align: center;
  transform: translate3d(-50%, var(--title-y), 0) scale(var(--title-scale));
  opacity: var(--title-opacity);
  will-change: transform, opacity;
}

/* The product, standing in the water at the far end of the channel — on the
   axis the terraces converge on, at roughly the height of the hamlet behind
   it, so it reads as architecture rather than as a bottle pasted on a view.
   Everything here is a percentage of the PHOTOGRAPH, not of the viewport.

   The plate carries its own reflection, so `bottom` has to allow for it:
   the waterline sits --flacon-reflect of the way up the element. */
.flacon {
  z-index: 2;
  left: var(--flacon-x);
  bottom: calc(var(--waterline) - var(--flacon-h) * var(--flacon-reflect));
  height: var(--flacon-h);
  width: auto;
  max-width: none;
  opacity: var(--flacon-opacity);
  transform: translate3d(-50%, var(--flacon-y), 0);
  transform-origin: 50% 100%;
  will-change: opacity;
}

/* The span. Width-driven like the reference's bridge, anchored to the bottom
   so its abutments always run off the frame. Width is written by script.js,
   which needs the same number to place the copy inside the opening. */
.arch {
  z-index: 5;
  left: 50%;
  bottom: var(--arch-bottom);
  width: var(--arch-width);
  height: auto;
  transform: translate3d(var(--arch-x), var(--arch-y), 0) scale(var(--arch-scale));
  transform-origin: 50% 48%;
  will-change: transform;
}

/* Act-two near ground: two terrace corners that rise into frame once you are
   under the span, the way the reference's near cliffs do. */
.corners {
  z-index: 6;
  left: 50%;
  top: 50%;
  width: min(122vw, 2400px);
  height: auto;
  opacity: var(--corners-opacity);
  transform: translate3d(var(--corners-x), var(--corners-y), 0) scale(var(--corners-scale));
  transform-origin: 50% 48%;
  will-change: transform, opacity;
}

/* The near banks. Full-canvas plates with the opposite side cleared, so both
   sit at left:50% and part symmetrically — a half-width file would render at
   double height under width:Nvw; height:auto. */
.wall {
  z-index: 7;
  left: 50%;
  bottom: -2vh;
  width: min(118vw, 2400px);
  height: auto;
  will-change: transform;
}

.wall-left {
  transform: translate3d(var(--wall-l-x), var(--wall-y), 0) scale(var(--wall-scale));
  transform-origin: 21% 52%;
}

.wall-right {
  transform: translate3d(var(--wall-r-x), var(--wall-y), 0) scale(var(--wall-scale));
  transform-origin: 79% 52%;
}

.veil {
  inset: 0;
  z-index: var(--veil-z);
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(var(--veil-tint), var(--veil-top)) 0%,
    rgba(var(--veil-tint), var(--veil-mid)) 48%,
    rgba(var(--veil-tint), var(--veil-bottom)) 100%
  );
}

/* ==========================================================================
   Header
   ========================================================================== */

/* The hero puts sunlit limestone directly behind the header at both ends —
   brighter than anything in the reference's frame — so cream type needs a
   scrim as well as a shadow. Kept to the top band and very soft, so it never
   reads as a bar. Once the page scrolls past the hero the type flips to ink
   on paper and the scrim is swapped for a real surface. */
.site-header::before {
  content: "";
  position: absolute;
  inset: -30% 0 auto;
  height: 220%;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(14, 11, 7, 0.42), rgba(14, 11, 7, 0));
}

.site-header {
  position: fixed;
  z-index: 60;
  top: 0;
  left: 0;
  right: 0;
  display: grid;
  grid-template-columns: minmax(160px, 1fr) auto minmax(160px, 1fr);
  align-items: center;
  gap: 32px;
  padding: 28px clamp(20px, 3vw, 48px);
  color: rgba(246, 239, 228, 0.9);
  transition: padding 400ms var(--ease), color 400ms var(--ease),
    background-color 400ms var(--ease);
}

.site-logo {
  justify-self: start;
  color: rgba(246, 239, 228, 0.96);
  font-family: var(--display);
  font-size: clamp(19px, 1.5vw, 25px);
  line-height: 1;
  letter-spacing: 0.01em;
  text-decoration: none;
  white-space: nowrap;
  /* the header sits on sunlit limestone at the hero, where a soft shadow
     alone disappears — this is a tight dark core plus a wide halo */
  text-shadow: 0 1px 2px rgba(12, 8, 4, 0.62), 0 2px 22px rgba(12, 8, 4, 0.5);
}

.site-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(20px, 2.2vw, 44px);
}

.site-nav button {
  color: rgba(246, 239, 228, 0.9);
  line-height: 1;
  text-shadow: 0 1px 2px rgba(12, 8, 4, 0.62), 0 2px 22px rgba(12, 8, 4, 0.5);
  cursor: pointer;
}

.site-nav button {
  padding: 6px 2px;
  font-size: clamp(15px, 1.15vw, 19px);
  white-space: nowrap;
}

.site-nav button::after {
  content: "";
  display: block;
  height: 1px;
  margin-top: 5px;
  background: currentColor;
  transform: scaleX(var(--u, 0));
  transform-origin: 0 50%;
  transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.site-nav button:hover,
.site-nav button.is-current {
  --u: 1;
}


/* ==========================================================================
   Copy blocks
   ========================================================================== */

.intro-copy,
.story-panel {
  text-align: center;
  will-change: transform, opacity;
}

.intro-copy::before,
.story-panel::before {
  content: "";
  position: absolute;
  inset: -18% -14%;
  z-index: -1;
  background: radial-gradient(62% 60% at 50% 50%, rgba(14, 10, 4, 0.6), rgba(14, 10, 4, 0) 76%);
}

/* Sits in the UPPER part of the arch's opening — over the hamlet, the way
   the reference's does — leaving the water below it for the flacon. Anchored
   by its top, not its centre, so a copy edit cannot push it onto the stone. */
.intro-copy {
  z-index: 9;
  left: 50%;
  top: 50%;
  /* 500, not 560. The block is placed at the highest row of the opening its own
     half-width clears, so a narrower block sits higher — measured, 60px of width
     buys about 50px of height, which is what pays for the button underneath it
     without the stack reaching the flacon. */
  width: min(500px, calc(100vw - 40px));
  transform: translate3d(-50%, calc(var(--intro-dy) + var(--intro-y)), 0);
  opacity: var(--intro-opacity);
}

.intro-copy p {
  margin: 0 auto;
  max-width: 560px;
  font-size: clamp(1rem, 1.1vw, 1.16rem);
  font-weight: 500;
  line-height: 1.32;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
}

/* Says the category out loud, directly above the wordmark and over open sky.
   Tracked to sit as a label rather than as prose, and pinned to --title-top so
   it tracks the wordmark at every viewport instead of needing a breakpoint.
   The max() floor keeps it out from under the fixed header if the title has
   been pushed to its own floor on a very short frame. */
.hero-kicker {
  position: absolute;
  z-index: 4;
  left: 50%;
  top: max(clamp(96px, 12vh, 116px), calc(var(--title-top, 200px) - clamp(26px, 3.6vh, 42px)));
  /* rides the wordmark's own scroll envelope, or it would be the one thing on
     the layer stack that does not leave when act one does */
  transform: translate3d(-50%, var(--title-y), 0);
  opacity: calc(var(--title-opacity) * 0.9);
  margin: 0;
  width: max-content;
  max-width: 92vw;
  color: var(--paper);
  /* was var(--sans), which is declared nowhere: an unresolvable var() makes the
     declaration invalid at computed-value time, so font-family fell back to
     whatever it inherited. It inherited --ui, which is what this wanted, so it
     looked right and did nothing — and would have kept doing nothing if the
     inherited face ever changed. */
  font-family: var(--ui);
  font-size: clamp(0.62rem, 0.7vw, 0.76rem);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-align: center;
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.45);
}

/* The first screen had no route out of it: the only CTA was in act three, two
   thousand pixels of scroll away. pointer-events has to be re-enabled because
   .intro-copy's parent switches it off for the whole layer stack. */
.intro-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 0 18px;
  min-height: 44px;
  border: 1px solid rgba(247, 243, 236, 0.5);
  border-radius: 999px;
  color: var(--paper);
  font-family: var(--ui); /* was var(--sans) — see .hero-kicker */
  font-size: 0.86rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  text-decoration: none;
  pointer-events: auto;
  backdrop-filter: blur(2px);
  transition: background-color 420ms var(--ease), border-color 420ms var(--ease),
    transform 420ms var(--ease);
}

.intro-cta::after {
  content: "↓";
  font-size: 0.9em;
  transition: transform 420ms var(--ease);
}

.intro-cta:hover,
.intro-cta:focus-visible {
  background: rgba(247, 243, 236, 0.14);
  border-color: rgba(247, 243, 236, 0.8);
}

.intro-cta:hover::after,
.intro-cta:focus-visible::after {
  transform: translateY(3px);
}

/* RTL: the arrow leads on the other side and the tracking has to go */
[dir="rtl"] .hero-kicker {
  letter-spacing: 0;
  text-transform: none;
}

.hero-tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  /* wider than .intro-copy on purpose: the opening's clear width at the row the
     block lands on is about 546px, and the block itself is held to 500 so the
     solver puts it high enough to leave the water clear */
  width: max-content;
  max-width: min(100%, calc(100vw - 32px));
  margin-inline: auto;
  gap: 10px;
  margin-top: 24px;
}

.hero-tags span {
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  padding: 0 22px;
  color: var(--ink);
  border-radius: 999px;
  background: var(--paper);
  font-size: 0.94rem;
  font-weight: 500;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
}

.story-panel {
  z-index: 10;
  left: 50%;
  top: 50%;
  width: min(760px, calc(100vw - 42px));
  pointer-events: none;
}

.story-panel h2 {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(2.4rem, 4.6vw, 4.75rem);
  font-weight: 400;
  line-height: 1;
  text-shadow: 0 16px 38px var(--shadow);
}

.story-panel p {
  width: min(520px, 100%);
  margin: 24px auto 0;
  font-size: clamp(1rem, 1.05vw, 1.12rem);
  font-weight: 500;
  line-height: 1.34;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
}

/* Top-anchored, like the hero copy, for the same reason. Narrower than the
   garden panel so it clears a higher row of the opening — the arch's curve
   means every 20px of width costs vertical room, and this block is the
   tallest on the page. */
.panel-flacon {
  width: min(500px, calc(100vw - 84px));
  opacity: var(--panel-flacon-opacity);
  transform: translate3d(-50%, calc(var(--panel-dy) + var(--panel-flacon-y)), 0);
}

.panel-garden {
  top: 30%;
  opacity: var(--panel-garden-opacity);
  transform: translate3d(-50%, var(--panel-garden-y), 0);
}

.panel-flacon p {
  margin-top: 18px;
}

.panel-flacon .facts {
  margin-top: clamp(16px, 2.2vh, 30px);
}

.facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(24px, 5vw, 80px);
  width: min(470px, 100%);
  margin: clamp(28px, 4vh, 56px) auto 0;
}

.facts dt {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(2.4rem, 4vw, 4.2rem);
  line-height: 0.92;
  text-shadow: 0 14px 34px var(--shadow);
}

.facts dd {
  margin: 14px 0 0;
  font-size: 0.96rem;
  font-weight: 500;
  line-height: 1.24;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
}

.note-button {
  min-height: 50px;
  margin-top: 26px;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 0 28px;
  border-radius: 999px;
  color: var(--ink);
  background: var(--paper);
  font-weight: 500;
  box-shadow: 0 16px 34px rgba(0, 0, 0, 0.22);
  pointer-events: auto;
  cursor: pointer;
  transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.note-button:hover {
  transform: translateY(-2px);
}

.note-button span:first-child {
  font-size: 1.2rem;
  line-height: 1;
}


/* ==========================================================================
   Below the hero
   The hero is one photograph sliced into planes; everything under it is a
   document. The bands alternate paper / ink so the scroll has a pulse
   instead of one continuous cream field, and so the dark atelier photograph
   sits on a surface that agrees with it.
   ========================================================================== */

:root {
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --paper-2: #ece4d6;
  --ink-2: #100c09;
  --rule: rgba(23, 18, 14, 0.14);
  --rule-dark: rgba(246, 239, 228, 0.16);
  --gutter: clamp(20px, 5vw, 96px);
}

.band {
  position: relative;
  /* Skip style and layout for bands that are off-screen. Measured on a phone
     frame at 4x CPU throttle, scrolling the whole document: 846-1286ms of style
     recalc with this, 1608-1928ms without, so it earns its keep and stays.
     The hero is excluded: it is position:sticky and must keep painting.

     `auto <length>` is NOT what keeps the page from resizing, whatever it looks
     like it promises: it remembers a band's real height only once the band has
     been rendered, and that first render happens as you scroll into it. The 900px
     here is a first guess that is wrong for every band; script.js measures the
     real content heights and writes them back over this. See primeBands(). */
  content-visibility: auto;
  contain-intrinsic-size: auto 900px;
  /* the header is fixed, so an anchor jump lands under it without this */
  scroll-margin-top: clamp(76px, 11vh, 104px);
  padding: clamp(72px, 11vh, 156px) var(--gutter);
  background: var(--paper);
  color: var(--ink);
}

.band + .band {
  border-top: 1px solid var(--rule);
}

.band-head {
  max-width: 62ch;
  margin: 0 auto clamp(40px, 6vh, 80px);
  text-align: center;
}

.eyebrow {
  margin: 0 0 14px;
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(23, 18, 14, 0.72);
}

.band-title {
  margin: 0;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.2rem, 4.4vw, 3.9rem);
  line-height: 1.04;
  letter-spacing: -0.01em;
}

.band-sub {
  margin: clamp(16px, 2vh, 24px) auto 0;
  max-width: 54ch;
  font-size: clamp(0.98rem, 1.05vw, 1.1rem);
  line-height: 1.62;
  color: rgba(23, 18, 14, 0.78);
}

/* ---- dark bands ---- */

.atelier-band,
.visit-band {
  background: var(--ink);
  color: var(--paper);
  border-top-color: transparent;
}

.atelier-band .eyebrow,
.visit-band .eyebrow {
  color: rgba(246, 239, 228, 0.5);
}

.atelier-band .band-sub,
.visit-band .band-sub {
  color: rgba(246, 239, 228, 0.68);
}

.materials-band {
  background: var(--paper-2);
}

/* ==========================================================================
   Reveal
   One-shot entrances, so they are CSS transitions rather than another
   consumer of the scroll rAF loop — the loop exists to scrub the hero, and
   putting non-scrubbed work in it means it can never go idle.
   ========================================================================== */

.reveal {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  transition: opacity 900ms var(--ease), transform 900ms var(--ease);
  transition-delay: var(--d, 0ms);
}

.reveal.is-in {
  opacity: 1;
  transform: none;
}

/* .product-card sets its own `transition` shorthand further down the file, at
   the same specificity but later in source order, and a shorthand resets
   transition-delay to 0. So the collection — the one grid where a run of six
   cards most wants to arrive as a run — was the only section on the page whose
   stagger did nothing, while materials and journal staggered correctly.
   Restated at a specificity the shorthand cannot reach, and deliberately placed
   ABOVE .reveal.is-now so the fast path still wins its 0ms. */
.reveal.product-card {
  transition-delay: var(--d, 0ms);
}

/* Outrun: the element was already half way up the frame when its turn came, so
   the entrance is behind the scroll and playing it out just means the reader
   looks at empty paper. Be there instead of arriving. Short enough to finish
   inside a fast flick, long enough not to pop. */
.reveal.is-now {
  transition-duration: 200ms;
  transition-delay: 0ms;
}

/* ...but once the card is in, the delay must not follow it into a hover: a
   staggered card would otherwise wait up to 540ms before lifting. */
.product-card.is-in:hover,
.product-card.is-in:focus-visible,
.product-card.is-in:focus-within {
  transition-delay: 0ms;
}

/* ==========================================================================
   Collection
   ========================================================================== */

.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: clamp(18px, 2.2vw, 34px);
  max-width: 1500px;
  margin: 0 auto;
}

/* Six products want 3+3, not the 4+2 that auto-fit lands on at wide widths —
   a short second row reads as an afterthought rather than a set. */
@media (min-width: 960px) {
  .product-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.product-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--rule);
  border-radius: 18px;
  background: #fbf7f0;
  cursor: pointer;
  transition: opacity 900ms var(--ease), transform 620ms var(--ease),
    box-shadow 620ms var(--ease), border-color 620ms var(--ease);
}

.product-card:hover,
.product-card:focus-visible {
  transform: translate3d(0, -6px, 0);
  box-shadow: 0 26px 60px rgba(24, 16, 8, 0.16);
  border-color: rgba(23, 18, 14, 0.26);
}

.product-media {
  overflow: hidden;
  aspect-ratio: 4 / 3;
}

.product-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 900ms var(--ease);
}

.product-card:hover .product-media img,
.product-card:focus-visible .product-media img {
  transform: scale(1.055);
}

.product-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: clamp(20px, 2.4vh, 30px) clamp(18px, 1.6vw, 28px) clamp(18px, 2vh, 26px);
}

.product-body h3 {
  margin: 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.25rem, 1.5vw, 1.6rem);
  line-height: 1.1;
}

.product-rhythm {
  margin: 8px 0 0;
  font-size: 0.86rem;
  letter-spacing: 0.04em;
  color: rgba(23, 18, 14, 0.78);
}

.product-note {
  margin: 12px 0 0;
  font-size: 0.92rem;
  line-height: 1.56;
  color: rgba(23, 18, 14, 0.8);
}

.product-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin: clamp(14px, 1.8vh, 22px) 0 0;
  padding: 0;
  list-style: none;
}

.product-tags li {
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(23, 18, 14, 0.06);
  color: rgba(23, 18, 14, 0.76);
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: background-color 460ms var(--ease);
}

.product-card:hover .product-tags li {
  background: rgba(23, 18, 14, 0.1);
}

/* margin-top:auto keeps this row on one baseline across cards of unequal copy */
.product-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  margin-top: auto;
  padding-top: clamp(16px, 2vh, 24px);
}

.product-price {
  font-size: 0.86rem;
  color: rgba(23, 18, 14, 0.74);
  font-variant-numeric: tabular-nums;
}

.product-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.86rem;
  color: rgba(23, 18, 14, 0.9);
  white-space: nowrap;
}

.product-link i {
  display: inline-block;
  font-style: normal;
  transition: transform 460ms var(--ease);
}

.product-card:hover .product-link i,
.product-card:focus-visible .product-link i {
  transform: translateX(6px);
}

/* ==========================================================================
   Materials
   ========================================================================== */

.material-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  gap: clamp(20px, 2.4vw, 40px);
  max-width: 1400px;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

.material h3 {
  margin: 18px 0 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: 1.24rem;
  line-height: 1.1;
}

.material-when {
  margin: 6px 0 0;
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(23, 18, 14, 0.72);
}

.material p:last-child {
  margin: 12px 0 0;
  font-size: 0.9rem;
  line-height: 1.58;
  color: rgba(23, 18, 14, 0.8);
}

.material-shot {
  display: block;
  overflow: hidden;
  border-radius: 50%;
  aspect-ratio: 1;
}

.material-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 900ms var(--ease);
}

.material:hover .material-shot img {
  transform: scale(1.07) rotate(-1.5deg);
}

/* Five materials want five columns. auto-fit lands on 4+1 between 918 and
   1181px — a single stranded tile with 77% of the row empty, directly under
   a strip that shows all five in one line. This is the same failure the
   product grid is pinned against a few rules above. */
@media (min-width: 918px) {
  .material-row {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

/* ==========================================================================
   Atelier
   ========================================================================== */

.atelier-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 84px);
  align-items: start;
  max-width: 1500px;
  margin: 0 auto;
}

.atelier-media {
  margin: 0;
  overflow: hidden;
  border-radius: 18px;
  /* the copy column is taller than the photo on wide screens; sticking the
     photo means the numbers scroll past it rather than dragging a tall
     column of dead space alongside */
  position: sticky;
  top: clamp(88px, 14vh, 132px);
}

.atelier-media img {
  display: block;
  width: 100%;
  height: auto;
}

.atelier-band .band-title {
  max-width: 18ch;
}

.atelier-band .band-sub {
  margin-left: 0;
  margin-right: 0;
}

.steps {
  margin: clamp(36px, 5vh, 64px) 0 0;
  padding: 0;
  list-style: none;
}

.steps li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(18px, 2vw, 34px);
  padding: clamp(18px, 2.4vh, 28px) 0;
  border-top: 1px solid var(--rule-dark);
}

.steps li:last-child {
  border-bottom: 1px solid var(--rule-dark);
}

.step-n {
  font-family: var(--display);
  font-size: 0.92rem;
  letter-spacing: 0.08em;
  color: rgba(246, 239, 228, 0.68);
  font-variant-numeric: tabular-nums;
}

.steps h3 {
  margin: 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: 1.28rem;
  line-height: 1.1;
}

.steps p {
  margin: 8px 0 0;
  font-size: 0.94rem;
  line-height: 1.56;
  color: rgba(246, 239, 228, 0.68);
}

.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: clamp(20px, 3vw, 44px);
  margin: clamp(36px, 5vh, 60px) 0 0;
}

.stat-row dt {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(2rem, 3.2vw, 3rem);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.stat-row dd {
  margin: 10px 0 0;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(246, 239, 228, 0.72);
}

/* ==========================================================================
   Journal
   ========================================================================== */

.journal-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr));
  gap: clamp(22px, 2.6vw, 42px);
  max-width: 1400px;
  margin: 0 auto;
}

.journal-media {
  display: block;
  overflow: hidden;
  border-radius: 14px;
  aspect-ratio: 16 / 10;
}

.journal-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1000ms var(--ease);
}

.journal-meta {
  display: block;
  margin: 18px 0 0;
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(23, 18, 14, 0.72);
}

.journal-more {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
  font-size: 0.84rem;
  color: rgba(23, 18, 14, 0.9);
}

.journal-more i {
  font-style: normal;
  transition: transform 460ms var(--ease);
}

/* ==========================================================================
   Visit
   ========================================================================== */

.visit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: clamp(36px, 6vw, 96px);
  align-items: start;
  max-width: 1300px;
  margin: 0 auto;
}

.visit-address {
  margin: clamp(24px, 3vh, 36px) 0 0;
  font-style: normal;
  font-size: 1rem;
  line-height: 1.8;
  color: rgba(246, 239, 228, 0.78);
}

.visit-address a {
  color: inherit;
  text-underline-offset: 4px;
}

.signup h3 {
  margin: 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.5rem, 2.2vw, 2rem);
  line-height: 1.1;
}

.signup > p {
  margin: 12px 0 0;
  font-size: 0.96rem;
  line-height: 1.6;
  color: rgba(246, 239, 228, 0.66);
}

.field {
  position: relative;
  margin: clamp(24px, 3vh, 34px) 0 0;
}

.field input {
  width: 100%;
  padding: 20px 2px 12px;
  border: 0;
  border-bottom: 1px solid var(--rule-dark);
  border-radius: 0;
  background: none;
  color: var(--paper);
  font: inherit;
  font-size: 1rem;
  transition: border-color 420ms var(--ease);
}

.field input:focus {
  outline: none;
  border-bottom-color: var(--paper);
}

.field label {
  position: absolute;
  left: 2px;
  top: 20px;
  font-size: 1rem;
  color: rgba(246, 239, 228, 0.5);
  pointer-events: none;
  transform-origin: 0 0;
  transition: transform 420ms var(--ease), color 420ms var(--ease);
}

.field input:focus + label,
.field input:not(:placeholder-shown) + label {
  transform: translateY(-18px) scale(0.72);
  color: rgba(246, 239, 228, 0.72);
}

.form-msg {
  min-height: 1.2em;
  margin: 14px 0 0;
  font-size: 0.86rem;
  color: rgba(246, 239, 228, 0.72);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}

.form-msg.is-shown {
  opacity: 1;
  transform: none;
}

.form-msg.is-error {
  color: #e9b7a4;
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-height: 50px;
  margin-top: clamp(20px, 2.6vh, 30px);
  padding: 0 28px;
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: transform 320ms var(--ease), box-shadow 320ms var(--ease);
}

.btn i {
  font-style: normal;
  transition: transform 460ms var(--ease);
}

.btn:hover i {
  transform: translateX(6px);
}

.btn:hover {
  box-shadow: 0 16px 34px rgba(0, 0, 0, 0.24);
}

/* ==========================================================================
   Footer
   ========================================================================== */

.site-footer {
  padding: clamp(56px, 8vh, 104px) var(--gutter) clamp(28px, 4vh, 44px);
  background: var(--ink-2);
  color: rgba(246, 239, 228, 0.72);
}

.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(auto-fit, minmax(140px, 1fr));
  gap: clamp(28px, 4vw, 64px);
  max-width: 1400px;
  margin: 0 auto;
}

.footer-mark {
  margin: 0 0 10px;
  color: var(--paper);
  font-family: var(--display);
  font-size: clamp(1.4rem, 2vw, 1.9rem);
}

.footer-brand p:last-child {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.6;
}

.footer-col {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.footer-col h4 {
  margin: 0 0 6px;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(246, 239, 228, 0.66);
}

.footer-col a {
  width: fit-content;
  color: inherit;
  font-size: 0.92rem;
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size 460ms var(--ease), color 300ms var(--ease);
}

.footer-col a:hover {
  color: var(--paper);
  background-size: 100% 1px;
}

.footer-fine {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  max-width: 1400px;
  margin: clamp(40px, 6vh, 72px) auto 0;
  padding-top: 22px;
  border-top: 1px solid rgba(246, 239, 228, 0.12);
  font-size: 0.78rem;
  color: rgba(246, 239, 228, 0.66);
}

/* ==========================================================================
   Header states
   ========================================================================== */

.site-header::before {
  transition: opacity 400ms var(--ease);
}

.site-header.is-solid {
  padding-top: 16px;
  padding-bottom: 16px;
  color: var(--ink);
  background: rgba(246, 239, 228, 0.9);
  backdrop-filter: blur(14px) saturate(1.1);
  -webkit-backdrop-filter: blur(14px) saturate(1.1);
  border-bottom: 1px solid var(--rule);
}

.site-header.is-solid::before {
  opacity: 0;
}

.site-header.is-solid .site-logo,
.site-header.is-solid .site-nav button,
.site-header.is-solid .menu-toggle {
  color: var(--ink);
  text-shadow: none;
}

/* ==========================================================================
   Preloader
   The plates are ~4.5 MB and the hero is a composite: half-decoded, it shows
   a stack of unrelated pictures. Holding the curtain until the hero's own
   images have DECODED (not merely loaded) is the honest fix.
   ========================================================================== */

.preloader {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  background: var(--ink-2);
  transition: opacity 900ms var(--ease), visibility 900ms;
}

/* THE CURTAIN GOES ON SWALLOWING TAPS FOR NEARLY A SECOND AFTER IT CLEARS.
   `visibility` does not interpolate: across a 900ms transition to `hidden` the
   element stays `visible` for the whole duration and only flips at the end. So
   the preloader — position:fixed, inset:0, z-index:200 — is still the topmost
   hit-testable thing on the page while it fades, and nothing had set
   pointer-events on it. Measured on the deploy at 393x852: at +700ms after the
   curtain is done it is at opacity 0.00 and a tap on the logo still lands on
   the preloader; only at +950ms, when the script's own display:none fires, does
   the page start answering.

   That is a full second of a page that looks finished and ignores you, landing
   immediately after an eight-second load — which is exactly when a visitor is
   most likely to tap something and conclude the site is broken. */
.preloader.is-done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.preloader-inner {
  display: grid;
  justify-items: center;
  gap: 20px;
  width: min(320px, 72vw);
  text-align: center;
}

.preloader-mark {
  font-family: var(--display);
  font-size: clamp(1.4rem, 3vw, 2rem);
  color: var(--paper);
}

.preloader-count {
  font-size: 0.76rem;
  letter-spacing: 0.2em;
  color: rgba(246, 239, 228, 0.5);
  font-variant-numeric: tabular-nums;
}

.preloader-bar {
  display: block;
  width: 100%;
  height: 1px;
  background: rgba(246, 239, 228, 0.18);
}

.preloader-bar i {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--paper);
  transition: width 320ms var(--ease);
}

/* ==========================================================================
   Cursor
   Pointer-precision only. On touch there is no cursor to replace, and a ring
   chasing taps is noise.
   ========================================================================== */

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 190;
  display: none;
  pointer-events: none;
  will-change: transform;
}

.cursor i {
  display: block;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border: 1px solid rgba(246, 239, 228, 0.75);
  border-radius: 50%;
  transition: width 320ms var(--ease), height 320ms var(--ease),
    margin 320ms var(--ease), background-color 320ms var(--ease),
    border-color 320ms var(--ease);
}

.cursor.on-paper i {
  border-color: rgba(23, 18, 14, 0.78);
}

.cursor.is-hot i {
  width: 52px;
  height: 52px;
  margin: -26px 0 0 -26px;
  background: rgba(246, 239, 228, 0.14);
}

.cursor.on-paper.is-hot i {
  background: rgba(23, 18, 14, 0.08);
}

@media (hover: hover) and (pointer: fine) {
  .cursor {
    display: block;
  }
}

/* ==========================================================================
   Scroll cue
   ========================================================================== */

.scroll-cue {
  position: absolute;
  z-index: 12;
  left: 50%;
  bottom: 26px;
  width: 1px;
  height: 54px;
  overflow: hidden;
  background: rgba(246, 239, 228, 0.26);
  transform: translateX(-50%);
  transition: opacity 620ms var(--ease);
}

.scroll-cue span {
  position: absolute;
  inset: 0 0 auto;
  height: 40%;
  background: var(--paper);
  animation: cue 2.4s var(--ease) infinite;
}

.scroll-cue.is-gone {
  opacity: 0;
}

@keyframes cue {
  0% {
    transform: translateY(-100%);
  }
  60%,
  100% {
    transform: translateY(250%);
  }
}

/* ==========================================================================
   Quick view
   ========================================================================== */

.quickview {
  position: fixed;
  inset: 0;
  z-index: 150;
  display: grid;
  place-items: center;
  padding: clamp(16px, 4vw, 48px);
}

.quickview[hidden] {
  display: none;
}

.quickview-scrim {
  position: absolute;
  inset: 0;
  background: rgba(12, 8, 4, 0.62);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 460ms var(--ease);
}

.quickview-panel {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  width: min(1000px, 100%);
  max-height: 88vh;
  overflow: hidden auto;
  border-radius: 20px;
  background: var(--paper);
  color: var(--ink);
  opacity: 0;
  transform: translate3d(0, 22px, 0) scale(0.985);
  transition: opacity 520ms var(--ease), transform 520ms var(--ease);
}

.quickview.is-open .quickview-scrim {
  opacity: 1;
}

.quickview.is-open .quickview-panel {
  opacity: 1;
  transform: none;
}

.quickview-media {
  margin: 0;
}

.quickview-media img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 260px;
  object-fit: cover;
}

.quickview-body {
  padding: clamp(24px, 3.4vw, 48px);
}

.quickview-body h3 {
  margin: 6px 0 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.7rem, 2.6vw, 2.4rem);
  line-height: 1.06;
}

.quickview-body > p {
  margin: 14px 0 0;
  font-size: 0.98rem;
  line-height: 1.6;
  color: rgba(23, 18, 14, 0.72);
}

.pyramid {
  margin: clamp(20px, 3vh, 30px) 0 0;
  padding: 0;
}

.pyramid div {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  gap: 16px;
  padding: 12px 0;
  border-top: 1px solid var(--rule);
}

.pyramid dt {
  padding-top: 3px;
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(23, 18, 14, 0.72);
}

.pyramid dd {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.5;
}

.quickview-price {
  margin: clamp(18px, 2.4vh, 26px) 0 0;
  font-size: 0.92rem;
  color: rgba(23, 18, 14, 0.74);
  font-variant-numeric: tabular-nums;
}

.quickview .btn {
  background: var(--ink);
  color: var(--paper);
}

.quickview-close {
  position: absolute;
  z-index: 2;
  top: 12px;
  right: 14px;
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(246, 239, 228, 0.86);
  color: var(--ink);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 320ms var(--ease), transform 320ms var(--ease);
}

.quickview-close:hover {
  background: var(--paper);
  transform: rotate(90deg);
}

body.is-locked {
  overflow: hidden;
}

/* ==========================================================================
   Skip link
   ========================================================================== */

.skip-link {
  position: fixed;
  z-index: 210;
  top: 10px;
  left: 10px;
  padding: 12px 20px;
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font-size: 0.9rem;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform 320ms var(--ease);
}

.skip-link:focus-visible {
  transform: none;
}

/* ==========================================================================
   Responsive
   The hero's plates are anchored to the bottom and sized in vw, so a narrower
   viewport has to buy back width or the near banks stop reaching the span.
   The span itself is height-derived in script.js and needs no breakpoint.
   ========================================================================== */

@media (max-width: 1100px) {
  .wall {
    width: 150vw;
  }

  .corners {
    width: 160vw;
  }

  .atelier-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .atelier-media {
    position: static;
  }
}

@media (max-width: 900px) {
  /* Blur radius follows the frame's width — see --blur-k. */
  :root {
    --blur-k: 0.45;
  }

  /* A 16:9 plate sized by WIDTH is far too short on a portrait viewport — at
     390px it renders a 450px band of landscape in an 844px frame with sky
     above and below it. Size it by HEIGHT instead and let the aspect lock
     crop it horizontally; the pool then runs the full frame, which is the
     right crop for a phone anyway. Everything positioned inside .ground is a
     percentage of the plate, so nothing has to move. */
  .ground {
    width: auto;
    height: 158%;
  }


  .ridge {
    width: auto;
    height: 118%;
  }

  .quickview-panel {
    grid-template-columns: minmax(0, 1fr);
  }

  .quickview-media img {
    max-height: 34vh;
  }
}

@media (max-width: 640px) {
  :root {
    --blur-k: 0.34;
  }

  /* ------------------------------------------------------------------ products
     Six stacked cards with a 4:3 photograph put 1.53 products on a 412x800
     screen: you scroll the length of the collection to see it, and at no point
     do you see it as a set. Turn each card on its side. The photograph keeps a
     portrait crop so it still reads as a product shot, the long note is clamped
     to two lines (the whole note is one tap away in the quick view), and the
     card becomes an index row. Measured: 1.53 -> 3.4 per screen, and the
     collection band loses about 1,600px of scroll. */
  .product-card {
    flex-direction: row;
    align-items: stretch;
  }

  .product-media {
    flex: 0 0 43%;
    aspect-ratio: 3 / 4;
  }

  .product-body {
    padding: 14px 15px 13px;
  }

  .product-body h3 {
    font-size: 1.12rem;
  }

  .product-rhythm {
    margin-top: 5px;
    font-size: 0.78rem;
  }

  .product-note {
    margin-top: 7px;
    font-size: 0.82rem;
    line-height: 1.34;
    /* two lines, not a paragraph: the row is an index, not the copy */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .product-tags {
    gap: 6px;
    margin-top: 9px;
  }

  .product-tags li {
    padding: 3px 9px;
    font-size: 0.66rem;
  }

  .product-foot {
    margin-top: 10px;
    gap: 8px;
  }

  .product-price {
    font-size: 0.78rem;
  }

  .stage {
    min-height: 620px;
  }

  .wall {
    width: 210vw;
  }

  .corners {
    width: 220vw;
  }

  .hero-tags {
    gap: 8px;
    margin-top: 18px;
  }

  .hero-tags span {
    min-height: 36px;
    padding: 0 15px;
    font-size: 0.84rem;
  }

  .panel-garden {
    top: 28%;
  }

  /* inside the opening on a phone there is far less room */
  .panel-flacon h2 {
    font-size: 2rem;
  }

  .panel-flacon p {
    margin-top: 14px;
    font-size: 0.92rem;
  }

  .panel-flacon .facts {
    gap: 14px;
    margin-top: 18px;
  }

  .panel-flacon .facts dt {
    font-size: 1.9rem;
  }

  .panel-flacon .facts dd {
    margin-top: 8px;
    font-size: 0.8rem;
  }

  .scroll-cue {
    display: none;
  }
}

/* ==========================================================================
   Motion off
   Everything below the hero stops moving entirely; the hero still scrubs, it
   just stops easing and drifting (handled in script.js).
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  /* .is-now carries a duration, and it is the more specific selector, so it has
     to be named here too or the fast fade survives reduced motion. */
  .reveal,
  .reveal.is-now {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .cursor,
  .scroll-cue {
    display: none;
  }

  .product-card,
  .product-media img,
  .material-shot img,
  .journal-media img,
  .quickview-panel,
  .quickview-scrim,
  .btn,
  .btn i,
  .product-link i,
  .journal-more i {
    transition-duration: 1ms;
  }

  .scroll-cue span {
    animation: none;
  }
}

/* ==========================================================================
   Editorial composition
   The first pass laid every section out as an even grid — 3x2 cards, five
   circles, three equal journal tiles. Even grids have no argument in them:
   they say all six of these are the same, which is not what a house with a
   signature means. These give the sections a subject.
   ========================================================================== */

@media (min-width: 960px) {
  .product-grid {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    /* cards size to their own content. Stretching them to the row height is
       what opened a dead band under the lead card's tags: margin-top:auto on
       .product-foot then had a whole row's worth of slack to push into. */
    align-items: start;
  }

  .product-card {
    grid-column: span 2;
  }

  /* on the horizontal variants the footer follows the copy instead of being
     pinned to the bottom of a much taller photograph */
  .product-card.is-lead .product-foot,
  .product-card.is-wide .product-foot {
    margin-top: clamp(20px, 2.6vh, 32px);
  }

  /* the signature, given the room a signature gets */
  .product-card.is-lead {
    grid-column: span 4;
    flex-direction: row;
  }

  .product-card.is-lead .product-media {
    flex: 0 0 54%;
    aspect-ratio: 5 / 4;
  }

  .product-card.is-lead .product-body {
    justify-content: center;
    padding: clamp(26px, 3vw, 46px);
  }

  .product-card.is-lead h3 {
    font-size: clamp(1.7rem, 2.4vw, 2.5rem);
  }

  .product-card.is-lead .product-note {
    max-width: 34ch;
    font-size: 1rem;
  }

  /* and a wide closer, so the run does not just stop */
  .product-card.is-wide {
    grid-column: span 6;
    flex-direction: row-reverse;
  }

  .product-card.is-wide .product-media {
    flex: 0 0 46%;
    aspect-ratio: 16 / 9;
  }

  .product-card.is-wide .product-body {
    justify-content: center;
    padding: clamp(26px, 3vw, 52px);
  }

  .product-card.is-wide h3 {
    font-size: clamp(1.6rem, 2.2vw, 2.2rem);
  }

  .product-card.is-wide .product-note {
    max-width: 46ch;
  }
}

/* ==========================================================================
   Materials — the single exposure, then the cuts
   ========================================================================== */

.slab {
  position: relative;
  max-width: 1400px;
  margin: 0 auto clamp(34px, 5vh, 62px);
  border-radius: 16px;
  overflow: hidden;
}

.slab img {
  display: block;
  width: 100%;
  height: auto;
  /* the slab is 4:3 and mostly empty stone above and below the row */
  aspect-ratio: 40 / 11;
  object-fit: cover;
  object-position: 50% 46%;
}

/* A hairline and a numeral in ink disappear into speckled limestone — the
   stone's own texture has the same value and frequency. The marks sit on a
   dark wash instead, anchored to the bottom of the plate. */
.slab-marks {
  position: absolute;
  inset: auto 0 0;
  height: 42%;
  background: linear-gradient(rgba(14, 10, 5, 0), rgba(14, 10, 5, 0.62));
}

.slab-marks span {
  position: absolute;
  left: var(--x);
  bottom: clamp(10px, 1.6vw, 18px);
  display: grid;
  justify-items: center;
  gap: 7px;
  transform: translateX(-50%);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  color: rgba(246, 239, 228, 0.92);
}

.slab-marks i {
  display: block;
  width: 1px;
  height: clamp(16px, 2.6vw, 30px);
  background: rgba(246, 239, 228, 0.5);
}

.material h3 {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.material-n {
  font-family: var(--ui);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  color: rgba(23, 18, 14, 0.7);
}

@media (min-width: 900px) {
  /* the slab already shows each object; the tiles become small keys to it */
  .material-shot {
    width: clamp(76px, 7vw, 104px);
    border-radius: 50%;
  }
}

/* Below 900px the slab is too wide to read across, but hiding it threw away
   the strongest thing in the section and dropped mobile back to a cramped
   two-column circle catalogue. It scrolls instead, at a size where the
   objects are still legible, and the descriptions become rows. */
@media (max-width: 899px) {
  .slab {
    border-radius: 14px;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .slab::-webkit-scrollbar {
    display: none;
  }

  .slab img {
    width: 190%;
    max-width: none;
    aspect-ratio: 40 / 13;
  }

  .slab-marks {
    width: 190%;
  }
}

@media (max-width: 640px) {
  .material-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .material {
    display: grid;
    grid-template-columns: 76px minmax(0, 1fr);
    gap: 18px;
    align-items: start;
    padding: 20px 0;
    border-top: 1px solid var(--rule);
  }

  .material:last-child {
    border-bottom: 1px solid var(--rule);
  }

  .material-shot {
    width: 76px;
  }

  .material h3 {
    margin-top: 0;
  }
}

/* ==========================================================================
   Journal — one lead, two followers
   ========================================================================== */

.journal-card {
  margin: 0;
}

.journal-open {
  display: block;
  width: 100%;
  padding: 0;
  text-align: left;
  color: inherit;
  cursor: pointer;
}

.journal-h {
  display: block;
  margin: 10px 0 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.3rem, 1.7vw, 1.68rem);
  line-height: 1.14;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size 620ms var(--ease);
}

.journal-open:hover .journal-h,
.journal-open:focus-visible .journal-h {
  background-size: 100% 1px;
}

.journal-p {
  display: block;
  margin: 12px 0 0;
  font-size: 0.94rem;
  line-height: 1.6;
  color: rgba(23, 18, 14, 0.8);
}

.journal-open:hover .journal-media img,
.journal-open:focus-visible .journal-media img {
  transform: scale(1.06);
}

.journal-open:hover .journal-more i {
  transform: translateX(6px);
}

@media (min-width: 960px) {
  /* The lead has to span both ROWS as well as sit beside the followers.
     Spanning columns alone pushed follower two onto a new row and left two
     empty columns under the lead. Explicit areas, so it cannot re-flow. */
  .journal-grid {
    grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
    grid-template-areas:
      "lead one"
      "lead two";
    align-items: start;
  }

  .journal-card.is-lead {
    grid-area: lead;
  }

  .journal-card:nth-child(2) {
    grid-area: one;
  }

  .journal-card:nth-child(3) {
    grid-area: two;
  }

  .journal-card:not(.is-lead) .journal-open {
    display: grid;
    grid-template-columns: minmax(0, 0.72fr) minmax(0, 1fr);
    gap: clamp(14px, 1.4vw, 22px);
    align-items: start;
  }

  .journal-card:not(.is-lead) .journal-media {
    aspect-ratio: 1;
  }

  .journal-card:not(.is-lead) .journal-meta {
    margin-top: 0;
  }

  .journal-card.is-lead .journal-media {
    aspect-ratio: 16 / 9;
  }

  .journal-card.is-lead .journal-h {
    font-size: clamp(1.7rem, 2.4vw, 2.3rem);
  }

  .journal-card.is-lead .journal-p {
    max-width: 52ch;
    font-size: 1rem;
  }
}

.reader .quickview-panel {
  grid-template-columns: minmax(0, 1fr);
  width: min(700px, 100%);
}

.reader-body {
  padding: clamp(28px, 4vw, 56px);
}

.reader-body h3 {
  margin: 6px 0 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.8rem, 3vw, 2.6rem);
  line-height: 1.06;
}

.reader-body p {
  margin: 18px 0 0;
  font-size: 1rem;
  line-height: 1.72;
  color: rgba(23, 18, 14, 0.78);
}

/* ==========================================================================
   Mobile menu
   ========================================================================== */

.menu-toggle {
  justify-self: end;
  display: none;
  align-items: center;
  gap: 10px;
  padding: 8px 4px;
  color: inherit;
  font-size: 0.86rem;
  letter-spacing: 0.06em;
  cursor: pointer;
}

.menu-toggle-bars {
  display: grid;
  gap: 5px;
  width: 20px;
}

.menu-toggle-bars i {
  display: block;
  height: 1px;
  background: currentColor;
  transition: transform 380ms var(--ease), opacity 380ms var(--ease);
}

.menu-toggle[aria-expanded="true"] .menu-toggle-bars i:first-child {
  transform: translateY(3px) rotate(45deg);
}

.menu-toggle[aria-expanded="true"] .menu-toggle-bars i:last-child {
  transform: translateY(-3px) rotate(-45deg);
}

.menu {
  position: fixed;
  inset: 0;
  z-index: 55;
  display: grid;
  align-content: center;
  gap: 8px;
  padding: 18vh var(--gutter) 10vh;
  background: var(--ink-2);
  color: var(--paper);
  opacity: 0;
  transition: opacity 420ms var(--ease);
}

.menu[hidden] {
  display: none;
}

.menu.is-open {
  opacity: 1;
}

.menu-list {
  display: grid;
  gap: 2px;
}

.menu-list button {
  padding: 14px 0;
  border-bottom: 1px solid rgba(246, 239, 228, 0.12);
  color: var(--paper);
  font-family: var(--display);
  font-size: clamp(1.9rem, 9vw, 2.6rem);
  line-height: 1.1;
  text-align: left;
  cursor: pointer;
  /* each row arrives just after the one above it */
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity 460ms var(--ease), transform 460ms var(--ease);
  transition-delay: var(--d, 0ms);
}

.menu.is-open .menu-list button {
  opacity: 1;
  transform: none;
}

.menu-foot {
  margin: clamp(28px, 5vh, 48px) 0 0;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  color: rgba(246, 239, 228, 0.66);
  opacity: 0;
  transition: opacity 460ms var(--ease) 320ms;
}

.menu.is-open .menu-foot {
  opacity: 1;
}

@media (max-width: 780px) {
  .site-nav {
    display: none;
  }

  .menu-toggle {
    display: inline-flex;
  }

  .site-header {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 12px;
    padding: 16px var(--rail-pad);
  }
}

/* ==========================================================================
   Footer — the auto-fit track collapsed into the brand column at 390px and
   the two overlapped. Explicit tracks below.
   ========================================================================== */

@media (max-width: 860px) {
  .footer-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .footer-brand {
    grid-column: 1 / -1;
  }
}

@media (max-width: 420px) {
  .footer-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ==========================================================================
   Motion off — additions
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .menu,
  .menu-list button,
  .menu-foot,
  .journal-h,
  .menu-toggle-bars i {
    transition-duration: 1ms;
  }

  .menu-list button {
    opacity: 1;
    transform: none;
  }
}

.footer-note {
  margin: 10px 0 0;
  max-width: 26ch;
  font-size: 0.78rem;
  line-height: 1.55;
  color: rgba(246, 239, 228, 0.62);
}

.demo-note {
  margin: 0 0 clamp(18px, 2.4vh, 28px);
  padding: 14px 18px;
  max-width: 52ch;
  border-left: 2px solid rgba(246, 239, 228, 0.28);
  font-size: 0.84rem;
  line-height: 1.6;
  color: rgba(246, 239, 228, 0.72);
}

/* the card-level control is a real button now, not a styled span */
button.product-link {
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
}

/* ==========================================================================
   Arabic / RTL

   Two rules govern everything here:

   1. THE PHOTOGRAPH DOES NOT MIRROR. The hero is one real place sliced into
      depth planes — the sun is behind the ridge on the left, the distillery
      chimney is on the left bank, the near walls are the banks of that
      valley. Flipping it would put the light on the wrong side of a scene
      that was generated with a locked camera. Only the interface mirrors.

   2. ARABIC JOINS. Every letter-spacing rule the Latin type uses has to go
      to zero, or the script renders as disconnected letterforms. Same for
      text-transform: uppercase, which is meaningless in Arabic and only
      widens an already-wide script.
   ========================================================================== */

[dir="rtl"] {
  --display: "Amiri", "Noto Naskh Arabic", Georgia, serif;
  --ui: "Tajawal", "Noto Kufi Arabic", system-ui, sans-serif;
}

/* the composite keeps its own reading direction */
[dir="rtl"] .world,
[dir="rtl"] .slab {
  direction: ltr;
}

[dir="rtl"] .intro-copy,
[dir="rtl"] .story-panel {
  direction: rtl;
}

/* --- joining and tracking ------------------------------------------------ */

[dir="rtl"] h1,
[dir="rtl"] h2,
[dir="rtl"] h3,
[dir="rtl"] h4,
[dir="rtl"] p,
[dir="rtl"] a,
[dir="rtl"] li,
[dir="rtl"] dt,
[dir="rtl"] dd,
[dir="rtl"] span,
[dir="rtl"] label,
[dir="rtl"] button,
[dir="rtl"] input,
[dir="rtl"] address {
  letter-spacing: normal;
}

[dir="rtl"] .eyebrow,
[dir="rtl"] .product-tags li,
[dir="rtl"] .material-when,
[dir="rtl"] .journal-meta,
[dir="rtl"] .stat-row dd,
[dir="rtl"] .footer-col h4,
[dir="rtl"] .pyramid dt {
  text-transform: none;
  font-size: 0.78rem;
}

/* Amiri sets on a shorter body than Bodoni at the same size and needs the
   room back above and below — 0.82 clips the ascenders outright. */
/* Amiri's natural line height is close to 1.9; at 1.12 the glyphs overflowed
   the box in both directions, which is why cropping against the box could
   never be made to work. The ink measurement handles the placement now, and
   this only has to stop the descenders colliding with the copy below. */
[dir="rtl"] .hero-title {
  line-height: 1.5;
}

[dir="rtl"] .band-title,
[dir="rtl"] .story-panel h2 {
  line-height: 1.28;
}

[dir="rtl"] .product-body h3,
[dir="rtl"] .steps h3,
[dir="rtl"] .material h3,
[dir="rtl"] .journal-h,
[dir="rtl"] .signup h3,
[dir="rtl"] .quickview-body h3,
[dir="rtl"] .reader-body h3 {
  line-height: 1.4;
}

[dir="rtl"] .band-sub,
[dir="rtl"] .intro-copy p,
[dir="rtl"] .story-panel p {
  line-height: 1.85;
}

[dir="rtl"] .product-note,
[dir="rtl"] .journal-p,
[dir="rtl"] .steps p,
[dir="rtl"] .material p:last-child,
[dir="rtl"] .reader-body p {
  line-height: 1.9;
}

/* --- numerals ------------------------------------------------------------
   A year or a price inside an RTL line reverses without this. */

[dir="rtl"] .facts dt,
[dir="rtl"] .stat-row dt,
[dir="rtl"] .product-price,
[dir="rtl"] .quickview-price,
[dir="rtl"] .step-n,
[dir="rtl"] .material-n,
[dir="rtl"] .preloader-count,
[dir="rtl"] .visit-address a,
[dir="rtl"] .footer-col a[href^="tel"],
[dir="rtl"] .footer-col a[href^="mailto"] {
  direction: ltr;
  unicode-bidi: embed;
  display: inline-block;
}

/* --- physical offsets the layout still uses ------------------------------ */

[dir="rtl"] .field label {
  left: auto;
  right: 2px;
  transform-origin: 100% 0;
}

[dir="rtl"] .skip-link {
  left: auto;
  right: 10px;
}

[dir="rtl"] .quickview-close {
  right: auto;
  left: 14px;
}

[dir="rtl"] .menu-list button,
[dir="rtl"] .journal-open,
[dir="rtl"] .atelier-band .band-sub {
  text-align: right;
}

[dir="rtl"] .demo-note {
  border-left: 0;
  border-right: 2px solid rgba(246, 239, 228, 0.28);
}

[dir="rtl"] .footer-col a {
  background-position: 100% 100%;
}

[dir="rtl"] .journal-h {
  background-position: 100% 100%;
}

/* the arrows point the other way when the line does */
[dir="rtl"] .product-link i,
[dir="rtl"] .journal-more i,
[dir="rtl"] .btn i,
[dir="rtl"] .note-button span:first-child {
  transform: scaleX(-1);
}

[dir="rtl"] .product-card:hover .product-link i,
[dir="rtl"] .product-card:focus-visible .product-link i,
[dir="rtl"] .journal-open:hover .journal-more i,
[dir="rtl"] .btn:hover i {
  transform: scaleX(-1) translateX(6px);
}

[dir="rtl"] .site-nav button::after {
  transform-origin: 100% 50%;
}

/* the lead product card reverses with the text; the wide closer flips back so
   the two never point the same way */
[dir="rtl"] .product-card.is-wide {
  flex-direction: row;
}

/* --- the language control ------------------------------------------------ */

.lang-switch {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 4px;
  color: inherit;
  font-size: 0.82rem;
  letter-spacing: 0.08em;
  cursor: pointer;
  text-shadow: 0 1px 2px rgba(12, 8, 4, 0.62), 0 2px 22px rgba(12, 8, 4, 0.5);
}

.site-header.is-solid .lang-switch {
  color: var(--ink);
  text-shadow: none;
}

.lang-switch span {
  opacity: 0.5;
  transition: opacity 300ms var(--ease);
}

.lang-switch .is-on {
  opacity: 1;
}

.lang-switch .lang-sep {
  opacity: 0.3;
}

/* One glyph. Naming Tajawal here pulled a whole webfont onto the critical path
   of every English load for a single "ع" that every system already has a face
   for. The Arabic families are loaded on demand now (see loadArabicFonts), and
   once they are here Tajawal is picked up anyway. */
.lang-switch .lang-ar {
  font-family: system-ui, "Segoe UI", "Noto Naskh Arabic", sans-serif;
  font-size: 1rem;
}

[dir="rtl"] .lang-switch .lang-ar {
  font-family: "Tajawal", system-ui, sans-serif;
}

/* three controls in the header at desktop; the toggle only shows on mobile */
@media (min-width: 781px) {
  .site-header {
    grid-template-columns: minmax(140px, 1fr) auto minmax(140px, 1fr);
  }
}

@media (max-width: 780px) {
  .site-header {
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: 14px;
  }

  .lang-switch {
    justify-self: end;
  }
}

/* Switching script changes every text metric on the page at once. Without
   this the whole document jumps; with it, it reads as a deliberate change. */
.site-shell,
.site-footer,
.site-header {
  transition: opacity 260ms var(--ease);
}

html.is-switching .site-shell,
html.is-switching .site-footer,
html.is-switching .site-header {
  opacity: 0.35;
}

@media (prefers-reduced-motion: reduce) {
  .site-shell,
  .site-footer,
  .site-header {
    transition: none;
  }
}

/* ==========================================================================
   Touch target size
   Measured on a 412x800 phone, the real controls were 19-36px tall: the logo
   19, the language switch 34, the menu button 36 and every product "Discover"
   25. WCAG 2.5.8 and both platform guidelines want 44.

   Grown with an overlay rather than with padding or min-height, because the
   header is a three-item flex row and the product footer is a baseline-aligned
   pair — giving either of them a taller box moves type that is currently sitting
   where it was designed to sit. A centred ::after inherits the element's own
   width, costs no layout, and is invisible. Coarse pointers only: a mouse does
   not need it and a hover target that is bigger than it looks is worse.
   ========================================================================== */

@media (pointer: coarse) {
  .site-logo,
  .lang-switch,
  .menu-toggle,
  .product-link,
  .quickview-close {
    position: relative;
  }

  .site-logo::after,
  .lang-switch::after,
  .menu-toggle::after,
  .product-link::after,
  .quickview-close::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 44px;
    transform: translateY(-50%);
    /* Deliberately NOT pointer-events:none. A pseudo-element is part of its
       originating element for hit testing, which is the entire mechanism here:
       a tap that lands on the overlay activates the control. Setting
       pointer-events:none would make it decorative and change nothing.
       Every one of these is already at least 44 wide, so the overlay only ever
       grows the vertical axis and cannot reach a neighbour in the header row. */
  }

  .site-logo,
  .lang-switch,
  .menu-toggle,
  .product-link {
    touch-action: manipulation;
  }
}
