/*
 * Scroll-scrub product template.
 *
 * A scroll-driven image-sequence "film": a fixed full-viewport canvas scrubs
 * through a frame sequence while captions fade in by scroll progress, then a
 * printed content section takes over.
 *
 * Every class is prefixed `ss-` and every state toggle is an `is-*` class so
 * nothing here collides with the project's Tailwind design system. All custom
 * properties live on `.ss-root` and inherit down to the (DOM-descendant, but
 * visually fixed) stage / nav / overlay elements.
 *
 * Theme it from the tokens at the top of .ss-root: surface/ink colors, the
 * --accent (links + CTA), and the font stacks.
 *
 * Tailwind preflight already gives us box-sizing:border-box and zeroed margins
 * on headings/p/blockquote/ul, so the only resets we re-declare are the few
 * scoped tweaks the layout actually depends on.
 */

.ss-root {
  /* Surfaces + ink — dark by default; retune to your brand. */
  --bg: #040406; /* lifted off dead pure-black (#000) for depth; stays cool-monochrome */
  --bg-print: #0b0b0d;
  --ink: #f5f5f7;
  --ink-mid: #a1a1a6;
  --ink-dim: #86868b;
  --hair: rgba(255, 255, 255, 0.08);
  --hair-strong: rgba(255, 255, 255, 0.18);
  --accent: #e8ecf2; /* brand silver — monochrome identity (no gold) */ /* links + CTA */
  --accent-ink: #0a0a0c; /* dark ink for text sitting on the silver accent */
  --display-stack:
    "Fraunces", Georgia, "Times New Roman", serif;
  --text-stack:
    "Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: "Space Mono", "SF Mono", ui-monospace, Menlo, Monaco, Consolas, monospace;

  position: relative;
  min-height: 100vh;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--text-stack);
  font-size: 17px;
  line-height: 1.47;
  letter-spacing: -0.022em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* clip (not hidden): still kills horizontal overflow from the fixed film, but
     does NOT make .ss-root a scroll container — so position:sticky descendants
     (the Atelier carousel pin) stick to the viewport. hidden is the fallback for
     engines without overflow:clip. */
  overflow-x: hidden;
  overflow-x: clip;
}
.ss-root a {
  color: #ffffff;
  text-decoration: none;
}
.ss-root a:hover {
  text-decoration: none;
}
.ss-root img,
.ss-root canvas,
.ss-root video {
  display: block;
  max-width: 100%;
}

/* ============================== STAGE (fixed canvas) ============================== */
.ss-stage {
  position: fixed;
  inset: 0;
  z-index: 0;
  background: #000;
  overflow: hidden;
  transition: opacity 1.2s ease;
}
.ss-stage canvas {
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 0.9s ease;
  /* Colour grade — the raw frames read a touch flat, so lift saturation +
     contrast (GPU filter, no per-frame JS cost). Dial these to taste. */
  filter: saturate(1.2) contrast(1.16) brightness(1.005);
}
.ss-stage.is-ready canvas {
  opacity: 1;
}
.ss-stage.is-faded {
  opacity: 0;
  pointer-events: none;
}

/* gentle vignette so caption copy reads */
.ss-vignette {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(ellipse at center, transparent 38%, rgba(0, 0, 0, 0.55) 100%),
    linear-gradient(
      180deg,
      rgba(0, 0, 0, 0.45) 0%,
      transparent 18%,
      transparent 78%,
      rgba(0, 0, 0, 0.55) 100%
    );
  transition: opacity 1.2s ease;
}
.ss-vignette.is-hidden {
  opacity: 0;
}

/* ============================== LOADER ============================== */
.ss-loader {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  background: #000;
  color: var(--ink);
  transition: opacity 0.9s ease;
}
.ss-loader.is-hidden {
  opacity: 0;
  pointer-events: none;
}
.ss-loader.is-gone {
  display: none;
}
.ss-loader .ss-mark {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mid);
  margin-bottom: 24px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.ss-loader .ss-mark svg {
  width: 18px;
  height: 22px;
  fill: #f5f5f7;
}
.ss-loader-bar {
  width: 280px;
  height: 1px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
  margin-bottom: 14px;
}
.ss-loader-bar > i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--ink);
  transition: width 0.18s linear;
}
.ss-loader-pct {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  color: var(--ink-dim);
}

/* ============================== CHROME (fixed nav + progress) ============================== */
.ss-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  height: 44px;
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  background: rgba(0, 0, 0, 0.72);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  /* Auto-hide: glides up + fades out on scroll-down, glides back + fades in on
     scroll-up (JS toggles .is-hidden — see initNavAutoHide). The opacity fade +
     longer easeOutQuint make the exit silky rather than an abrupt slide. This is
     a liked signature behaviour and runs REGARDLESS of prefers-reduced-motion —
     the environment has it ON, and a `transition:none` override here made the
     nav SNAP away instantly (what read as "disappears too quickly"). */
  will-change: transform, opacity;
  transition:
    transform 0.6s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.ss-nav.is-hidden {
  transform: translateY(-100%);
  opacity: 0;
}
.ss-nav-inner {
  max-width: 1024px;
  margin: 0 auto;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 22px;
  font-size: 12px;
  color: var(--active-flavor-color, #f5f5f7);
  transition: color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.ss-nav-mark {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.02em;
  display: inline-flex;
  align-items: center;
  gap: 0;
  color: var(--active-flavor-color, #f5f5f7);
  transition: color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.ss-nav-mark svg {
  width: 18px;
  height: 18px;
  fill: var(--active-flavor-color, #f5f5f7);
  transition: fill 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.ss-nav ul {
  list-style: none;
  display: flex;
  gap: 22px;
  opacity: 0.92;
  margin: 0;
  padding: 0;
}
.ss-nav li {
  cursor: default;
  opacity: 0.8;
  transition: opacity 0.2s;
}
.ss-nav li a {
  position: relative;
  isolation: isolate;
  display: inline-block;
  padding: 5px 11px;
  border-radius: 8px;
  /* The letters are painted by a gradient clipped to the glyphs: a base layer in
     the active flavour colour (via currentColor, so it still TRANSITIONS when the
     flavour shifts) with a bright white sheen layer parked off-screen at rest. On
     hover the sheen sweeps across the text — the same gleam the finale headline
     and the CTA pills get — instead of a frosted badge appearing behind the link. */
  color: var(--active-flavor-color, #f5f5f7);
  background-image:
    linear-gradient(105deg, rgba(255, 255, 255, 0) 42%, rgba(255, 255, 255, 0.95) 50%, rgba(255, 255, 255, 0) 58%),
    linear-gradient(currentColor, currentColor);
  background-size: 220% 100%, 100% 100%;
  background-position: 200% 0, 0 0;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  transition: color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
/* hover / keyboard focus: one bright sheen sweep across the letters. Runs
   regardless of prefers-reduced-motion — it's a signature interaction, like the
   CTA sheen and the finale shimmer. */
.ss-nav li a:hover,
.ss-nav li a:focus-visible {
  animation: pv-nav-sheen 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.ss-nav li a:focus-visible {
  outline: 1px solid var(--active-flavor-color, #f5f5f7);
  outline-offset: 2px;
}
@keyframes pv-nav-sheen {
  from { background-position: 200% 0, 0 0; }
  to   { background-position: -120% 0, 0 0; }
}
.ss-nav li:hover {
  opacity: 1;
  color: #fff;
}
.ss-nav .ss-right {
  display: flex;
  gap: 18px;
  align-items: center;
  opacity: 0.9;
}
.ss-nav .ss-search {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: #f5f5f7;
  stroke-width: 2;
  stroke-linecap: round;
}
@media (max-width: 740px) {
  .ss-nav ul {
    display: none;
  }
}

.ss-progress {
  position: fixed;
  top: 44px;
  left: 0;
  right: 0;
  height: 1px;
  z-index: 49;
  background: rgba(255, 255, 255, 0.06);
  pointer-events: none;
  /* When the nav auto-hides (slides up), the progress line rides up WITH it to
     the very top of the screen — still visible — instead of staying stranded at
     the 44px nav offset. Same easing/duration as the nav so they move together. */
  transition: top 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
/* .ss-progress is a sibling that follows .ss-nav in .ss-root, so it tracks the
   nav's hidden state. */
.ss-nav.is-hidden ~ .ss-progress {
  top: 0;
}
.ss-progress > i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--active-flavor-color, #f5f5f7);
  transition: background-color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

/* announcement strip — sits inside scroll flow, above the spacer */
.ss-banner {
  position: relative;
  z-index: 5;
  margin-top: 44px;
  background: transparent;
  color: #f5f5f7;
  /* flex so the line of text sits vertically centred with the pill button
     (was inline, so the text baseline-aligned to the button's bottom edge) */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 14px;
  letter-spacing: -0.014em;
  padding: 11px 22px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.ss-banner b {
  font-weight: 600;
}
.ss-banner a {
  margin-left: 0; /* spacing handled by the flex gap now */
  color: #ffffff;
  font-size: 13px;
  opacity: 0.8;
  transition: opacity 0.2s;
}
.ss-banner a:hover {
  opacity: 1;
}

/* ============================== OVERLAYS (sit on top of the fixed canvas while it scrubs) ============================== */
/* Hero — full first viewport */
.ss-hero {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding: 0 22px 14vh;
  text-align: center;
  /* Scrim: anchored to the BOTTOM of the page (this element is fixed/inset:0),
     darkest along the bottom edge and holding through the copy, then fading out
     above the text so the imagery shows up top. Lives on the overlay itself, so
     it fades in/out with the caption — and can never be cut off at the bottom. */
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.84) 0%,
    rgba(0, 0, 0, 0.74) 22%,
    rgba(0, 0, 0, 0.46) 42%,
    rgba(0, 0, 0, 0.16) 56%,
    transparent 70%
  );
  opacity: 0;
  transform: translateY(8px);
  /* EXIT transition (base = the not-in state): the scrim lingers briefly, then
     fades, so the staggered copy below can read out before the backdrop clears. */
  transition:
    opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1) 0.15s,
    transform 0.7s cubic-bezier(0.16, 1, 0.3, 1) 0.15s;
}
.ss-hero.is-in {
  opacity: 1;
  transform: translateY(0);
  /* ENTRANCE transition — snappier, no delay (GSAP owns the per-letter cascade). */
  transition:
    opacity 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Hero copy EXIT — staggered, smooth fade-out as the hero scrolls away
   (headline → lede → cue → eyebrow), soft lift + de-focus. The transition is
   declared ONLY in this :not(.is-in) state so it never fights the GSAP
   entrance (which owns these elements while .is-in). !important overrides the
   inline opacity GSAP leaves on the eyebrow after the entrance settles. */
.ss-hero:not(.is-in) .ss-eyebrow,
.ss-hero:not(.is-in) h1,
.ss-hero:not(.is-in) .ss-lede,
.ss-hero:not(.is-in) .ss-cue {
  opacity: 0 !important;
  transform: translateY(-14px);
  filter: blur(5px);
  transition:
    opacity 0.5s cubic-bezier(0.4, 0, 0.6, 1),
    transform 0.5s cubic-bezier(0.4, 0, 0.6, 1),
    filter 0.5s cubic-bezier(0.4, 0, 0.6, 1);
}
.ss-hero:not(.is-in) h1          { transition-delay: 0s; }
.ss-hero:not(.is-in) .ss-lede    { transition-delay: 0.08s; }
.ss-hero:not(.is-in) .ss-cue     { transition-delay: 0.16s; }
.ss-hero:not(.is-in) .ss-eyebrow { transition-delay: 0.24s; }
@media (prefers-reduced-motion: reduce) {
  .ss-hero:not(.is-in) .ss-eyebrow,
  .ss-hero:not(.is-in) h1,
  .ss-hero:not(.is-in) .ss-lede,
  .ss-hero:not(.is-in) .ss-cue {
    /* Only drop the transform/blur under reduced-motion — KEEP the staggered
       opacity fade + its per-element transition-delays (set above) so the hero
       exits headline→lede→cue→badge in sequence, exactly like the flavour scenes.
       (Previously this reset transition-delay:0s, so for RM users — which is the
       user's OS setting — the whole hero snapped out together, not staggered.) */
    transform: none;
    filter: none;
  }
}
.ss-hero .ss-eyebrow {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-mid);
  margin-bottom: 11px;
}
.ss-hero h1 {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: clamp(44px, 7vw, 96px);
  line-height: 1.04;
  letter-spacing: -0.025em;
  color: #f5f5f7;
  max-width: 18ch;
  margin: 0 0 14px;
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 2px 36px rgba(0, 0, 0, 0.55);
}
.ss-hero h1 em {
  font-style: normal;
  /* Lifted dark stop keeps the metallic sheen but holds contrast on bright frames. */
  background: linear-gradient(180deg, #ffffff 0%, #d6d6d8 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.ss-hero .ss-lede {
  max-width: 580px;
  font-family: var(--display-stack);
  font-weight: 400;
  font-size: clamp(17px, 1.4vw, 21px);
  line-height: 1.38;
  letter-spacing: -0.014em;
  color: rgba(245, 245, 247, 0.92);
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 2px 30px rgba(0, 0, 0, 0.6);
}
.ss-hero .ss-cue {
  margin-top: 34px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
/* Cue label letters — GSAP sweeps a white glow + soft zoom across them
   (initHeroCascade → startCueLoop). Inline-block so transform/scale applies. */
.ss-hero .ss-cue-label .pv-cue-l {
  display: inline-block;
  transform-origin: 50% 60%;
  will-change: transform, color;
}
.ss-hero .ss-cue .ss-bar {
  width: 1px;
  height: 36px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.34), transparent);
  animation: ss-cue 2.4s ease-in-out infinite;
}
@keyframes ss-cue {
  0%,
  100% {
    transform: scaleY(0.4);
    transform-origin: top;
  }
  50% {
    transform: scaleY(1);
  }
}

/* Scenes — editorial captions tied to film progress */
.ss-scene {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  padding: 0 22px;
  display: flex;
}
/* Scene copy animates PER-ELEMENT (badge → heading → text), staggered in AND
   out — so nothing ever snaps in or vanishes as one block. Each child is hidden
   by default and revealed via .is-in with its own delay; the EXIT (:not(.is-in))
   reverses with a soft lift + de-focus, mirroring the hero. */
.ss-scene .ss-kicker,
.ss-scene h2,
.ss-scene p,
.ss-scene .ss-num,
.ss-scene .ss-unit {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity 0.55s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.55s cubic-bezier(0.16, 1, 0.3, 1),
    filter 0.55s cubic-bezier(0.16, 1, 0.3, 1);
}
/* ENTER — cascade down the block */
.ss-scene.is-in .ss-kicker { opacity: 1; transform: none; transition-delay: 0.04s; }
.ss-scene.is-in .ss-num    { opacity: 1; transform: none; transition-delay: 0.12s; }
.ss-scene.is-in h2         { opacity: 1; transform: none; transition-delay: 0.14s; }
.ss-scene.is-in .ss-unit   { opacity: 1; transform: none; transition-delay: 0.22s; }
.ss-scene.is-in p          { opacity: 1; transform: none; transition-delay: 0.26s; }
/* EXIT — staggered soft lift + blur (text leaves first, badge last) */
.ss-scene:not(.is-in) .ss-kicker,
.ss-scene:not(.is-in) h2,
.ss-scene:not(.is-in) p,
.ss-scene:not(.is-in) .ss-num,
.ss-scene:not(.is-in) .ss-unit {
  opacity: 0;
  transform: translateY(-13px);
  filter: blur(4px);
  transition:
    opacity 0.5s cubic-bezier(0.4, 0, 0.6, 1),
    transform 0.5s cubic-bezier(0.4, 0, 0.6, 1),
    filter 0.5s cubic-bezier(0.4, 0, 0.6, 1);
}
.ss-scene:not(.is-in) p        { transition-delay: 0s; }
.ss-scene:not(.is-in) h2       { transition-delay: 0.08s; }
.ss-scene:not(.is-in) .ss-num  { transition-delay: 0.08s; }
.ss-scene:not(.is-in) .ss-unit { transition-delay: 0.12s; }
.ss-scene:not(.is-in) .ss-kicker { transition-delay: 0.16s; }
@media (prefers-reduced-motion: reduce) {
  .ss-scene .ss-kicker,
  .ss-scene h2,
  .ss-scene p,
  .ss-scene .ss-num,
  .ss-scene .ss-unit { transform: none !important; filter: none !important; }
}
.ss-scene .ss-box {
  max-width: 560px;
  color: #f5f5f7;
  /* Tight shadow for edge definition + broad halo. Inherited by the kicker /
     heading / paragraph; the gradient-clipped num reads off the scene scrim. */
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 2px 30px rgba(0, 0, 0, 0.6);
}
.ss-scene .ss-kicker {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-mid);
  margin-bottom: 11px;
}
.ss-scene h2 {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: clamp(36px, 5.5vw, 72px);
  line-height: 1.04;
  letter-spacing: -0.025em;
  color: #f5f5f7;
  /* kill the default heading margins so badge → title → copy sit tight */
  margin: 0;
}
.ss-scene h2 em {
  font-style: normal;
  /* Lifted from --ink-mid so the accent half of the heading stays readable. */
  color: #c7c7c9;
}
.ss-scene p {
  margin-top: 13px;
  font-family: var(--display-stack);
  font-size: clamp(16px, 1.2vw, 20px);
  line-height: 1.4;
  letter-spacing: -0.014em;
  /* Pure white + a heavier dark halo: this copy sits over bright flavour frames
     (esp. Pineapple, where it overlaps the lit bottle/label) and was getting lost.
     position/z-index keeps it on the top layer, above the scene scrim. */
  color: #ffffff;
  position: relative;
  z-index: 1;
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.85),
    0 2px 10px rgba(0, 0, 0, 0.8),
    0 4px 34px rgba(0, 0, 0, 0.7);
}
.ss-scene .ss-num {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: clamp(96px, 15vw, 210px);
  line-height: 0.95;
  letter-spacing: -0.04em;
  background: linear-gradient(180deg, #ffffff 0%, #d6d6d8 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.ss-scene .ss-unit {
  display: block;
  margin-top: 12px;
  font-size: 12px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(245, 245, 247, 0.75);
}

/* Scene anchors — each carries a scrim anchored to the side its copy sits on,
   so only that edge darkens while the rest of the frame stays untouched. The
   scrim lives on a ::before that FADES with the scene's .is-in state, NOT on the
   .ss-scene element itself. Otherwise every scene's scrim is permanently on at
   opacity 1, so an INACTIVE neighbour's scrim (e.g. Caramel's left-darkening
   gradient) bleeds on top of the ACTIVE scene's text and greys it — that was the
   Pineapple "white not coming through on the left" bug on mobile. */
.ss-scene::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.55s cubic-bezier(0.16, 1, 0.3, 1);
}
.ss-scene.is-in::before { opacity: 1; }
.ss-scene.ss-left {
  align-items: center;
  justify-content: flex-start;
  padding-left: max(6vw, 28px);
}
.ss-scene.ss-left::before {
  background: linear-gradient(
    to right,
    rgba(0, 0, 0, 0.72) 0%,
    rgba(0, 0, 0, 0.42) 30%,
    transparent 62%
  );
}
.ss-scene.ss-right {
  align-items: center;
  justify-content: flex-end;
  padding-right: max(6vw, 28px);
}
.ss-scene.ss-right::before {
  background: linear-gradient(
    to left,
    rgba(0, 0, 0, 0.72) 0%,
    rgba(0, 0, 0, 0.42) 30%,
    transparent 62%
  );
}
.ss-scene.ss-right .ss-box {
  text-align: right;
}
.ss-scene.ss-center {
  align-items: center;
  justify-content: center;
  text-align: center;
}
.ss-scene.ss-center::before {
  background: radial-gradient(
    ellipse 78% 56% at 50% 50%,
    rgba(0, 0, 0, 0.62) 0%,
    rgba(0, 0, 0, 0.32) 45%,
    transparent 72%
  );
}
.ss-scene.ss-center .ss-box {
  text-align: center;
}

/* End-of-film closer — appears at the very end of the scrub */
.ss-film-closer {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 22px;
  background: radial-gradient(
    ellipse 80% 60% at 50% 50%,
    rgba(0, 0, 0, 0.55) 0%,
    rgba(0, 0, 0, 0.26) 48%,
    transparent 74%
  );
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.ss-film-closer.is-in {
  opacity: 1;
  transform: translateY(0);
}
.ss-film-closer .ss-word {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: clamp(60px, 10vw, 160px);
  /* >=1 + a hair of padding so descenders (g, y, p) aren't clipped by the
     background-clip:text fill box. */
  line-height: 1.1;
  padding-bottom: 0.08em;
  letter-spacing: -0.04em;
  background: linear-gradient(180deg, #ffffff 0%, #dcdcde 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.ss-film-closer .ss-tag {
  margin-top: 18px;
  font-family: var(--display-stack);
  font-size: clamp(17px, 1.5vw, 22px);
  letter-spacing: -0.014em;
  color: rgba(245, 245, 247, 0.9);
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 2px 30px rgba(0, 0, 0, 0.6);
}

/* ============================== SCROLL SPACER (drives the scrub) ============================== */
.ss-video-spacer {
  position: relative;
  z-index: -1;
  width: 1px;
  /* ~2vh per frame × 361 ≈ 720vh — a long, weighty scrub */
  height: 720vh;
}

/* ============================== PRINTED dossier (post-film) ============================== */
.ss-printed {
  position: relative;
  z-index: 7;
  background: var(--bg-print);
  color: var(--ink);
}
.ss-printed::before {
  content: "";
  display: block;
  height: 14vh;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, transparent 60%);
  pointer-events: none;
}

/* SPECS SLAB */
.ss-slab {
  padding: 110px 22px;
  background: var(--bg-print);
  border-top: 1px solid var(--hair);
}
.ss-slab-wrap {
  max-width: 1024px;
  margin: 0 auto;
}
.ss-slab-title {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: clamp(32px, 4vw, 56px);
  line-height: 1.08;
  letter-spacing: -0.025em;
  text-align: center;
  color: #f5f5f7;
  margin-bottom: 14px;
}
.ss-slab-title em {
  font-style: normal;
  color: var(--ink-mid);
}
.ss-slab-sub {
  text-align: center;
  color: var(--ink-mid);
  font-size: 18px;
  letter-spacing: -0.014em;
  margin-bottom: 64px;
}
.ss-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--hair);
  border: 1px solid var(--hair);
  border-radius: 18px;
  overflow: hidden;
}
.ss-grid .ss-cell {
  background: #0b0b0d;
  padding: 38px 26px;
  text-align: left;
  min-height: 240px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.ss-cell .ss-kk {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-dim);
  min-height: 1em; /* reserve space so the typewriter reveal doesn't shift layout */
}
/* blinking caret while the label types in (JS toggles .is-typing) */
.ss-cell .ss-kk.is-typing::after {
  content: "";
  display: inline-block;
  width: 1px;
  height: 1em;
  margin-left: 2px;
  vertical-align: -0.12em;
  background: var(--ink-mid);
  animation: pv-caret 0.7s steps(1) infinite;
}
@keyframes pv-caret { 50% { opacity: 0; } }
.ss-cell .ss-vv {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: clamp(40px, 4.2vw, 64px);
  line-height: 0.98;
  letter-spacing: -0.03em;
  margin-top: 18px;
  /* solid silver — was a clip-gradient; gradient text on metrics reads decorative
     and loses punch/legibility. (Hero display keeps its subtle gradient.) */
  color: #f5f5f7;
}
.ss-cell .ss-vv sup {
  font-size: 0.4em;
  font-weight: 500;
  color: var(--ink-dim);
  margin-left: 6px;
  vertical-align: 0.8em;
  -webkit-text-fill-color: var(--ink-dim);
}
.ss-cell .ss-nn {
  color: var(--ink-mid);
  font-size: 14px;
  letter-spacing: -0.014em;
  line-height: 1.4;
  margin-top: 18px;
}
@media (max-width: 900px) {
  .ss-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 520px) {
  .ss-grid {
    grid-template-columns: 1fr;
  }
}

/* PULL-QUOTE */
.ss-pull {
  padding: 140px 22px 120px;
  background: #000;
  border-top: 1px solid var(--hair);
}
.ss-pull-wrap {
  max-width: 880px;
  margin: 0 auto;
  text-align: center;
}
.ss-pull blockquote {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: clamp(28px, 3.4vw, 48px);
  line-height: 1.15;
  letter-spacing: -0.022em;
  color: #f5f5f7;
}
.ss-pull blockquote em {
  font-style: normal;
  color: var(--ink-mid);
}
.ss-pull cite {
  display: block;
  margin-top: 36px;
  font-family: var(--mono);
  font-style: normal;
  font-size: 13px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* INVENTORY */
.ss-inventory {
  background: var(--bg-print);
  padding: 110px 22px;
  border-top: 1px solid var(--hair);
}
.ss-inv-wrap {
  max-width: 1024px;
  margin: 0 auto;
}
.ss-inv-head {
  display: flex;
  align-items: flex-end; /* both sit on the divider below */
  justify-content: space-between;
  gap: 24px;
  border-bottom: 1px solid var(--hair-strong);
  padding-bottom: 14px;
}
.ss-inv-head .ss-t {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: 28px;
  line-height: 1; /* reduced so the heading sits tight to the divider */
  letter-spacing: -0.022em;
}
.ss-inv-head .ss-s {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-align: left; /* stays left-aligned even though it sits on the right */
  line-height: 1.3;
}
.ss-row {
  display: grid;
  grid-template-columns: 60px 1.4fr 2fr 100px;
  gap: 24px;
  padding: 22px 0;
  border-bottom: 1px solid var(--hair);
  align-items: baseline;
}
.ss-row .ss-ix {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-dim);
  letter-spacing: 0.04em;
}
.ss-row .ss-lbl {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: 18px;
  color: #f5f5f7;
  letter-spacing: -0.014em;
}
.ss-row .ss-desc {
  color: var(--ink-mid);
  font-size: 15px;
  letter-spacing: -0.014em;
  line-height: 1.45;
}
.ss-row .ss-meta {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-align: right;
}
@media (max-width: 720px) {
  .ss-row {
    grid-template-columns: 40px 1fr;
  }
  .ss-row .ss-desc {
    grid-column: 1 / -1;
    padding-left: 64px;
  }
  .ss-row .ss-meta {
    grid-column: 1 / -1;
    padding-left: 64px;
    text-align: left;
  }
}

/* CLOSER */
.ss-closer {
  position: relative;
  background: #000;
  padding: 140px 22px 100px;
  text-align: center;
  border-top: 1px solid var(--hair);
}
.ss-closer h3 {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: clamp(40px, 5.6vw, 80px);
  line-height: 1.05;
  letter-spacing: -0.025em;
  color: #f5f5f7;
  margin-bottom: 18px;
}
.ss-closer h3 em {
  font-style: normal;
  color: var(--ink-mid);
}
.ss-closer .ss-p {
  max-width: 560px;
  margin: 0 auto 36px;
  color: var(--ink-mid);
  font-size: 18px;
  letter-spacing: -0.014em;
}
.ss-closer .ss-stk {
  display: flex;
  gap: 22px;
  justify-content: center;
  flex-wrap: wrap;
  align-items: center;
}
/* Scoped under .ss-root so the white label wins over `.ss-root a` (which would
   otherwise paint the link text in --accent — i.e. invisible on the accent fill). */
.ss-root .ss-btn {
  display: inline-block;
  background: var(--accent);
  color: var(--accent-ink);
  padding: 11px 22px;
  border-radius: 980px;
  font-family: var(--display-stack);
  font-size: 17px;
  letter-spacing: -0.022em;
  transition: background 0.2s, transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.ss-root .ss-btn:hover {
  background: #ffffff;
  transform: translateY(-2px);
  text-decoration: none;
}
.ss-root .ss-btn:active { transform: translateY(0); }
.ss-link-arrow {
  font-family: var(--display-stack);
  font-size: 17px;
  letter-spacing: -0.022em;
  color: #ffffff;
  transition: opacity 0.2s;
}
.ss-link-arrow:hover {
  opacity: 0.8;
  text-decoration: none;
}
.ss-link-arrow::after {
  content: " ›";
  font-weight: 400;
}
.ss-closer .ss-note {
  margin-top: 24px;
  font-size: 12px;
  color: var(--ink-dim);
  letter-spacing: -0.01em;
}

/* FOOTER */
.ss-footer {
  background: var(--bg-print);
  color: var(--ink-dim);
  font-size: 12px;
  letter-spacing: -0.01em;
  padding: 30px 22px 26px;
  border-top: 1px solid var(--hair);
}
.ss-footer .ss-fwrap {
  max-width: 1024px;
  margin: 0 auto;
}
.ss-footer .ss-legalese {
  line-height: 1.5;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--hair);
}
.ss-footer .ss-legalese p + p {
  margin-top: 10px;
}
.ss-footer .ss-legalese a {
  color: var(--ink-mid);
}
.ss-footer .ss-fcols {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 30px;
  padding: 30px 0;
}
.ss-footer .ss-fcols h4 {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: 12px;
  color: #f5f5f7;
  margin-bottom: 10px;
}
.ss-footer .ss-fcols ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.ss-footer .ss-fcols li {
  padding: 3px 0;
}
.ss-footer .ss-fcols a {
  color: var(--ink-dim);
}
.ss-footer .ss-fcols a:hover {
  text-decoration: none;
  color: var(--ink-mid);
}
@media (max-width: 900px) {
  .ss-footer .ss-fcols {
    grid-template-columns: repeat(2, 1fr);
  }
}
.ss-footer .ss-row2 {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 18px;
  border-top: 1px solid var(--hair);
  flex-wrap: wrap;
  gap: 12px;
}
.ss-footer .ss-row2 .ss-l a {
  margin: 0 14px 0 0;
  color: var(--ink-dim);
}

.ss-nav-cta {
  display: inline-block;
  border: 1px solid var(--active-flavor-color, #f5f5f7);
  color: var(--active-flavor-color, #f5f5f7);
  padding: 4px 14px;
  border-radius: 980px;
  font-weight: 500;
  text-decoration: none;
  transition: border-color 0.4s cubic-bezier(0.16, 1, 0.3, 1), color 0.4s cubic-bezier(0.16, 1, 0.3, 1), background-color 0.2s;
}
.ss-nav-cta:hover {
  background-color: rgba(255, 255, 255, 0.1);
  text-decoration: none;
}

/* --- Pre-order buttons dressed as silver award badges: the same brushed-silver
   plate, inset highlights and sheen sweep as the chapter badges (.ss-eyebrow /
   .ss-kicker, see pv-badge-sheen), but keeping their FULL pill radius. Declared
   after the base button rules so it wins on equal specificity. --- */
.ss-root .ss-btn,
.ss-root .ss-nav-cta {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* .ss-root prefix is REQUIRED: bare .ss-nav-cta (0,1,0) loses to
     `.ss-root a {color:#fff}` (0,1,1), which left the Pre-Order text white on the
     silver plate (invisible). .ss-root .ss-nav-cta is (0,2,0) and wins. */
  color: #1b1c1f;
  background: linear-gradient(135deg, #ffffff 0%, #e7e9ed 46%, #c5c8cf 100%);
  border: 1px solid rgba(255, 255, 255, 0.85);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 0 0 1px rgba(120, 124, 132, 0.22),
    0 8px 22px rgba(0, 0, 0, 0.34);
  transition:
    transform 0.2s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.3s ease,
    background 0.3s ease;
}
.ss-root .ss-btn::after,
.ss-nav-cta::after {
  content: "";
  position: absolute;
  z-index: 2;
  top: 0;
  bottom: 0;
  left: 0;
  width: 55%;
  background: linear-gradient(
    100deg,
    transparent 0%,
    rgba(255, 255, 255, 0.85) 50%,
    transparent 100%
  );
  transform: translateX(-180%) skewX(-12deg);
  animation: pv-badge-sheen 4.5s ease-in-out infinite;
  pointer-events: none;
}
.ss-root .ss-btn:hover,
.ss-nav-cta:hover {
  color: #0a0a0c;
  background: linear-gradient(135deg, #ffffff 0%, #eef0f3 46%, #d3d6dd 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 0 0 1px rgba(120, 124, 132, 0.32),
    0 16px 32px rgba(0, 0, 0, 0.45);
}
.ss-root .ss-btn:hover { transform: translateY(-2px); }
/* The brushed-silver sheen is a SIGNATURE brand animation and must keep sweeping
   under prefers-reduced-motion (the user browses with RM ON), exactly like the
   chapter badges. Do NOT re-add `animation: none` / `opacity: 0` here — that
   kills the shimmer. RM may at most drop transform/blur, never the sheen. The
   full pill radius (border-radius: 980px) lives on the base .ss-btn / .ss-nav-cta
   rules above and is untouched, so it is preserved. */

/* ==============================================================================
   SHARED CORPORATE CHROME — Nyathi Corporation umbrella
   Added for the umbrella design (not part of the original scroll-scrub template).
   Brand switcher + corporate footer line + the shared age gate, ported from the
   root Nyathi site so Pure Vodka gates with the same look and the same
   sessionStorage key. Asset paths are relative to this file
   (/pure-vodka/css/), so ../../ reaches the site root.
   ============================================================================== */

/* --- Brand switcher (sits in the ss-nav between the logo and the menu) --- */
.ss-brand-switch {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 18px;
  padding: 3px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 980px;
  font-family: var(--display-stack);
  font-size: 11px;
  letter-spacing: 0.04em;
}
.ss-brand-switch a {
  display: inline-block;
  padding: 3px 11px;
  border-radius: 980px;
  color: rgba(245, 245, 247, 0.7);
  white-space: nowrap;
  transition: color 0.2s, background-color 0.2s;
}
.ss-brand-switch a:hover {
  color: #fff;
  text-decoration: none;
}
.ss-brand-switch a.is-current {
  background: rgba(255, 255, 255, 0.92);
  color: #000;
}
@media (max-width: 740px) {
  .ss-brand-switch { margin-left: 10px; }
  .ss-brand-switch a { padding: 3px 8px; }
}

/* --- Real Pure Vodka wordmark (replaces the placeholder ring mark).
   The source SVG is a black wordmark; brightness(0) invert(1) renders it
   clean white to read on the dark chrome. --- */
.ss-logo-img {
  display: block;
  width: auto;
  filter: brightness(0) invert(1);
}
.ss-logo-img--loader { height: 40px; }
.ss-logo-img--nav { height: 24px; }
@media (max-width: 740px) {
  .ss-logo-img--nav { height: 20px; }
}

/* --- Corporate footer line (appended inside the existing ss-footer) --- */
.ss-corp-line {
  text-align: center;
  padding-top: 18px;
  margin-top: 6px;
  border-top: 1px solid var(--hair);
  font-family: var(--display-stack);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--ink-mid);
}
.ss-corp-line a {
  color: var(--ink);
}

/* --- Black Mango — emphasis display font used by the shared age gate --- */
@font-face {
  font-family: 'Black Mango';
  src: url('../../fonts/blackmango-extrabold-webfont.woff2') format('woff2'),
       url('../../fonts/blackmango-extrabold-webfont.woff') format('woff');
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

/* --- Shared age gate (ported verbatim from the root site's css/style.css;
   self-contained here with its own tokens + corrected ../../ asset paths) --- */
#age-gate {
  --ag-font-hero: 'Black Mango', 'Syne', -apple-system, 'Segoe UI', sans-serif;
  --ag-font-emphasis: 'Black Mango', 'Syne', sans-serif;
  --ag-accent: #00FFB2;
  --ag-ease: cubic-bezier(0.16, 1, 0.3, 1);

  position: fixed;
  inset: 0;
  z-index: 99999;
  background-color: #F4EBDD;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  isolation: isolate;
  transition: opacity 0.6s var(--ag-ease), visibility 0.6s var(--ag-ease);
}
#age-gate::before {
  content: "";
  position: absolute;
  inset: -20%;
  background-image: url("../../images/pattern-design.svg");
  background-repeat: no-repeat;
  background-position: center center;
  background-size: min(160vh, 160vw) auto;
  opacity: 0.55;
  pointer-events: none;
  z-index: 0;
}
#age-gate::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, rgba(244, 235, 221, 0.3) 0%, rgba(244, 235, 221, 0.85) 80%);
  pointer-events: none;
  z-index: 0;
}
#age-gate .age-gate-inner {
  position: relative;
  z-index: 1;
  max-width: 720px;
  width: 90%;
  padding: 2rem;
  text-align: center;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}
#age-gate.hidden { opacity: 0; visibility: hidden; pointer-events: none; }
/* Already verified this session (set pre-paint by the inline head script) — remove
   the gate entirely from the first frame so it never flashes on refresh/navigation. */
html.age-verified #age-gate { display: none !important; }
#age-gate .age-gate-logo {
  display: block;
  width: clamp(180px, 28vw, 240px);
  height: auto;
  margin-bottom: 2.5rem;
}
#age-gate .age-gate-question {
  font-family: var(--ag-font-hero);
  font-size: clamp(1.1rem, 2.2vw, 1.6rem);
  font-weight: 600;
  line-height: 1.3;
  color: #002106;
  margin-bottom: 2rem;
}
#age-gate .age-gate-buttons {
  display: flex;
  gap: 1rem;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  margin: 0 auto 2rem;
}
#age-gate .age-gate-buttons .ornate-btn {
  --ornate-fill: #FAF2EC;
  --ornate-stroke: #002106;
  --ornate-ink: #002106;
}
#age-gate .age-gate-legal {
  font-size: 0.6rem;
  color: rgba(0, 33, 6, 0.65);
  line-height: 1.6;
  max-width: 520px;
  margin-inline: auto;
}

/* Ornate buttons (ported from root css/style.css) */
#age-gate .ornate-btn {
  --ornate-fill: #FAF2EC;
  --ornate-stroke: #002106;
  --ornate-ink: #002106;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem 3.5rem;
  min-height: 62px;
  min-width: 220px;
  width: auto;
  max-width: 100%;
  background: transparent;
  border: 0;
  cursor: pointer;
  text-decoration: none;
  color: var(--ornate-ink);
  font-family: var(--ag-font-emphasis);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  line-height: 1;
  isolation: isolate;
  box-sizing: border-box;
  transition: transform 0.25s var(--ag-ease), filter 0.3s ease;
}
#age-gate .ornate-btn__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  overflow: visible;
}
#age-gate .ornate-btn__outer {
  fill: var(--ornate-fill);
  stroke: var(--ornate-stroke);
  stroke-width: 1.25;
  stroke-miterlimit: 10;
}
#age-gate .ornate-btn__inner-line {
  fill: var(--ornate-fill);
  stroke: var(--ornate-stroke);
  stroke-width: 0.85;
  stroke-linecap: round;
  stroke-linejoin: round;
}
#age-gate .ornate-btn__label {
  position: relative;
  z-index: 2;
  white-space: nowrap;
}
#age-gate .ornate-btn:hover {
  transform: translateY(-2px);
  filter: drop-shadow(0 8px 22px rgba(0, 33, 6, 0.35));
}
#age-gate .ornate-btn:active { transform: translateY(0) scale(0.98); }
#age-gate .ornate-btn:focus-visible { outline: 2px solid var(--ag-accent); outline-offset: 6px; }

/* ==============================================================================
   PER-FLAVOUR GLASS PANELS (left side)
   Glassmorphism info cards + a lifestyle-image slot, revealed in sync with the
   scroll film. They reuse the same [data-range] + .is-in mechanism as the scene
   captions (toggled by js/pure-vodka.js), so no extra JS is needed.
   ============================================================================== */
/* These panels are SCRUBBED to scroll: js/pure-vodka.js sets --r (0..1) every
   frame from the film's own smoothed position, so the reveal eases in as you
   scroll down and reverses as you scroll back up — no one-shot transitions. */
.pv-flavor-panel {
  position: fixed;
  bottom: clamp(20px, 5vh, 56px);
  top: auto;
  z-index: 6;
  width: clamp(180px, 17vw, 244px);
  display: flex;
  flex-direction: column;
  gap: 11px;
  pointer-events: none;
  will-change: transform;
}
/* Classic (the intro) sits LEFT; silver accent, cards slide in from the left */
.pv-flavor-panel--classic {
  left: max(4vw, 22px);
  right: auto;
  --fl: #e8ecf2;
  --slide-x: -72px;
}
/* Pineapple sits LEFT (its caption is on the right); cards slide in from the left */
.pv-flavor-panel--pineapple {
  left: max(4vw, 22px);
  right: auto;
  --fl: #d4f134;
  --slide-x: -72px;
}
/* Caramel sits RIGHT (its caption is on the left); cards slide in from the right */
.pv-flavor-panel--caramel {
  right: max(4vw, 22px);
  left: auto;
  --fl: #e5a93b;
  --slide-x: 72px;
}
.pv-flavor-panel--caramel .pv-card { text-align: right; }
.pv-flavor-panel--caramel .pv-card::before { left: auto; right: 0; }

/* Lifestyle image slot (glass-framed) */
.pv-lifestyle {
  position: relative;
  margin: 0;
  border-radius: 16px;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  border: 1px solid color-mix(in oklab, var(--fl) 35%, rgba(255, 255, 255, 0.16));
  background: rgba(255, 255, 255, 0.04);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  perspective: 1100px;
}
/* Stacked product shots — cross-flip as you scroll through the flavour.
   Scaled from the top so the logo/slogan at the foot of each shot is cropped out. */
.pv-shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  transform-origin: top center;
  transform: scale(1.2);
  opacity: 0;
  backface-visibility: hidden;
  will-change: opacity, transform;
}
.pv-lifestyle__ph {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 14px;
  font-family: var(--text-stack);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(245, 245, 247, 0.55);
}

/* Glass stat cards */
.pv-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 9px 11px;
  border-radius: 11px;
  background: rgba(15, 15, 19, 0.28);
  border: 1px solid rgba(255, 255, 255, 0.16);
  /* Safari: plain frost (no SVG-filter backdrop support) */
  -webkit-backdrop-filter: blur(30px) saturate(180%);
  /* Chromium: lighter blur + turbulence displacement = real frosted-glass
     refraction of the film scrubbing behind the card */
  backdrop-filter: blur(7px) saturate(165%) url(#pv-frost);
  box-shadow:
    0 10px 34px rgba(0, 0, 0, 0.40),
    inset 0 1px 0 rgba(255, 255, 255, 0.10);
  overflow: hidden;
}
.pv-frost-defs {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
}
.pv-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--fl);
  opacity: 0.9;
  /* Scroll-driven: fills bottom→top as you scroll through the flavour — JS sets
     --bar from the scrub position, so the line grows toward the top with scroll.
     The card's own opacity fade carries it in/out. Same bar on the right edge
     for Caramel. */
  transform: scaleY(var(--bar, 0));
  transform-origin: bottom;
}
/* Foundation label — plain (the gold award badge now lives on the chapter
   labels ABOVE each flavour title, not on the panel cards or carousel). */
.pv-card__k {
  font-family: var(--text-stack);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(245, 245, 247, 0.74);
}

/* ---- Award badge skin -----------------------------------------------------
   Vanilla rebuild of the AwardBadge look: a gold plate with an inner stroke, a
   medallion, and a slowly-rotating holographic sheen (overlay-blended, clipped
   to the plate). Applied to the chapter labels that sit ABOVE each flavour
   title — "THE FOUNDATION" (hero) and "THE INFUSION" / "THE INDULGENCE"
   (scenes). inline-block so the plate shrinks to the label and centres with the
   hero copy. */
.ss-hero .ss-eyebrow,
.ss-scene .ss-kicker {
  position: relative;
  isolation: isolate;
  display: inline-block;
  pointer-events: none; /* enabled (for the tilt) only while in view, below */
  padding: 5px 12px;
  border-radius: 8px;
  overflow: hidden;
  font-family: var(--mono);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #1b1c1f; /* dark ink on the silver plate */
  /* the scene .ss-box passes a dark text-shadow down to its kicker — reset it
     here so the INFUSION / INDULGENCE plates read as crisply as FOUNDATION. */
  text-shadow: none;
  background: linear-gradient(135deg, #ffffff 0%, #e7e9ed 46%, #c5c8cf 100%);
  border: 1px solid rgba(255, 255, 255, 0.85);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 0 0 1px rgba(120, 124, 132, 0.22),
    0 8px 22px rgba(0, 0, 0, 0.34);
  transform-style: preserve-3d;
  /* NB: no transform transition here — JS owns the tilt easing so it never
     fights the GSAP hero cascade. */
  transition: box-shadow 0.3s ease;
  vertical-align: middle;
}
/* hoverable only while its chapter is on screen (the overlays are fixed) */
.ss-hero.is-in .ss-eyebrow,
.ss-scene.is-in .ss-kicker { pointer-events: auto; }
.ss-hero .ss-eyebrow:hover,
.ss-scene .ss-kicker:hover {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 0 0 1px rgba(120, 124, 132, 0.3),
    0 16px 32px rgba(0, 0, 0, 0.45);
}
/* shimmer — a bright highlight glint sweeps across the badge title (grey/white,
   no colour, so the letters never look stroked) */
.ss-hero .ss-eyebrow::after,
.ss-scene .ss-kicker::after {
  content: "";
  position: absolute;
  z-index: 2;
  top: 0;
  bottom: 0;
  left: 0;
  width: 55%;
  background: linear-gradient(
    100deg,
    transparent 0%,
    rgba(255, 255, 255, 0.85) 50%,
    transparent 100%
  );
  transform: translateX(-180%) skewX(-12deg);
  animation: pv-badge-sheen 4.5s ease-in-out infinite;
  pointer-events: none;
}
@keyframes pv-badge-sheen {
  0% { transform: translateX(-180%) skewX(-12deg); }
  55%, 100% { transform: translateX(330%) skewX(-12deg); }
}
/* Chapter-badge sheen is a signature animation too — it must keep sweeping under
   prefers-reduced-motion (user has RM ON). No animation:none / opacity:0 kill here. */
/* Smaller silver badges on phones */
@media (max-width: 600px) {
  .ss-hero .ss-eyebrow,
  .ss-scene .ss-kicker {
    padding: 4px 9px;
    font-size: 9px;
    letter-spacing: 0.1em;
    border-radius: 7px;
  }
}
.pv-card__v {
  font-family: var(--display-stack);
  font-weight: 600;
  font-size: 15.5px;
  letter-spacing: -0.01em;
  color: #f6f6f8;
}
.pv-card__n {
  font-family: var(--text-stack);
  font-size: 11px;
  line-height: 1.42;
  color: rgba(245, 245, 247, 0.82);
}

/* Per-layer scrubbed progress (--cp), lagged so each layer completes a little
   after the previous — a silky stagger entirely driven by --r. */
.pv-flavor-panel .pv-lifestyle           { --cp: clamp(0, calc((var(--r, 0) - 0.00) / 0.52), 1); }
.pv-flavor-panel .pv-card:nth-of-type(1) { --cp: clamp(0, calc((var(--r, 0) - 0.18) / 0.52), 1); }
.pv-flavor-panel .pv-card:nth-of-type(2) { --cp: clamp(0, calc((var(--r, 0) - 0.34) / 0.52), 1); }
.pv-flavor-panel > * {
  opacity: var(--cp, 0);
  transform:
    translateX(calc((1 - var(--cp, 0)) * var(--slide-x, -60px)))
    translateY(calc((1 - var(--cp, 0)) * 14px))
    scale(calc(0.95 + 0.05 * var(--cp, 0)));
  filter: blur(calc((1 - var(--cp, 0)) * 6px));
}

/* Text rises line-by-line, lagging behind its own card's progress (--cp inherits) */
.pv-card__k { --tp: clamp(0, calc((var(--cp, 0) - 0.04) / 0.62), 1); }
.pv-card__v { --tp: clamp(0, calc((var(--cp, 0) - 0.20) / 0.62), 1); }
.pv-card__n { --tp: clamp(0, calc((var(--cp, 0) - 0.40) / 0.62), 1); }

/* Kicker + flavour name reveal LETTER-BY-LETTER (JS splits them into .pv-ch,
   each carries an --i index; the cascade is driven by the line's scrubbed --tp). */
.pv-card__k, .pv-card__v { opacity: 1; }
.pv-ch {
  display: inline-block;
  white-space: pre;
  --lp: clamp(0, calc((var(--tp, 0) - (var(--i, 0) * 0.045)) / 0.34), 1);
  opacity: var(--lp);
  transform: translateY(calc((1 - var(--lp)) * 0.5em));
  will-change: opacity, transform;
}

/* The note is body copy — a smooth clip-wipe reads better than per-letter */
.pv-card__n {
  opacity: var(--tp, 0);
  clip-path: inset(0 calc((1 - var(--tp, 0)) * 100%) 0 0);
}
.pv-flavor-panel--caramel .pv-card__n {
  clip-path: inset(0 0 0 calc((1 - var(--tp, 0)) * 100%));
}

/* Subtle RGB-split glitch on the flavour name when its card enters view
   (JS toggles .is-entered). Text-shadow only, so it never fights the scrub. */
@keyframes pv-glitch {
  0%, 100% { text-shadow: none; }
  10% { text-shadow: -1.5px 0 rgba(255, 70, 110, 0.9), 1.5px 0 rgba(70, 170, 255, 0.9); }
  24% { text-shadow: 1.5px 0 rgba(255, 70, 110, 0.9), -1.5px 0 rgba(70, 170, 255, 0.9); }
  38% { text-shadow: -1px 0 rgba(255, 70, 110, 0.7), 1px 0 rgba(70, 170, 255, 0.7); }
  55% { text-shadow: 0.5px 0 var(--fl); }
}
.pv-flavor-panel.is-entered .pv-card__v {
  animation: pv-glitch 0.5s linear 1;
}

/* On phones the panels sit at the BOTTOM as a compact image + cards strip and
   animate straight UP as you scroll into each flavour (no side slide). */
@media (max-width: 900px) {
  .pv-flavor-panel {
    display: flex;
    flex-direction: row;
    align-items: stretch; /* image + both cards all the same height */
    left: 10px;
    right: 10px;
    width: auto;
    bottom: max(10px, env(safe-area-inset-bottom));
    gap: 7px;
    --slide-x: 0px;
  }
  .pv-flavor-panel--caramel {
    left: 10px;
    right: 10px;
    flex-direction: row-reverse;
  }
  .pv-flavor-panel .pv-lifestyle { flex: 0 0 30%; aspect-ratio: 4 / 5; }
  .pv-flavor-panel .pv-card { flex: 1 1 0; min-width: 0; padding: 7px 9px; gap: 2px; }
  /* smaller card type on phones */
  .pv-flavor-panel .pv-card__k { font-size: 9px; letter-spacing: 0.14em; }
  .pv-flavor-panel .pv-card__v { font-size: 14px; line-height: 1.14; }
  .pv-flavor-panel .pv-card__n { font-size: 11px; line-height: 1.36; }
  .pv-flavor-panel > * {
    transform:
      translateY(calc((1 - var(--cp, 0)) * 42px))
      scale(calc(0.96 + 0.04 * var(--cp, 0)));
  }
}

/* Accessibility — honour reduced-motion: keep the scrubbed opacity fade but
   drop all slide / scale / flip movement. */
@media (prefers-reduced-motion: reduce) {
  .pv-flavor-panel { transform: none !important; }
  .pv-flavor-panel > * { transform: none !important; filter: none !important; }
  .pv-card__k, .pv-card__v, .pv-card__n { transform: none !important; clip-path: none !important; }
  .pv-flavor-panel.is-entered .pv-card__v { animation: none !important; }
  /* keep the scroll-driven bar fill (it's not a free-running animation) */
  .pv-shot { transform: scale(1.2) !important; }
}

/* ==============================================================================
   FINALISATION PASS — lighter Fraunces weights/sizes, specs alignment,
   banner button, per-letter reduced-motion.
   ============================================================================== */

/* Fraunces reads heavier + larger than the old grotesque — lighten and trim */
.ss-hero h1,
.ss-scene h2,
.ss-film-closer .ss-word,
.ss-slab-title,
.ss-closer h3,
.ss-pull blockquote,
.ss-cell .ss-vv,
.pv-card__v {
  font-weight: 500;
}
.ss-hero h1            { font-size: clamp(36px, 5.6vw, 76px); letter-spacing: -0.02em; }
/* Flavour titles (Pineapple / Caramel) match the hero headline scale. */
.ss-scene h2           { font-size: clamp(36px, 5.6vw, 76px); letter-spacing: -0.02em; }
.ss-film-closer .ss-word { font-size: clamp(48px, 8vw, 124px); letter-spacing: -0.03em; }
.ss-slab-title         { font-size: clamp(26px, 3.2vw, 46px); }
.ss-closer h3          { font-size: clamp(32px, 4.6vw, 64px); }
.ss-cell .ss-vv        { font-size: clamp(34px, 3.6vw, 54px); }

/* Specs grid: align every value on one line (kicker top, value directly under,
   note pushed to the bottom and free to wrap to 2 lines). */
.ss-cell { justify-content: flex-start; }
.ss-cell .ss-nn { margin-top: auto; padding-top: 16px; }

/* Banner CTA is a real button, not an underline-on-hover text link */
.ss-banner-cta {
  display: inline-block;
  margin-left: 12px;
  padding: 5px 15px;
  border: 1px solid var(--active-flavor-color, #f5f5f7);
  border-radius: 980px;
  color: var(--active-flavor-color, #f5f5f7);
  font-size: 12px;
  letter-spacing: 0.05em;
  opacity: 1;
  text-decoration: none;
  transition:
    background-color 0.2s ease,
    color 0.4s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.ss-banner-cta:hover {
  text-decoration: none;
  background: rgba(255, 255, 255, 0.12);
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .pv-ch { transform: none !important; }
}

/* ==============================================================================
   ENHANCEMENT PASS — patterns ported from the dark-portfolio reference, retuned
   to the Pure Vodka brand (luxury gold + per-flavour accents, never the AI
   cyan/purple). Everything below is ADDITIVE and prefixed so it never touches
   the canvas film: a bento collection grid (replaces the flat allocation rows),
   a parallax atelier gallery, smooth anchor-nav, scroll-reveal transitions, a
   loader word-cycle, and a one-shot hero entrance. All movement is gated behind
   prefers-reduced-motion.
   ============================================================================== */

/* --- Smooth anchor-nav. Scoped to this page (the file only loads here). The
   wheel-driven film scrub stays 1:1 with the scrollbar; only programmatic /
   anchor jumps ease. scroll-margin clears the 44px fixed nav + 1px progress. --- */
html { scroll-behavior: smooth; }
/* Brand-dark page base. WITHOUT this the body defaults to WHITE, which bleeds
   through as a white "frame/flash" at the film finale — when the dark canvas
   fades out (.ss-stage.is-faded) and the mobile URL-bar height shift briefly
   leaves the fixed stage not fully covering. The root Nyathi site sets this on
   body too; the Pure Vodka page was missing it. */
html, body { background: #040406; }
.ss-printed [id],
#flavours, #specs, #process, #buy { scroll-margin-top: 64px; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* --- Scroll-reveal: printed sections rise into view once. JS adds .is-visible
   via IntersectionObserver. Children stagger off an inline --d delay. --- */
.ss-reveal {
  opacity: 0;
  transform: translateY(30px);
  transition:
    opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.ss-reveal.is-visible { opacity: 1; transform: none; }
.ss-reveal [data-stagger] {
  opacity: 0;
  transform: translateY(22px);
  transition:
    opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1) var(--d, 0s),
    transform 0.8s cubic-bezier(0.16, 1, 0.3, 1) var(--d, 0s);
}
.ss-reveal.is-visible [data-stagger] { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .ss-reveal,
  .ss-reveal [data-stagger] { opacity: 1 !important; transform: none !important; }
}

/* --- One-shot hero entrance. Children default visible; the keyframe runs only
   while .ss-root.is-intro is set (added when the loader clears), so if JS never
   fires nothing is hidden. --- */
@keyframes pv-rise {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: none; }
}
/* Block-level fallback entrance — used only when GSAP isn't driving the
   per-letter/word cascade (JS adds .gsap-on to .ss-root when it is). */
.ss-root.is-intro:not(.gsap-on) .ss-hero .ss-eyebrow { animation: pv-rise 0.85s cubic-bezier(0.16, 1, 0.3, 1) 0.05s both; }
.ss-root.is-intro:not(.gsap-on) .ss-hero h1          { animation: pv-rise 0.95s cubic-bezier(0.16, 1, 0.3, 1) 0.16s both; }
.ss-root.is-intro:not(.gsap-on) .ss-hero .ss-lede    { animation: pv-rise 0.95s cubic-bezier(0.16, 1, 0.3, 1) 0.30s both; }
.ss-root.is-intro:not(.gsap-on) .ss-hero .ss-cue     { animation: pv-rise 0.90s cubic-bezier(0.16, 1, 0.3, 1) 0.46s both; }
@media (prefers-reduced-motion: reduce) {
  .ss-root.is-intro .ss-hero > * { animation: none !important; }
}

/* --- Loader word-cycle (reference's rotating words, retuned to the three
   chapters: Foundation / Infusion / Indulgence). --- */
.ss-loader-word {
  position: relative;
  height: 1.3em;
  margin-bottom: 22px;
  /* clip only the vertical rise; horizontal padding gives italic glyphs room so
     the first/last letters aren't shaved by the overflow box. */
  overflow: hidden;
  padding: 0 0.5em;
  font-family: var(--display-stack);
  font-weight: 500;
  font-style: italic;
  font-size: clamp(30px, 5vw, 52px);
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: rgba(245, 245, 247, 0.9);
}
.ss-loader-word span {
  display: block;
  animation: pv-word 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes pv-word {
  from { opacity: 0; transform: translateY(0.6em); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ss-loader-word span { animation: none; }
}

/* ==============================================================================
   BENTO COLLECTION GRID — replaces the flat 01/02/03 allocation rows.
   Alternating 7/5/5/7 spans on a 12-col grid (per the reference). Three product
   cards carry their flavour shot + halftone + hover "View" pill; the fourth is a
   distinct editorial/CTA card (breaks the identical-grid anti-pattern).
   ============================================================================== */
/* Animatable angle for the lens-flare that races around each card's border. */
@property --pv-ang {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
@keyframes pv-flare-spin {
  to { --pv-ang: 360deg; }
}

.pv-bento {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(16px, 1.6vw, 24px);
  margin-top: 52px;
}
.pv-bento-card {
  position: relative;
  display: block;
  border-radius: 24px;
  overflow: hidden;
  background: #0b0b0d;
  border: 1px solid var(--hair);
  isolation: isolate;
  text-decoration: none;
  color: inherit;
  /* micro-interaction: the whole card pops forward to full size on hover —
     quick + smooth — while a lens-flare glow races around the border. */
  transition:
    transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.4s ease,
    box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Lens-flare border: a conic sweep masked to the 1.5px rim, spun fast on hover. */
.pv-bento-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  border-radius: inherit;
  padding: 1.5px;
  /* both 0deg and 360deg are transparent so the comet fades out cleanly and the
     wrap point (top) has no hard seam */
  background: conic-gradient(from var(--pv-ang, 0deg),
      transparent 0deg,
      transparent 232deg,
      color-mix(in oklab, var(--card-accent) 55%, transparent) 290deg,
      #ffffff 320deg,
      color-mix(in oklab, var(--card-accent) 55%, transparent) 350deg,
      transparent 360deg);
  -webkit-mask:
      linear-gradient(#000 0 0) content-box,
      linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}
/* Scoped under .ss-reveal.is-visible so this transform out-specifies the
   reveal rule (.ss-reveal.is-visible [data-stagger] { transform: none }). */
.ss-reveal.is-visible .pv-bento-card:hover,
.pv-bento-card:hover {
  text-decoration: none;
  transform: translateY(-8px);
  z-index: 5;
  border-color: transparent;
  box-shadow:
    0 30px 70px -20px rgba(0, 0, 0, 0.78),
    0 0 40px -8px color-mix(in oklab, var(--card-accent) 65%, transparent);
}
.pv-bento-card:hover::before {
  opacity: 1;
  /* whips around quickly, then eases right down — fast, then really slowing */
  animation: pv-flare-spin 1.8s cubic-bezier(0.05, 0.7, 0.1, 1) infinite;
}
@media (prefers-reduced-motion: reduce) {
  .pv-bento-card { transition: none; }
  .ss-reveal.is-visible .pv-bento-card:hover,
  .pv-bento-card:hover { transform: none; }
  .pv-bento-card:hover::before { animation: none; }
}
/* --- Scroll-in reveal: each allocation card rises ONE AT A TIME as it enters
   view (initBentoReveal toggles .is-revealed per card), with its accent border
   glowing in. The slow rise transition is scoped to :not(:hover) so the hover
   lift stays snappy; the border-glow keyframe carries no fill so it reverts and
   never blocks the hover box-shadow. --- */
/* Hidden start state — gated on the JS-added .pv-rise-armed (see initBentoReveal)
   so a no-JS / failed-JS load leaves the cards VISIBLE rather than stranded. */
.pv-bento.pv-rise-armed .pv-bento-card.pv-rise { opacity: 0; transform: translateY(46px) scale(0.985); }
/* Revealed (per card, as it scrolls in): fade + rise + a one-shot accent border
   glow. Higher specificity than the hidden rule so it always wins. The border
   glow + fade run REGARDLESS of prefers-reduced-motion — it's a signature reveal
   and the user's OS has reduced-motion ON (gating it here hid it entirely). */
.pv-bento.pv-rise-armed .pv-bento-card.pv-rise.is-revealed {
  opacity: 1;
  animation: pv-card-border-in 1.3s cubic-bezier(0.16, 1, 0.3, 1);
}
/* transform:none only when NOT hovering, so the hover lift (.pv-bento-card:hover)
   still wins; this also carries the rise transition. */
.pv-bento.pv-rise-armed .pv-bento-card.pv-rise.is-revealed:not(:hover) {
  transform: none;
  transition:
    opacity 0.85s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.85s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.4s ease,
    box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Hover lift — must out-specify the (now armed-prefixed) hidden/rest rules above
   so the card still pops up on hover instead of snapping to its hidden offset. */
.pv-bento.pv-rise-armed .pv-bento-card.pv-rise:hover { transform: translateY(-8px); }
@keyframes pv-card-border-in {
  0%   { border-color: var(--hair); box-shadow: 0 0 0 0 transparent; }
  45%  {
    border-color: color-mix(in oklab, var(--card-accent) 90%, transparent);
    box-shadow: 0 0 30px -4px color-mix(in oklab, var(--card-accent) 60%, transparent);
  }
  100% { border-color: var(--hair); box-shadow: 0 0 0 0 transparent; }
}
/* Original asymmetric bento: Classic is the big feature (top-left) but now
   PORTRAIT, Pineapple + Caramel portrait, and the CTA a landscape box. Pineapple
   bottom-aligns beside the taller Classic for the offset / staggered look. */
/* Aligned two-row bento. Row 1: Classic (tall portrait, its larger image sets
   the row height) + Pineapple stretched to match — tops & bottoms align. Row 2:
   the CTA landscape tile sets the row height + Caramel stretched to match — its
   top & bottom align to the bespoke CTA. align-self:stretch on the partner card
   makes it grow with whichever sibling owns the row's intrinsic height. */
.pv-bento-card--a   { grid-column: span 5; aspect-ratio: 4 / 5.4; --card-accent: #e8ecf2; align-self: start; }
.pv-bento-card--b   { grid-column: span 7; --card-accent: #d4f134; align-self: stretch; }
.pv-bento-card--c   { grid-column: span 4; --card-accent: #e5a93b; align-self: stretch; }
.pv-bento-card--cta { grid-column: span 8; aspect-ratio: 16 / 9; --card-accent: var(--accent); align-self: start; }

.pv-bento-card__media { position: absolute; inset: 0; z-index: 0; }
.pv-bento-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  /* scaled hard from the top so the wordmark + slogan at the foot of each shot
     are cropped fully out of frame (matches the .pv-shot crop in the film
     panels). The portrait b/c cards share the image's aspect, so they need the
     heavier zoom to clear the slogan. */
  transform: scale(1.22);
  transform-origin: top center;
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
/* gentle settle within the frame — the glow does the talking */
.pv-bento-card:hover .pv-bento-card__media img { transform: scale(1.28); }

/* Halftone — micro dot screen, multiply-blended (reference detail). */
.pv-bento-card__halftone {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image: radial-gradient(circle, rgba(0, 0, 0, 0.55) 1px, transparent 1px);
  background-size: 4px 4px;
  opacity: 0.16;
  mix-blend-mode: multiply;
}
/* Bottom scrim so the standing label always reads. Anchored to the CARD (not the
   parallaxing .pv-bento-card__media, whose --pvy translate would otherwise drag the
   scrim up off the bottom edge and expose un-darkened image there). */
.pv-bento-card--a::after,
.pv-bento-card--b::after,
.pv-bento-card--c::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;            /* above media + halftone, below the label (z2) + flare (z4) */
  pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(to top,
    rgba(0, 0, 0, 0.82) 0%,
    rgba(0, 0, 0, 0.5) 26%,
    rgba(0, 0, 0, 0.16) 46%,
    transparent 66%);
}

/* Standing label (always visible) */
.pv-bento-card__label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  padding: clamp(18px, 2.4vw, 30px);
}
.pv-bento-card__ix {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  /* index picks up each flavour's own accent (silver / lime / caramel), muted so
     it stays a quiet detail — lets the monochrome cards carry a touch of flavour. */
  color: color-mix(in oklab, var(--card-accent, #f5f5f7) 72%, var(--ink-dim));
}
.pv-bento-card__name {
  font-family: var(--display-stack);
  font-weight: 500;
  font-size: clamp(22px, 2.4vw, 34px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  color: #f5f5f7;
  margin-top: 4px;
}
.pv-bento-card__meta {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(245, 245, 247, 0.62);
  white-space: nowrap;
}

/* Hover veil + animated-gradient "View — Flavour" pill (reference detail) */
.pv-bento-card__hover {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.42);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  backdrop-filter: blur(8px) saturate(140%);
  opacity: 0;
  transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.pv-bento-card:hover .pv-bento-card__hover,
.pv-bento-card:focus-visible .pv-bento-card__hover { opacity: 1; }
.pv-bento-pill {
  padding: 1.5px;
  border-radius: 980px;
  background: linear-gradient(110deg, var(--card-accent), #f7f7f9 48%, var(--card-accent));
  background-size: 220% 100%;
  animation: pv-gradient-shift 6s ease infinite;
  transform: translateY(8px) scale(0.96);
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.pv-bento-card:hover .pv-bento-pill { transform: none; }
.pv-bento-pill > span {
  display: block;
  padding: 9px 20px;
  border-radius: 980px;
  background: rgba(10, 10, 12, 0.86);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  font-family: var(--display-stack);
  font-size: 15px;
  letter-spacing: -0.01em;
  color: #f5f5f7;
}
.pv-bento-pill em {
  font-style: italic;
  color: var(--card-accent);
}
@keyframes pv-gradient-shift {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  /* The allocation image reveal (staggered fade + settle-zoom) is a SIGNATURE
     moment and runs regardless of reduced-motion — matching the hero/scene
     cascades (the environment runs with reduced-motion ON, so gating it here
     silently disabled the reveal the user asked for). The reveal settles at
     scale(1.22) — the crop that hides the wordmark/slogan. Only the idle hover
     zoom is dropped. */
  .pv-bento-card:hover .pv-bento-card__media img { transform: scale(1.22); transition: none; }
  .pv-bento-pill { transition: none; transform: none !important; animation: none; }
}

/* Editorial / CTA card — deliberately different from the photo cards. */
.pv-bento-card--cta {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: clamp(26px, 3vw, 42px);
  background:
    radial-gradient(120% 140% at 0% 0%, rgba(232, 236, 242, 0.12) 0%, transparent 52%),
    linear-gradient(180deg, #121216 0%, #0a0a0c 100%);
  border-color: rgba(255, 255, 255, 0.22);
}
.pv-bento-card--cta .pv-cta-k {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}
.pv-bento-card--cta .pv-cta-h {
  font-family: var(--display-stack);
  font-weight: 500;
  font-size: clamp(26px, 3vw, 42px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: #f5f5f7;
  max-width: 14ch;
}
.pv-bento-card--cta .pv-cta-h em { font-style: italic; color: var(--accent); }
.pv-bento-card--cta .pv-cta-link {
  font-family: var(--display-stack);
  font-size: 16px;
  letter-spacing: -0.01em;
  color: #f5f5f7;
  align-self: flex-start;
}
.pv-bento-card--cta .pv-cta-link::after { content: " ›"; color: var(--accent); }
.pv-bento-card--cta:hover .pv-cta-link { color: var(--accent); }
/* Grain dot texture in the order block (same dot screen as the allocation card
   images + stats panel), clipped inside the card by its rounded corners +
   overflow:hidden. Sits above the card's gradient but BELOW the copy (which is
   lifted to z-index 2) so "Reserve all three…" stays crisp. */
.pv-bento-card--cta::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  border-radius: inherit;
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
  background-size: 4px 4px;
}
.pv-bento-card--cta .pv-cta-k,
.pv-bento-card--cta .pv-cta-h,
.pv-bento-card--cta .pv-cta-link { position: relative; z-index: 2; }

/* Small "from Nyathi" disclaimer link under the PRE-ORDER COLLECTION button. */
.ss-from-nyathi {
  display: inline-block;
  margin-top: 16px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 0.3s ease, border-color 0.3s ease;
}
.ss-from-nyathi:hover {
  color: var(--ink-mid);
  border-bottom-color: currentColor;
}

@media (max-width: 820px) {
  .pv-bento { grid-template-columns: 1fr; }
  /* full-width stacked cards on phones (no offset margin) */
  .pv-bento-card--a,
  .pv-bento-card--b,
  .pv-bento-card--c,
  .pv-bento-card--cta {
    grid-column: 1 / -1;
    align-self: stretch;
    margin-top: 0;
  }
  .pv-bento-card--a,
  .pv-bento-card--b,
  .pv-bento-card--c { aspect-ratio: 4 / 5; }
}

/* ==============================================================================
   HERO HEADLINE REVEAL — the second line resolves ONCE from "PURE CLASSIC." into
   the slogan tail "PURE IN SPIRIT.", each letter fading up in sequence. No flip,
   no repeat. JS swaps the text once and splits it into .pv-letter spans, then
   adds .is-revealed to cascade them in.
   ============================================================================== */
.ss-hero h1 .pv-flip {
  display: inline-block;
}
/* GSAP hero cascade: per-letter (headline) + per-word (lede) entrance units */
.ss-hero .ss-h1-line .pv-rch,
.ss-hero .ss-lede .pv-rword {
  display: inline-block;
  will-change: transform, opacity;
}
/* Exit: classic eases out with a soft blur + lift (no snap). */
.ss-hero h1 .pv-flip.is-leaving {
  opacity: 0;
  transform: translateY(-0.22em);
  filter: blur(7px);
  transition:
    opacity 0.36s cubic-bezier(0.4, 0, 0.2, 1),
    transform 0.36s cubic-bezier(0.4, 0, 0.2, 1),
    filter 0.36s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Enter: each letter eases up from blurred + lowered to crisp. */
.ss-hero h1 .pv-letter {
  display: inline-block;
  /* Bright LIGHT-grey silver so it never gets lost on the film. Two background
     layers: a moving white sheen (off-screen at rest) over the base silver —
     the sheen is animated across the letters once revealed (shimmer). */
  background-image:
    linear-gradient(105deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0.98) 50%, rgba(255, 255, 255, 0) 60%),
    linear-gradient(180deg, #fbfbfc 0%, #eceef2 52%, #dde1e8 100%);
  background-size: 250% 100%, 100% 100%;
  background-position: 200% 0, 0 0;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* lighter shadow so the bright silver isn't dragged down to grey */
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.35);
  opacity: 0;
  transform: translateY(0.55em);
  filter: blur(8px);
  transition:
    opacity 0.95s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.95s cubic-bezier(0.16, 1, 0.3, 1),
    filter 0.95s cubic-bezier(0.16, 1, 0.3, 1);
  /* slower per-letter cascade as CLASSIC morphs into PURE IN SPIRIT */
  transition-delay: calc(var(--li, 0) * 0.1s);
}
.ss-hero h1 .pv-flip.is-revealed .pv-letter {
  opacity: 1;
  transform: none;
  filter: blur(0);
  /* a bright sheen sweeps ONCE across the words (staggered per letter → travels
     left→right) after they have animated in — slow, with a white glow as it
     passes so it reads as a clear bright shimmer */
  animation:
    pv-headline-shimmer 0.95s ease-in-out calc(1.4s + var(--li, 0) * 0.045s) 1 both,
    pv-headline-glow 0.95s ease-in-out calc(1.4s + var(--li, 0) * 0.045s) 1 both;
}
@keyframes pv-headline-shimmer {
  from { background-position: 200% 0, 0 0; }
  to   { background-position: -160% 0, 0 0; }
}
@keyframes pv-headline-glow {
  0%, 100% { text-shadow: 0 1px 10px rgba(0, 0, 0, 0.35); }
  50% {
    text-shadow:
      0 1px 10px rgba(0, 0, 0, 0.35),
      0 0 16px rgba(255, 255, 255, 0.95),
      0 0 34px rgba(255, 255, 255, 0.6);
  }
}
/* (The CLASSIC → PURE IN SPIRIT morph cascade AND its shimmer run regardless of
   prefers-reduced-motion — it's a signature hero moment, per brand direction.) */

/* ==============================================================================
   FULL-WIDTH QUOTE — the master-distiller pull-quote over a full-bleed product
   image. The image parallax-shifts on scroll (JS, [data-quote-parallax]) and the
   quote lights up word-by-word as the section scrolls through view (JS toggles
   .is-lit on each .pv-qw). Keeps the .ss-pull blockquote type styles.
   ============================================================================== */
.pv-quote {
  position: relative;
  overflow: hidden;
  padding: clamp(120px, 24vh, 260px) 22px;
  background: #000;
  border-top: 1px solid var(--hair);
}
.pv-quote-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}
/* much taller than the frame (top:-28% + height:156%) so it has generous room
   to parallax up/down without ever exposing an edge. JS translates it on scroll
   — the large travel makes the parallax clearly visible. */
.pv-quote-bg img {
  position: absolute;
  left: 0;
  top: -28%;
  width: 100%;
  height: 156%;
  object-fit: cover;
  object-position: center 30%;
  will-change: transform;
}
/* Legibility scrim — darker at the rim, clearer over the bottle centre. */
.pv-quote::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    radial-gradient(ellipse 70% 60% at 50% 46%, rgba(0, 0, 0, 0.34) 0%, rgba(0, 0, 0, 0.72) 78%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.5) 0%, transparent 24%, transparent 76%, rgba(0, 0, 0, 0.6) 100%);
}
.pv-quote-wrap {
  position: relative;
  z-index: 2;
  max-width: 940px;
  margin: 0 auto;
  text-align: center;
}
/* Word-reveal: words sit dim + slightly lowered + soft until lit by scroll
   progress, then ease up to crisp white. The per-word transition is what gives
   the staggered "reveals as you scroll" cascade. */
.pv-qw {
  display: inline-block;
  color: rgba(245, 245, 247, 0.1);
  transform: translateY(0.16em);
  transition:
    color 0.9s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.pv-qw.is-lit { color: #f5f5f7; transform: none; }
.pv-qw--em { font-style: italic; }
.pv-qw--em.is-lit { color: #ffffff; }
@media (prefers-reduced-motion: reduce) {
  /* parallax kept on (brand direction); only soften the per-word transform */
  .pv-qw { transform: none; transition: color 0.3s ease; }
}

/* ==============================================================================
   ATELIER — pinned card carousel. The section is a tall scroll runway; an inner
   sticky stage holds the larger cards. GSAP (ScrollTrigger, scrubbed — see
   js/pure-vodka.js initAtelierCarousel) flies each card diagonally from the
   bottom-right, up to full size at centre, then off to the top-left as the page
   scrolls. No-JS / reduced-motion falls back to a simple image grid.
   ============================================================================== */
.pv-atelier {
  position: relative;
  height: 360vh; /* runway: long enough to read the pinned stats + card stream */
  /* full-bleed: the whole section IS the pinned stage now (the stats live
     inside it), so break out of the slab's 22px side padding. */
  margin: 0 -22px;
}
.pv-atelier-pin {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
}
/* Pinned CRAFTED TO PERFECTION backdrop — stays put while the cards fly over. */
.pv-atelier-copy {
  position: absolute;
  inset: 0;
  z-index: 0;
  margin: 0;
  max-width: none;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  gap: clamp(14px, 2.6vh, 30px);
  padding: clamp(56px, 11vh, 130px) 22px;
  pointer-events: none;
}
.pv-atelier-copy > * { margin: 0; }
.pv-atelier-copy .ss-grid { width: 100%; max-width: 1040px; }
/* compact the stat cells so heading + grid fit the pinned viewport */
.pv-atelier-copy .ss-cell {
  min-height: 0;
  /* roomier top/bottom so the value + description never crowd the cell edges */
  padding: clamp(24px, 3.4vh, 38px) clamp(16px, 1.6vw, 24px);
}
/* Mobile: the atelier pins to 100vh and the four stat rows stack into a single
   column — top-anchor and tighten type / padding / gaps so the LAST row's
   description line ("100% organic locally-sourced…") is never clipped by the
   pinned viewport bottom (was cut off). */
@media (max-width: 600px) {
  .pv-atelier-copy {
    /* Vertically CENTRE the pinned stats block (was top-anchored) and give it real
       breathing room. All vh-based: tall phones get generous space, short phones
       stay compact enough that the last row's description never clips. */
    justify-content: center;
    gap: clamp(10px, 2vh, 22px);
    padding: clamp(40px, 7vh, 72px) 16px clamp(28px, 5vh, 48px);
  }
  .pv-atelier-copy .ss-slab-title { font-size: clamp(20px, 6vw, 28px); }
  .pv-atelier-copy .ss-slab-sub { font-size: 12.5px; line-height: 1.3; }
  /* extra space before the table (user request) */
  .pv-atelier-copy .ss-grid { gap: 1px; margin-top: clamp(6px, 1.6vh, 18px); }
  /* more top/bottom padding inside each stat cell (user request), vh-scaled so it
     grows on tall phones but still fits four stacked rows on short ones. */
  .pv-atelier-copy .ss-cell { padding: clamp(16px, 2.6vh, 30px) 16px; }
  .pv-atelier-copy .ss-cell .ss-kk { font-size: 10px; letter-spacing: 0.14em; }
  .pv-atelier-copy .ss-cell .ss-vv { font-size: clamp(24px, 7vw, 32px); margin-top: 3px; }
  .pv-atelier-copy .ss-cell .ss-nn { font-size: 12px; line-height: 1.3; margin-top: 4px; padding-top: 6px; }
}
.pv-carousel {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
/* Larger cards stacked centre-stage. GSAP (ScrollTrigger, scrubbed) flies each
   one diagonally from the bottom-right to the top-left as the pinned section
   scrolls, scaling up as it passes through the centre. CSS only sets the resting
   box; transform + opacity are owned by GSAP. Centring is done with negative
   margins (not translate) so GSAP's transform doesn't fight it. */
.pv-carousel-item {
  --w: clamp(320px, 41vw, 580px);
  --h: clamp(420px, 54vw, 740px);
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--w);
  height: var(--h);
  margin: calc(var(--h) * -0.5) 0 0 calc(var(--w) * -0.5);
  border-radius: 18px;
  overflow: hidden;
  border: 1px solid var(--hair);
  background: #0b0b0d;
  box-shadow: 0 30px 80px 10px rgba(0, 0, 0, 0.6);
  opacity: 0; /* GSAP fades cards in; prevents a flash of the full stack */
  will-change: transform, opacity;
}
/* Mobile: shrink the cards so they no longer span almost the full width — the
   stream then reads as a spread-out diagonal (corners stay visible) instead of
   each card overlaying and cropping the one behind it. Pairs with the larger
   step + travel set in initAtelierCarousel. */
@media (max-width: 600px) {
  .pv-carousel-item {
    --w: clamp(190px, 62vw, 300px);
    --h: clamp(250px, 82vw, 400px);
  }
}
.pv-carousel-box {
  position: absolute;
  inset: 0;
}
.pv-carousel-box::before {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.35),
    rgba(0, 0, 0, 0) 30%,
    rgba(0, 0, 0, 0) 52%,
    rgba(0, 0, 0, 0.6)
  );
}
.pv-carousel-item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  /* crop the wordmark + slogan at the foot of each shot */
  transform: scale(1.14);
  transform-origin: top center;
}
/* Plain caption over the card image — bottom-left, legible mono label. */
.pv-carousel-cap {
  position: absolute;
  left: 20px;
  bottom: 18px;
  z-index: 4;
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(11px, 1.2vw, 14px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.65);
  white-space: nowrap;
}
.pv-carousel-hint {
  position: absolute;
  z-index: 4;
  left: 0;
  right: 0;
  bottom: clamp(28px, 6vh, 64px);
  text-align: center;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: rgba(245, 245, 247, 0.45);
  pointer-events: none;
}
/* NOTE: the carousel intentionally runs its GSAP scroll animation even under
   prefers-reduced-motion (per brand direction — this motion is core to the
   page). The old reduced-motion image-grid fallback was removed because it
   forced a horizontal row of cards. */

/* ==============================================================================
   FLAVOUR SCENE CASCADE — mirror the hero's entrance on the Pineapple & Caramel
   chapters: the flavour NAME reveals letter-by-letter and the sub-headline copy
   word-by-word (rise + de-blur), staggered off a per-unit --si index. JS
   (initSceneCascade) splits the text into .pv-sch / .pv-sword spans; the cascade
   re-fires every time the scene scrolls back into view (.ss-scene.is-in). The
   scene box no longer fades as a block (the cascade carries the reveal), so the
   letters read clearly instead of cross-fading under a container opacity ramp.
   ============================================================================== */
/* Shared entrance keyframes. Driven by ANIMATION (not transition) so they fire
   reliably every time the trigger class is (re)applied by the scrub / observer —
   the per-letter cascade was silently snapping when its container faded in within
   the same frame; an animation always runs. --- */
@keyframes pv-cascade-in {
  from { opacity: 0; transform: translateY(0.55em); filter: blur(7px); }
  to   { opacity: 1; transform: none;              filter: blur(0); }
}
@keyframes pv-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Letters/words hidden at rest; the animation (added with the trigger class)
   carries them in. */
/* the flavour name is a single word — never let the split letters break across
   lines */
.ss-scene h2 { white-space: nowrap; }
.ss-scene h2 .pv-sch,
.ss-scene p .pv-sword { display: inline-block; opacity: 0; }
.ss-scene .ss-kicker  { opacity: 0; }
/* Sequential reveal — the elements arrive ONE AFTER ANOTHER, not together:
   1) the silver badge fades in (+ its glow), 2) the flavour name cascades
   letter-by-letter, 3) the sub-headline copy cascades word-by-word. The base
   delays (0.34s / 0.72s) space the three groups so each clearly follows the
   previous, matching the hero's staggered entrance. */
.ss-scene.is-in h2 .pv-sch  { animation: pv-cascade-in 0.7s cubic-bezier(0.16, 1, 0.3, 1) calc(0.34s + var(--si, 0) * 0.05s) both; }
.ss-scene.is-in p .pv-sword { animation: pv-cascade-in 0.7s cubic-bezier(0.16, 1, 0.3, 1) calc(0.72s + var(--si, 0) * 0.03s) both; }
/* EXIT — when the scene leaves (.is-leaving, set by JS only AFTER it has
   entered), hold the split letters/words solid so the block-level staggered
   fade-out above (text → title → badge, soft lift + blur) actually carries them
   out smoothly instead of the per-letter cascade snapping them to hidden. */
.ss-scene.is-leaving h2 .pv-sch,
.ss-scene.is-leaving p .pv-sword { opacity: 1; animation: none; }
/* The badge enters via an ANIMATION (fade + glow), so its filled end-state would
   defeat a plain exit transition (snap). Give it an explicit staggered fade-OUT
   keyframe instead — fires last (0.16s) to mirror the block choreography. */
@keyframes pv-fade-out {
  from { opacity: 1; transform: none;               filter: blur(0); }
  to   { opacity: 0; transform: translateY(-13px);  filter: blur(4px); }
}
.ss-scene.is-leaving .ss-kicker {
  animation: pv-fade-out 0.5s cubic-bezier(0.4, 0, 0.6, 1) 0.16s both;
}
/* NB: these signature cascades intentionally run REGARDLESS of
   prefers-reduced-motion — matching the hero headline (the brand-approved
   reference). The environment runs with reduced-motion ON, so gating them behind
   it silently disabled the whole cascade. */

/* ==============================================================================
   BADGE ENTRANCE GLOW — the silver chapter plates (THE FOUNDATION / INFUSION /
   INDULGENCE) pulse a bright border glow as they scroll into view. One-shot per
   entrance (re-fires when .is-in re-toggles). The 0%/100% keyframes match the
   badge's base box-shadow so it settles with no jump, leaving hover free to take
   over. The shimmer sweep (::after) is unaffected.
   ============================================================================== */
@keyframes pv-badge-enter-glow {
  0% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.95),
      inset 0 0 0 1px rgba(120, 124, 132, 0.22),
      0 8px 22px rgba(0, 0, 0, 0.34),
      0 0 0 0 rgba(255, 255, 255, 0);
  }
  42% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.95),
      inset 0 0 0 1px rgba(255, 255, 255, 0.95),
      0 8px 22px rgba(0, 0, 0, 0.34),
      0 0 24px 4px rgba(255, 255, 255, 0.72),
      0 0 46px 10px rgba(255, 255, 255, 0.32);
  }
  100% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.95),
      inset 0 0 0 1px rgba(120, 124, 132, 0.22),
      0 8px 22px rgba(0, 0, 0, 0.34),
      0 0 0 0 rgba(255, 255, 255, 0);
  }
}
.ss-hero.is-in .ss-eyebrow {
  animation: pv-badge-enter-glow 1.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* scene badge: fade in FIRST (it leads the cascade), with the border glow */
.ss-scene.is-in .ss-kicker {
  animation:
    pv-fade-in 0.55s cubic-bezier(0.16, 1, 0.3, 1) 0.06s both,
    pv-badge-enter-glow 1.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* runs regardless of prefers-reduced-motion (matches the hero badge entrance) */

/* ==============================================================================
   MAIN-SECTION HEADLINE CASCADE — the printed sections (CRAFTED TO PERFECTION,
   THE ALLOCATION, SECURE YOUR BOTTLE) get the same hero-style entrance: the
   headline reveals letter-by-letter (rise + de-blur), then its sub-heading fades
   in slowly AFTER the letters land. JS (initSectionCascades) splits each headline
   into .pv-cch spans (tagged with a running --si) and tags each sub-heading
   .pv-subfade; both fire off the section's existing .ss-reveal.is-visible state.
   ============================================================================== */
/* Per-word wrapper around the split letters: keeps a word's letters together so
   a line can only break at a SPACE, never mid-word. Without this, every letter is
   an independent inline-block and "THE ALLOCATION" shatters across lines on a
   narrow (mobile) column. Used by both .pv-cch (sections) and .pv-cl (closer). */
.pv-cword { display: inline-block; white-space: nowrap; }
.pv-cch { display: inline-block; opacity: 0; }
.ss-reveal.is-visible .pv-cch {
  animation: pv-cascade-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) calc(var(--si, 0) * 0.045s) both;
}
/* sub-heading waits for the headline letters to finish, then fades up slowly */
.pv-subfade { opacity: 0; }
.ss-reveal.is-visible .pv-subfade {
  animation: pv-cascade-in 1.05s cubic-bezier(0.16, 1, 0.3, 1) var(--subd, 1.1s) both;
}
/* runs regardless of prefers-reduced-motion (matches the hero headline) */

/* ==============================================================================
   STAT-VALUE CASCADE — the non-numeric values (ACTIVE / GRAIN) can't count up,
   so instead they reveal letter-by-letter like the hero headline. JS
   (initAtelierStats) splits them into .pv-vch spans when the cell's turn comes;
   each carries its own silver gradient so per-letter opacity actually fades the
   visible glyph (a parent background-clip:text wouldn't fade through child spans).
   ============================================================================== */
.ss-cell .ss-vv .pv-vch {
  display: inline-block;
  color: #f5f5f7; /* solid silver (matches .ss-vv); per-letter opacity still fades fine */
  opacity: 0;
}
.ss-cell .ss-vv.is-cascading .pv-vch {
  animation: pv-cascade-in 0.7s cubic-bezier(0.16, 1, 0.3, 1) calc(var(--vi, 0) * 0.06s) both;
}
/* runs regardless of prefers-reduced-motion (matches the hero / count-up reveals,
   which also run regardless per brand direction) */

/* ==============================================================================
   STATS GRAIN OVERLAY — the same dot-screen texture used on the allocation card
   images (.pv-bento-card__halftone), here clipped inside the stats grid in the
   CRAFTED TO PERFECTION section. Dark multiply dots vanish on the near-black
   panel, so this uses faint LIGHT dots to read as a subtle grain. Absolutely
   positioned (not a grid item) and pointer-events:none so it never affects the
   stat cells; clipped by the grid's existing overflow:hidden + rounded corners.
   ============================================================================== */
.ss-grid { position: relative; }
.ss-grid::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  border-radius: inherit;
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  background-size: 4px 4px;
  opacity: 0.85;
  /* Diagonal fade — grain holds solid from the top-left, then fades out toward the
     bottom-right, starting ~60% along the TL→BR diagonal (135deg points to BR). */
  -webkit-mask-image: linear-gradient(135deg, #000 0%, #000 60%, transparent 100%);
  mask-image: linear-gradient(135deg, #000 0%, #000 60%, transparent 100%);
}

/* the small note text under each stat value fades in SLOWLY, after the value
   above it has counted up / cascaded in (delay ≈ the count-up duration). */
/* The note types in via JS (initAtelierStats), delayed after its value — JS sets
   opacity to 1 when its turn comes, so it stays hidden until then. */
.ss-cell .ss-nn { opacity: 0; }
/* blinking caret while the note types in (JS toggles .is-typing) */
.ss-cell .ss-nn.is-typing::after {
  content: "";
  display: inline-block;
  width: 1px;
  height: 1em;
  margin-left: 2px;
  vertical-align: -0.12em;
  background: var(--ink-mid);
  animation: pv-caret 0.7s steps(1) infinite;
}
/* runs regardless of prefers-reduced-motion */

/* ==============================================================================
   CLOSER FINALE CASCADE — give "PURE SPIRIT." and "CRAFTED FOR THE
   UNCOMPROMISING." the EXACT hero "PURE IN SPIRIT" treatment: each letter rises
   + de-blurs in, then a one-shot bright shimmer sweeps across with a white glow.
   Same keyframes (pv-headline-shimmer / pv-headline-glow), same per-letter
   timing as the hero. A single running --li spans both lines so the sweep travels
   continuously across the whole finale. JS (initCloserCascade) splits the text;
   it fires off .ss-film-closer.is-in (re-glints each time the finale scrolls in).
   ============================================================================== */
.ss-film-closer .pv-cl {
  display: inline-block;
  /* two layers: a moving white sheen (off-screen at rest) over the base silver */
  background-image:
    linear-gradient(105deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0.98) 50%, rgba(255, 255, 255, 0) 60%),
    linear-gradient(180deg, #fbfbfc 0%, #eceef2 52%, #dde1e8 100%);
  background-size: 250% 100%, 100% 100%;
  background-position: 200% 0, 0 0;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.35);
  opacity: 0;
}
/* Each letter rises + de-blurs in (pv-cascade-in), then the bright shimmer sweep
   + white glow run once. The shimmer starts earlier than the hero (0.85s base)
   per request; a single running --li spans both lines so the sweep travels
   continuously across the whole finale. */
.ss-film-closer.is-in .pv-cl {
  animation:
    pv-cascade-in 0.95s cubic-bezier(0.16, 1, 0.3, 1) calc(var(--li, 0) * 0.06s) both,
    pv-headline-shimmer 0.95s ease-in-out calc(0.85s + var(--li, 0) * 0.045s) 1 both,
    pv-headline-glow 0.95s ease-in-out calc(0.85s + var(--li, 0) * 0.045s) 1 both;
}
/* runs regardless of prefers-reduced-motion (matches the hero "PURE IN SPIRIT") */

/* ==============================================================================
   BUTTON SHEEN — a single bright highlight sweeps across the pill CTAs on hover
   (Pre-Order nav, banner CTA, Pre-Order Collection). Layered over the existing
   lift/colour change. The pill clips the sheen via overflow:hidden + its pill
   radius; the sheen sits above the fill so it reads as a glint passing over.
   ============================================================================== */
.ss-root .ss-btn,
.ss-nav-cta,
.ss-banner-cta {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.ss-root .ss-btn::after,
.ss-nav-cta::after,
.ss-banner-cta::after {
  content: "";
  position: absolute;
  z-index: 1;
  top: 0;
  bottom: 0;
  left: 0;
  width: 60%;
  background: linear-gradient(100deg, transparent 0%, rgba(255, 255, 255, 0.55) 50%, transparent 100%);
  transform: translateX(-180%) skewX(-14deg);
  pointer-events: none;
}
.ss-root .ss-btn:hover::after,
.ss-nav-cta:hover::after,
.ss-banner-cta:hover::after {
  animation: pv-btn-sheen 0.85s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes pv-btn-sheen {
  from { transform: translateX(-180%) skewX(-14deg); }
  to   { transform: translateX(230%) skewX(-14deg); }
}
/* the text-link CTA isn't a pill — give it a small arrow nudge on hover instead */
.ss-link-arrow::after { transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); display: inline-block; }
.ss-link-arrow:hover::after { transform: translateX(4px); }

/* ==============================================================================
   ALLOCATION IMAGES — slow silky zoom-reveal (staggered, cascading) + a subtle
   scroll parallax. The reveal (scale + fade) lives on the IMG; the parallax
   translate lives on the MEDIA wrapper (JS sets --pvy via initBentoParallax) so
   the two transforms never fight — and the fixed halftone sibling stays put while
   the photo drifts behind it. Runs regardless of reduced-motion (brand direction).
   ============================================================================== */
/* Fade (forwards — stays visible) + zoom (backwards — reverts to base scale so the
   hover zoom still works) split into two animations so neither blocks the other. */
@keyframes pv-img-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes pv-img-zoom { from { transform: scale(1.45); } to { transform: scale(1.22); } }
/* Armed hidden once JS takes over, so the images don't show at base before the
   reveal fires (no-JS leaves them visible). Triggered when the GRID itself scrolls
   into view (JS adds .is-img-in via a per-frame check) — NOT the section top, which
   enters far earlier (the heading) while these images are still below the fold. */
.pv-bento.pv-armed .pv-bento-card__media img { opacity: 0; }
.pv-bento.is-img-in .pv-bento-card--a .pv-bento-card__media img { animation: pv-img-fade 1.1s ease 0.05s forwards, pv-img-zoom 1.7s cubic-bezier(0.22, 1, 0.36, 1) 0.05s backwards; }
.pv-bento.is-img-in .pv-bento-card--b .pv-bento-card__media img { animation: pv-img-fade 1.1s ease 0.25s forwards, pv-img-zoom 1.7s cubic-bezier(0.22, 1, 0.36, 1) 0.25s backwards; }
.pv-bento.is-img-in .pv-bento-card--c .pv-bento-card__media img { animation: pv-img-fade 1.1s ease 0.45s forwards, pv-img-zoom 1.7s cubic-bezier(0.22, 1, 0.36, 1) 0.45s backwards; }
/* parallax carrier — translated by JS within the image's scale(1.22) overflow
   headroom, so the card never shows a gap */
.pv-bento-card--a .pv-bento-card__media,
.pv-bento-card--b .pv-bento-card__media,
.pv-bento-card--c .pv-bento-card__media {
  transform: translate3d(0, var(--pvy, 0px), 0);
  will-change: transform;
}

/* ==============================================================================
   HERO SUB-HEADLINE — the white copy sits over bright film frames. The legibility
   scrim is now carried by the .ss-hero element itself (position:fixed; inset:0 —
   the full page), anchored to the bottom edge and fading up above the text, so it
   can never be cut off at the bottom. (Was a floating ::before box behind the lede
   whose magic-offset bottom didn't always reach the page edge.)
   ============================================================================== */
.ss-hero .ss-lede {
  position: relative;
}
