/* =========================================================
   Cinema Skin FX — interaction + motion layer
   Loads after styles.css. Everything here is enhancement:
   remove this file and the page still reads and works.
   ========================================================= */

/* ---------- Motion tokens ---------- */
:root{
  --ease-out:cubic-bezier(.16,.84,.44,1);
  --ease-soft:cubic-bezier(.22,.68,.32,1);
  --ease-back:cubic-bezier(.34,1.4,.5,1);
  --z-modal:300;
}

/* ---------- Cinematic grain ---------- */
body::after{
  content:"";
  position:fixed;inset:0;z-index:1;
  pointer-events:none;
  opacity:.035;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* scrollbar-gutter holds the space the scrollbar was using, so locking the
   body does not reflow the page sideways and jolt the scroll position. The
   handler above no longer closes the menu on that jolt either — belt and
   braces, because the same lock is used by the category modal. */
body.is-locked{overflow:hidden;}
html{scrollbar-gutter:stable;}

/* =========================================================
   Header: scroll progress + auto-hide
   ========================================================= */
/* Sits on the bar's bottom edge, full width — it reads as the bar's own
   base line rather than a stray rule floating inside it. */
.scroll-progress{
  position:absolute;
  left:0;right:0;bottom:-1px;
  height:2px;
  background:rgba(198,164,85,.15);
  overflow:hidden;
  transition:opacity .4s var(--ease-soft);
}
.site-header.is-cover .scroll-progress{opacity:0;}
.scroll-progress span{
  display:block;height:100%;width:0%;
  border-radius:var(--r-pill);
  background:linear-gradient(90deg,var(--gold-soft),var(--gold),var(--gold-accent));
  transform-origin:left;
}

/* Logo mark comes alive on hover */
.logo-mark .lm-spin{transform-origin:50% 50%;transition:transform 1.1s var(--ease-soft);}
.logo:hover .lm-spin{transform:rotate(180deg);}

/* =========================================================
   Mobile navigation — slide-in panel
   ========================================================= */
.nav-scrim{
  position:fixed;inset:0;z-index:98;
  background:rgba(14,14,14,.45);
  backdrop-filter:blur(3px);
  opacity:0;
  transition:opacity .35s var(--ease-soft);
}
.nav-scrim.is-on{opacity:1;}

@media (max-width:1024px){
  /* ⚠️ THE CLOSED PANEL MUST NOT BE IN THE LAYOUT.

     This panel is `position:fixed`, but `.site-header` carries a
     backdrop-filter — and a filtered ancestor becomes the containing block
     for its fixed descendants. So the panel is not viewport-fixed at all;
     it is positioned inside the header, and parking it one width past the
     right edge grew the document by exactly its own width. The whole page
     scrolled sideways by 320px at every width at or below 1024 — a real
     swipe on a phone, not a measurement artefact. It was invisible in
     testing because it only appears once the header leaves cover mode.

     `display:none` when closed is the only state that reliably contributes
     nothing. The slide survives it through `transition-behavior:
     allow-discrete` plus `@starting-style`; where those are not supported
     the panel simply appears instead of sliding, which is the right way for
     this to degrade. */
  /* ⚠️ IT IS THE WHOLE SCREEN NOW, NOT A 320px PANEL ON THE RIGHT, built
     to a supplied comp. The panel above it in styles.css carries the same
     shape; what lives here is the movement.

     The note above still holds and is the reason for every line of the
     positioning: .site-header carries a backdrop-filter, a filtered element
     is the containing block for its fixed children, and this <nav> is inside
     it — so "fixed" here means fixed to the header, not to the screen.
     top/left/right:0 therefore read off the header's own box, which spans
     the width and sits at y=0 whether it is stuck or in cover, and the
     height is a VIEWPORT UNIT, which resolves against the screen regardless
     of what the containing block is. That combination is what makes a
     full-screen overlay possible from in here at all.

     ⚠️ display:none WHEN CLOSED IS STILL THE POINT. Parking a sized panel
       inside a filtered header grew the document by its own width and put a
       real sideways swipe on every phone. Nothing about going full-screen
       changes that, and inset:0 would be worse: it would size to the header,
       which is 76px tall. */
  /* ⚠️ THE BOX IS inset:0, NOT height:100dvh, AND THE DIFFERENCE IS THE
       WHOLE FIX. Asked for against a photograph of the open menu on a phone
       where the panel stopped roughly 90px short of the bottom and the page
       showed through under it, behind the scrim.

       The scrim in that same photograph covered the screen to the last
       pixel. It is position:fixed;inset:0 and it sits OUTSIDE the header.
       That is the evidence: an inset-sized fixed box reaches the bottom of
       that device and a 100dvh one does not, so the panel is sized the way
       the thing that worked is sized.

       ⚠️ inset:0 ONLY WORKS ONCE THE HEADER STOPS BEING THE CONTAINING
         BLOCK — see .site-header.has-nav-open in styles.css. Without that
         rule this line is a REGRESSION, not a fix: it would size the panel
         to the header's own 76px box. The two changes ship together. */
  .nav{
    position:fixed;inset:0;
    width:auto;
    height:auto;
    display:none;
    flex-direction:column;align-items:stretch;
    padding:calc(14px + env(safe-area-inset-top)) 18px calc(20px + env(safe-area-inset-bottom));
    background:
      radial-gradient(120% 74% at 14% 0%, rgba(198,164,85,.20) 0%, rgba(198,164,85,0) 60%),
      linear-gradient(180deg, #151209 0%, #0A0906 100%);
    border-left:0;
    border-bottom:0;
    box-shadow:none;
    opacity:0;
    translate:0 -12px;
    transition:
      opacity .34s var(--ease-soft),
      translate .34s var(--ease-soft),
      display .34s allow-discrete;
    z-index:120;
    overflow-y:auto;
    overscroll-behavior:contain;
  }
  .nav.is-open{
    display:flex;
    opacity:1;
    translate:0 0;
  }
  @starting-style{
    .nav.is-open{opacity:0;translate:0 -12px;}
  }

  /* ⚠️ > a, NOT a. The two buttons on the floor of the menu are anchors
     too, and the bare selector dropped them to opacity:0 with a stagger
     delay meant for a destination list. nth-of-type counts only the six,
     because the plate and the floor are divs. */
  .nav > a{
    opacity:0;transform:translateX(14px);
    transition:opacity .4s var(--ease-out), transform .4s var(--ease-out), color .25s var(--ease-soft);
  }
  .nav.is-open > a{opacity:1;transform:none;}
  .nav.is-open > a:nth-of-type(1){transition-delay:.10s;}
  .nav.is-open > a:nth-of-type(2){transition-delay:.15s;}
  .nav.is-open > a:nth-of-type(3){transition-delay:.20s;}
  .nav.is-open > a:nth-of-type(4){transition-delay:.25s;}
  .nav.is-open > a:nth-of-type(5){transition-delay:.30s;}
  .nav.is-open > a:nth-of-type(6){transition-delay:.35s;}

  /* the plate and the floor arrive with it, from the other side of the list */
  .nav-head,.nav-foot{opacity:0;transition:opacity .38s var(--ease-out);}
  .nav.is-open .nav-head{opacity:1;transition-delay:.06s;}
  .nav.is-open .nav-foot{opacity:1;transition-delay:.40s;}
  .nav-toggle{position:relative;z-index:101;}
}

/* =========================================================
   Cursor spotlight (hero, the RDR quote card, CTA band)
   ========================================================= */
/* isolate + z-index:-1 puts the glow above the section's own background but
   below its content — without touching any child's `position`. */
[data-spotlight]{position:relative;isolation:isolate;}
/* ⚠️ .story is excluded: it uses ::after instead, for the reason below, and
   without this it would get both and the ::before would eat its ground. */
[data-spotlight]:not(.story)::before{
  content:"";
  position:absolute;inset:0;
  pointer-events:none;
  z-index:-1;
  opacity:0;
  transition:opacity .6s var(--ease-soft);
  background:radial-gradient(340px circle at var(--mx,50%) var(--my,50%),
             rgba(250,238,200,.42) 0%, rgba(250,238,200,0) 62%);
}
.cta-band[data-spotlight]::before{
  background:radial-gradient(380px circle at var(--mx,50%) var(--my,50%),
             rgba(198,164,85,.20) 0%, rgba(198,164,85,0) 64%);
}
[data-spotlight].is-lit::before{opacity:1;}

/* ⚠️ .story gets the same glow on ::AFTER, because its ::before is already a
   real ground layer and [data-spotlight]::before would have replaced it. That
   is also why the section does not simply carry the attribute: the shared rule
   would take the pseudo it already uses.

   Measured before this existed: with the section's own idle motion subtracted,
   the mouse was worth -3.5% there — which is to say nothing at all, on the one
   section between two that both answer.

   ::after sits ABOVE the content rather than behind it, so this has to be much
   fainter than the ::before version and has to let clicks through. .28 against
   the shared .42, over a section whose ground is already light. */
.story{position:relative;isolation:isolate;}
.story::after{
  content:"";
  position:absolute;inset:0;
  pointer-events:none;
  z-index:4;
  opacity:0;
  transition:opacity .6s var(--ease-soft);
  background:radial-gradient(300px circle at var(--mx,50%) var(--my,50%),
             rgba(250,238,200,.28) 0%, rgba(250,238,200,0) 64%);
  mix-blend-mode:soft-light;
}
.story.is-lit::after{opacity:1;}

/* ⚠️ .why answers already — it carries the attribute, it lights, and --mx
   tracks — but at 3.7% against 4.3% of its own idle motion it was under its
   own noise floor. The section is the lightest ground on the page and the
   shared champagne at .42 simply has nowhere to go on it, so this one gets a
   warmer, wider pool rather than a brighter one: a bigger radius moves more
   pixels for the same peak, which is what the eye reads as light moving. */
.why[data-spotlight]::before{
  background:radial-gradient(460px circle at var(--mx,50%) var(--my,50%),
             rgba(226,190,110,.30) 0%, rgba(226,190,110,0) 66%);
}


/* Split-text hero title */
.hero-title .word{
  display:inline-block;
  opacity:0;
  transform:translateY(26px) rotate(2deg);
  filter:blur(6px);
}
.hero-title.is-split.is-in .word{
  animation:wordIn .9s var(--ease-out) forwards;
  animation-delay:calc(var(--wi) * 68ms);
}
@keyframes wordIn{
  to{opacity:1;transform:none;filter:blur(0);}
}
/* The gradient-clipped word must not carry a filter — it breaks background-clip:text. */
.hero-title em.word{filter:none !important;}
/* "Close-Up" takes the same catch-light as the interview's gold word, and for
   the same reason: a ramp with a near-white stop that slides across the word
   means the whole word is that stop at some point in the cycle, and on a
   ground this light the pale stop IS the ground. Measured across the loop, the
   ramp version sat at 1.46:1; a solid base with a narrow band crossing it
   holds 1.95:1 and never dips to a vanish.

   ⚠️ Not a fix for gold-on-cream generally — see the note in BRAND.md. The
   accent word is the comp's colour and stays the comp's colour; this only
   removes the moment where it disappeared entirely. */
.hero-title em{
  background-image:
    linear-gradient(100deg,
      rgba(255,246,220,0) 43%, rgba(255,246,220,.5) 50%, rgba(255,246,220,0) 57%),
    linear-gradient(var(--gold-accent), var(--gold-accent));
  background-size:300% 100%, 100% 100%;
  background-position:170% 0, 0 0;
  background-repeat:no-repeat;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  animation:storyShine 7s linear infinite;
}

/* Floating products + cards */
[data-float]{animation:floaty 7s ease-in-out infinite;}
[data-float="2"]{animation-duration:8.4s;animation-delay:-1.6s;}
[data-float="3"]{animation-duration:9.6s;animation-delay:-3.2s;}
@keyframes floaty{
  0%,100%{transform:translateY(0);}
  50%    {transform:translateY(-11px);}
}
/* Parallax wrapper keeps float + pointer offset independent */
.parallax-wrap{display:contents;}


/* =========================================================
   Section heading rule
   ========================================================= */
.has-rule{position:relative;padding-bottom:14px;}
.has-rule::after{
  content:"";
  position:absolute;left:0;bottom:0;
  height:2px;width:0;
  background:linear-gradient(90deg,var(--gold),var(--gold-soft));
  border-radius:2px;
  transition:width 1s var(--ease-out) .2s;
}
.has-rule.is-in::after{width:74px;}

/* =========================================================
   Buttons: sheen sweep
   ========================================================= */
.btn{position:relative;overflow:hidden;}
.btn-sheen::after{
  content:"";
  position:absolute;top:0;bottom:0;left:-60%;
  width:45%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.62),transparent);
  transform:skewX(-18deg);
  transition:left .75s var(--ease-out);
  pointer-events:none;
}
.btn-sheen:hover::after,
.btn-sheen:focus-visible::after{left:130%;}

/* Press-down feedback. This one stays: it answers a click the visitor
   made, at the moment they made it, and it never moves the target before
   they reach it. */
.btn:active{transform:scale(.975);}
.col-card:active,.value-card:active{transform:scale(.99);}

/* =========================================================
   Tilt cards + glare
   ========================================================= */
[data-tilt]{
  transform-style:preserve-3d;
  transition:transform .5s var(--ease-soft), box-shadow .4s var(--ease-soft), border-color .4s var(--ease-soft);
}
[data-tilt].is-tilting{transition:transform .1s linear;}
[data-tilt]::before{
  content:"";
  position:absolute;inset:0;
  pointer-events:none;
  border-radius:inherit;
  opacity:0;
  transition:opacity .4s var(--ease-soft);
  background:radial-gradient(220px circle at var(--gx,50%) var(--gy,50%),
             rgba(250,238,200,.55) 0%, rgba(250,238,200,0) 60%);
}
[data-tilt]:hover::before{opacity:1;}
/* The glare is clipped to the card, but the foot sparkle sits ON the
   card's bottom rule and has to be free to grow past it — so the clip goes
   on the glare layer itself rather than on the card. */
.value-card{position:relative;}
.value-card::before{border-radius:inherit;overflow:hidden;}

/* =========================================================
   Process — the stepper's states live in routine.css, keyed on :checked
   ========================================================= */
/* The routine's active-step styling moved to routine.css with the rest of
   it: the state is a :checked radio now, not a class added on scroll. */

/* =========================================================
   Back to top
   ========================================================= */
.to-top{
  position:fixed;right:22px;bottom:22px;z-index:90;
  display:grid;place-items:center;
  width:46px;height:46px;border-radius:50%;
  background:var(--black);
  color:var(--champagne);
  box-shadow:0 14px 30px -12px rgba(0,0,0,.6);
  opacity:0;visibility:hidden;
  transform:translateY(14px) scale(.9);
  transition:opacity .4s var(--ease-soft), transform .4s var(--ease-back), visibility .4s;
}
.to-top.is-on{opacity:1;visibility:visible;transform:none;}
/* Was translateY(-3px). It is a 46px target that already sits in the
   corner under the pointer's approach — moving it on hover is the worst
   place on the page to do it. It brightens instead. */
.to-top:hover{
  background:#1C1A16;
  color:#FFF6DC;
  box-shadow:0 18px 36px -12px rgba(0,0,0,.7), 0 0 0 1px rgba(198,164,85,.5);
}
.to-top svg{
  width:18px;height:18px;
  fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round;
}

/* =========================================================
   Inquire — the floating CTA
   ========================================================= */
/* ⚠️ THE LEFT CORNER, AND IT ANSWERS TO NOBODY NOW. This used to stack on
   .to-top and every offset was derived from that button's height; moved
   across, it simply takes the same 22px margin .to-top uses on its own side.
   Nothing else in the stylesheets is position:fixed down here. */
.inq-fab{
  position:fixed;left:22px;bottom:22px;z-index:90;
  width:56px;height:56px;padding:0;
  border-radius:50%;
  justify-content:center;
  /* ⚠️ BLACK DISC, CHAMPAGNE GLYPH — AND THE GOLD VERSION IS WHY. Built gold
     first, on the reasoning that gold is this page's CTA colour. Rendered and
     looked at, it barely registered: every section this button floats over is
     a warm cream, and gold on cream is the one pairing BRAND.md singles out
     as not clearing contrast. Three treatments were rendered at the same
     frozen frame and compared — gold disc, gold disc with a dark ring, and
     this — and this is the only one the eye goes to first.
     ⚠️ IT IS ONLY DARK BECAUSE THE FOOTER IS THE PAGE'S ONLY DARK SCENE and
     this button is already standing down by then. data-scene is gold, sand
     or sea everywhere it is actually on screen. Give the page another noir
     section mid-scroll and this disc needs rethinking, not just moving.
     ⚠️ .btn-gold IS DELIBERATELY OFF THE CLASS LIST. It sets a gradient this
     would have to override; taking the class off means the disc is stated
     once instead of twice. */
  background:var(--black);
  color:var(--champagne);
  /* ⚠️ .btn IS overflow:hidden (interactive.css, just above .btn-sheen::after,
     which needs its sweep clipped to the button). Inherited here it clipped
     the pulse rings to this button's own circle — they animated correctly,
     scaled to 1.85, and not one pixel of them ever left the disc. Measured,
     not guessed: ::before reported transform matrix(1.85…) with the ring
     nowhere on screen. This button has no sheen, so there is nothing here
     that wants clipping. */
  overflow:visible;
  box-shadow:0 14px 30px -10px rgba(120,90,25,.6);
  opacity:0;visibility:hidden;
  transform:translateY(14px) scale(.9);
  /* ⚠️ THE WHOLE LIST IS RESTATED, AND THAT IS THE POINT. transition is a
     shorthand: it resets every part it does not name. Writing just opacity
     here would silently drop the box-shadow, background, border and colour
     transitions .btn sets — the hover would snap, and nothing would say why. */
  transition:opacity .4s var(--ease-soft),
             visibility .4s,
             transform .4s var(--ease-back),
             box-shadow .35s var(--ease),
             background-color .3s var(--ease),
             border-color .3s var(--ease),
             color .3s var(--ease);
}
/* visibility:hidden also takes it out of the tab order, so a button nobody
   can see is not a stop on the way to the footer */
.inq-fab.is-on{opacity:1;visibility:visible;transform:none;}
/* brightens rather than moves, and the wording is .to-top's: a 56px target
   in the corner under the pointer's approach is the worst place on the page
   to shift something */
.inq-fab:hover{
  background:#1C1A16;
  color:#FFF6DC;
  box-shadow:0 18px 38px -10px rgba(0,0,0,.6), 0 0 0 1px rgba(198,164,85,.5);
}
.inq-fab svg{
  width:23px;height:23px;flex:none;
  fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;
}

/* ⚠️ THE PULSE IS THE "PRESS ME", AND THE COLOUR IT IS NOT IS THE POINT.
   Asked for something that pops. The brand's third colour, seafoam, was the
   obvious reach and it is the wrong one: styles.css records it at 1.9:1 on
   its own tint — "unreadable as an icon stroke" — and the ramp dropped it
   for exactly that. So the attention is bought with movement and with gold
   on cream, not with a hue this page has already tried and rejected.

   ⚠️ ON THE PSEUDO-ELEMENTS, NEVER ON .inq-fab ITSELF. The button's own
   transform is its entrance (translateY + scale). An animation on the same
   property would fight that every time it appeared, and the button would
   arrive twitching instead of rising.

   ⚠️ TWO RINGS ON ONE TIMER, HALF A CYCLE APART. One ring reads as a blink;
   two reads as a radar sweep, which is the thing that actually holds a
   glance. The delay is exactly half the duration — change one, change both. */
.inq-fab::before,
.inq-fab::after{
  content:"";
  position:absolute;inset:0;
  border-radius:50%;
  /* ⚠️ --gold-ink, NOT --gold, AND THE GROUND IS WHY. The first pass drew
     these in --gold (#C6A455) and they were all but invisible: the sections
     this button floats over are warm cream, and gold on cream is the one
     pairing BRAND.md singles out as not clearing contrast. --gold-ink
     (#8A6B1F) is the token the palette keeps for exactly this — gold that
     has to hold against a light ground. */
  border:2.5px solid var(--gold-ink);
  opacity:0;
  pointer-events:none;
}
.inq-fab.is-on{animation:inqGlow 2.8s var(--ease-out) infinite;}
.inq-fab.is-on::before{animation:inqPulse 2.8s var(--ease-out) infinite;}
.inq-fab.is-on::after{animation:inqPulse 2.8s var(--ease-out) 1.4s infinite;}
@keyframes inqPulse{
  0%{transform:scale(1);opacity:.85;}
  70%{transform:scale(1.75);opacity:0;}
  100%{transform:scale(1.75);opacity:0;}
}

/* ⚠️ THE DISC BREATHES WITH THE RINGS, AND IT IS box-shadow, NOT transform.
   .inq-fab's transform is its entrance; a second animation on the same
   property would fight it. A shadow costs no layout and reads as the button
   lifting rather than growing, which is what keeps this the right side of
   tasteful on a page whose voice is "never loud or gimmicky". */
@keyframes inqGlow{
  0%,100%{box-shadow:0 14px 30px -10px rgba(120,90,25,.6), 0 0 0 0 rgba(138,107,31,.34);}
  50%{box-shadow:0 16px 34px -10px rgba(120,90,25,.7), 0 0 0 9px rgba(138,107,31,0);}
}
/* the ring is an attention-getter; once the pointer is on the button it has
   already done its work, and a target that keeps flashing under the cursor
   is just noise */
.inq-fab:hover,.inq-fab:focus-visible,
.inq-fab:hover::before,.inq-fab:hover::after,
.inq-fab:focus-visible::before,.inq-fab:focus-visible::after{
  animation:none;
}
.inq-fab:hover::before,.inq-fab:hover::after,
.inq-fab:focus-visible::before,.inq-fab:focus-visible::after{opacity:0;}

/* =========================================================
   Modal — category info panel
   ========================================================= */
.modal{
  position:fixed;inset:0;z-index:var(--z-modal);
  display:grid;place-items:center;
  padding:20px;
}
.modal[hidden]{display:none;}
.modal-scrim{
  position:absolute;inset:0;
  background:rgba(10,9,7,.62);
  backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px);
  opacity:0;
  transition:opacity .4s var(--ease-soft);
}
.modal.is-open .modal-scrim{opacity:1;}
.modal-card{
  position:relative;
  width:100%;
  max-height:calc(100dvh - 40px);
  overflow-y:auto;
  background:var(--white);
  border-radius:var(--r-lg);
  box-shadow:0 50px 100px -40px rgba(24,20,12,.65);
  opacity:0;
  transform:translateY(26px) scale(.97);
  transition:opacity .45s var(--ease-out), transform .45s var(--ease-out);
}
.modal.is-open .modal-card{opacity:1;transform:none;}
.modal-x{
  position:absolute;top:14px;right:14px;z-index:3;
  display:grid;place-items:center;
  width:38px;height:38px;border-radius:50%;
  background:rgba(255,255,255,.9);
  border:1px solid var(--line);
  color:var(--ink-2);
  transition:transform .3s var(--ease-back), border-color .3s var(--ease-soft), color .3s var(--ease-soft);
}
.modal-x:hover{transform:rotate(90deg);border-color:var(--gold);color:var(--gold-ink);}
.modal-x svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;}

/* --- Quick view --- */
.qv-card{
  max-width:820px;
  display:grid;
  grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
}
.qv-media{
  position:relative;
  display:grid;place-items:center;
  padding:44px 20px;
  background:linear-gradient(140deg,var(--sea-tint) 0%, #F5F9FB 45%, var(--sand) 100%);
}
.qv-halo{
  position:absolute;
  width:210px;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle,rgba(255,255,255,.9),rgba(255,255,255,0) 70%);
}
.qv-bottle{
  position:relative;
  width:132px;height:auto;
  filter:drop-shadow(0 26px 30px rgba(20,30,36,.26));
  animation:floaty 7s ease-in-out infinite;
}
.qv-body{padding:40px 40px 36px;}
.qv-title{
  font-family:var(--f-display);
  font-weight:400;font-size:clamp(23px,2.6vw,31px);
  line-height:1.15;letter-spacing:-.015em;
  margin-bottom:10px;
}
.qv-desc{
  font-size:13.5px;font-weight:300;line-height:1.7;color:var(--ink-2);
  margin-bottom:20px;
}
.qv-list{margin-bottom:26px;}
.qv-list li{
  opacity:0;transform:translateX(-8px);
  animation:qvItem .5s var(--ease-out) forwards;
}
.qv-list li:nth-child(1){animation-delay:.16s;}
.qv-list li:nth-child(2){animation-delay:.24s;}
.qv-list li:nth-child(3){animation-delay:.32s;}
.qv-list li:nth-child(4){animation-delay:.40s;}
@keyframes qvItem{ to{opacity:1;transform:none;} }
.qv-actions{display:flex;flex-wrap:wrap;gap:10px;}
.qv-note{
  margin-top:14px;
  font-size:11.5px;font-weight:300;color:var(--ink-3);
}
.qv-actions .btn{font-size:13px;padding:13px 22px;}

/* =========================================================
   Reveal upgrade
   ========================================================= */
/* This is the transition that runs — it overrides the one in styles.css.

   Travel is 34px rather than 18: at 18px on a large screen the movement was
   too small to read as an entrance, which is the whole point of it. Transform
   runs slightly longer than opacity so the piece is fully legible a moment
   before it finishes settling, which is what makes it feel unhurried rather
   than merely slow. */
[data-reveal]{
  transition:opacity .95s var(--ease-out),
             transform 1.05s var(--ease-out),
             filter .8s var(--ease-out);

  /* ⚠️ THIS LINE HAS TO SIT AFTER THE SHORTHAND ABOVE, AND IT WAS MISSING.

     styles.css sets the same thing —
         [data-reveal]{ transition-delay:calc(var(--d,0) * 95ms); }
     — and this file loads LATER. `transition` is a shorthand, so the three
     lines above reset transition-delay to 0s every time, and the delay in
     styles.css never reached a single element.

     Nothing looked wrong in either file: one sets a delay, the other never
     mentions delay. But all 52 data-reveal-delay attributes on the page were
     inert, and every section landed as ONE BLOCK — #faq and #contact carry
     delays 1 through 5 and were measured arriving inside 30ms of each other.

     Same trap as the margin-bottom that was written above a margin shorthand
     and did nothing. A shorthand always resets the longhands it covers,
     whether or not it names them.

     ⚠️ If the transition above is ever edited, this line stays BELOW it. */
  transition-delay:calc(var(--d,0) * 95ms);

  filter:blur(6px);
}
[data-reveal].is-in{filter:blur(0);}

/* ⚠️ blur(0) is NOT none. It keeps every revealed element on its own
   composited layer for the life of the page, and there are 60-odd of them.
   main.js adds .is-done when the filter transition ends, which drops the
   filter for good. Under reduced motion the filter is never set at all. */
[data-reveal].is-done{filter:none;}

/* =========================================================
   THE PAGE READS WITH THE SCRIPT OFF

   ⚠️ WITHOUT THIS RULE THE PAGE IS BLANK BELOW THE HEADER. Every
   [data-reveal] rests at opacity:0 and only main.js ever adds .is-in.
   Measured with scripting disabled at the browser: 69 of 69 blocks
   invisible, no <noscript>, no guard — while main.js's own first paragraph
   says the page is fully readable with that file absent.

   index.html carries class="no-js" on <html> and clears it with an inline
   script in <head>, before these stylesheets are parsed. If the script
   never runs, the class survives and everything below hands its resting
   state straight back.
   ⚠️ Any NEW resting state added to this file belongs in this rule too. */
.no-js [data-reveal],
.no-js [data-stage] > *{
  opacity:1;
  transform:none;
  translate:none;
  filter:none;
}

/* =========================================================
   THE CASCADE INSIDE A BLOCK

   [data-reveal] carries a whole card in as one object. This carries its
   CONTENTS in one after another, which is the difference between four cards
   appearing and a grid unfolding.

   ⚠️ It moves on `translate`, never `transform`. The card above it is
     mid-transform while these run, and .value-card also carries [data-tilt],
     which owns `transform` outright.

   ⚠️ aria-hidden children are deliberately left out. .value-lens and
     .value-foot run their own keyframes, and an animation beats a transition
     on the same property — an opacity:0 resting state there would simply be
     ignored and that one piece would arrive ahead of the cascade it is
     supposed to be part of. Decoration lands with its card.

   Two selectors because the container is sometimes the revealed element
   itself (a .scard) and sometimes sits inside one (a footer <ul> inside its
   .footer-col). */
[data-stage] > *:not([aria-hidden="true"]){
  opacity:0;
  translate:0 12px;
  transition:opacity .6s var(--ease-out), translate .68s var(--ease-out);
}
[data-stage].is-in > *:not([aria-hidden="true"]),
.is-in [data-stage] > *:not([aria-hidden="true"]){
  opacity:1;
  translate:0 0;
}
/* nth-child counts the decoration too, so the visible pieces step unevenly —
   which is wanted: an icon and a rule between two lines of copy should not
   make the copy arrive on a metronome. */
[data-stage] > *:nth-child(1){transition-delay:.13s;}
[data-stage] > *:nth-child(2){transition-delay:.20s;}
[data-stage] > *:nth-child(3){transition-delay:.27s;}
[data-stage] > *:nth-child(4){transition-delay:.34s;}
[data-stage] > *:nth-child(5){transition-delay:.41s;}
[data-stage] > *:nth-child(6){transition-delay:.48s;}
[data-stage] > *:nth-child(7){transition-delay:.55s;}
[data-stage] > *:nth-child(8){transition-delay:.62s;}

/* =========================================================
   Responsive
   ========================================================= */
@media (max-width:860px){
  .qv-card{grid-template-columns:1fr;max-width:460px;}
  .qv-media{padding:34px 20px 20px;}
  .qv-bottle{width:112px;}
  .qv-body{padding:26px 26px 30px;}
}

@media (max-width:560px){
  .to-top{right:14px;bottom:14px;width:42px;height:42px;}
  .inq-fab{left:14px;bottom:14px;width:52px;height:52px;}
  .inq-fab svg{width:21px;height:21px;}
  .qv-actions .btn{flex:1 1 auto;justify-content:center;}
}


/* =========================================================
   FAQ answer reveal
   ========================================================= */
.faq-item[open] .faq-a{animation:faqIn .45s var(--ease-out) both;}
@keyframes faqIn{
  from{opacity:0;translate:0 -6px;}
  to  {opacity:1;translate:0 0;}
}

/* =========================================================
   Reduced motion — everything decorative stops
   ========================================================= */
@media (prefers-reduced-motion:reduce){
  body::after{display:none;}
  .hero-title .word{opacity:1;transform:none;filter:none;}
  [data-reveal]{filter:none;}
  /* the cascade is decoration; under a reduced-motion preference every piece
     is simply already where it belongs */
  [data-stage] > *{opacity:1;translate:none;transition:none;}
  .has-rule::after{width:74px;transition:none;}
  .qv-list li{opacity:1;transform:none;animation:none;}
  .faq-item[open] .faq-a{animation:none;}
  /* it still fades in — a fade is not motion — but it no longer travels,
     and the ring stops entirely: a thing that pulses forever is the exact
     class of movement this preference is asking the page to stop.
     ⚠️ .is-on IS IN EVERY SELECTOR HERE AND IT HAS TO BE. A media query adds
     no specificity, so plain .inq-fab (0,1,0) loses to the .inq-fab.is-on
     (0,2,0) rules that start the animations — written without it, this block
     read correctly and did nothing at all, and the rings kept sweeping under
     a reduce preference. Caught by emulating the preference and reading back
     animationName, which still said inqPulse. */
  .inq-fab{transform:none;}
  .inq-fab.is-on,
  .inq-fab.is-on::before,
  .inq-fab.is-on::after{animation:none;}
  .inq-fab::before,.inq-fab::after,
  .inq-fab.is-on::before,.inq-fab.is-on::after{opacity:0;}
}


/* =========================================================
   Step 2 — the interview, live layer

   The section was built as a still composition. Everything below is what
   makes it answer back: what it does when you arrive, and what it does when
   you point at it. Nothing here is required to read the section — remove
   this file and the layout is intact, which is the rule for everything in
   it.
   ========================================================= */

/* ---------- The head arrives in sequence ---------- */

/* Rules grow out of the eyebrow rather than being there already */
.rule-eyebrow::before,
.rule-eyebrow::after{
  width:0;
  transition:width .8s var(--ease-out) .15s;
}
.rule-eyebrow.is-in::before,
.rule-eyebrow.is-in::after{width:26px;}

/* A catch-light travelling across the gold word.

   ⚠️ NOT the hero's ramp-that-slides. That treatment puts a near-white stop
   in the ramp itself, so at some point in the cycle the whole word is the
   pale stop — and on a ground this light the pale stop IS the ground. Half
   of "label" disappeared, exactly the way the title card's wordmark did.

   Two layers instead: a solid gold base that never moves, and a narrow
   champagne band that crosses it. The letterform is fully drawn in gold at
   every frame; only a thin highlight brightens, which is how metal reads
   anyway.

   ⚠️ background-clip:text and the reveal's blur cannot share an element — a
   filter rasterises the clip and the gradient turns into a grey slab. The
   reveal is on the <h2>, the clip is on the <em> inside it, which is why
   this works here and would not if both sat on the same node. */
.story-title em{
  background-image:
    linear-gradient(100deg,
      rgba(255,246,220,0) 43%, rgba(255,246,220,.5) 50%, rgba(255,246,220,0) 57%),
    linear-gradient(var(--gold-accent), var(--gold-accent));
  background-size:300% 100%, 100% 100%;
  background-position:170% 0, 0 0;
  background-repeat:no-repeat;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  animation:storyShine 8s linear 1.2s infinite;
}
@keyframes storyShine{
  0%      {background-position:170% 0, 0 0;}
  55%,100%{background-position:-150% 0, 0 0;}
}

/* The rule beside the question draws downward, then the badge lands on it */
.story-ask::before{
  transform-origin:top;
  transform:scaleY(0);
  transition:transform .8s var(--ease-out) .2s;
}
.story-ask.is-in::before{transform:scaleY(1);}
.ask-badge{
  opacity:0;
  scale:.4;
  transition:opacity .5s var(--ease), scale .6s var(--ease-back) .55s, box-shadow .4s var(--ease);
}
.story-ask.is-in .ask-badge{opacity:1;scale:1;transition-delay:.5s,.5s,0s;}
.story-ask:hover .ask-badge{
  box-shadow:0 8px 18px -6px rgba(160,124,40,.95), 0 0 0 5px rgba(253,251,247,.9), 0 0 0 8px rgba(198,164,85,.22);
}
.ask-badge svg{transition:transform .45s var(--ease-back);}
.story-ask:hover .ask-badge svg{transform:translateX(2px);}

/* The watermark never quite settles — 40s, far too slow to notice directly,
   which is the point: it stops the corner reading as a printed page. */
.story-mark{animation:storyDrift 40s ease-in-out infinite;}
@keyframes storyDrift{
  0%,100%{translate:0 0;rotate:0deg;}
  50%    {translate:10px -14px;rotate:-2deg;}
}

/* ---------- The quote card ---------- */

/* The seam between portrait and panel lights up as the card arrives */
.rdr-card{position:relative;}
.rdr-seam{
  position:absolute;top:0;bottom:0;
  left:41%;                     /* the grid's .82fr / 1.18fr split */
  margin-left:-1px;
  z-index:3;
  width:2px;
  pointer-events:none;
  background:linear-gradient(180deg,
    rgba(198,164,85,0), rgba(233,205,140,.95) 22%, rgba(233,205,140,.95) 78%, rgba(198,164,85,0));
  /* A hairline between a grey photograph and a black panel is just part of
     the edge. The bloom is what makes it read as light falling on the seam
     rather than as a border. */
  box-shadow:0 0 10px rgba(250,238,200,.5), 0 0 26px rgba(198,164,85,.3);
  transform:scaleY(0);
  transform-origin:center;
  transition:transform .9s var(--ease-out) .5s, box-shadow .5s var(--ease);
}
.rdr-card.is-in .rdr-seam{transform:scaleY(1);}
.rdr-card:hover .rdr-seam{
  box-shadow:0 0 14px rgba(250,238,200,.8), 0 0 34px rgba(198,164,85,.45);
}

/* The portrait drifts — a very slow push-in, so the plate is a window on a
   room rather than a printed photograph. It also leans a few pixels against
   the pointer, reading --mx/--my from the card's own spotlight. */
.rdr-photo img{
  animation:rdrPush 26s ease-in-out infinite;
  translate:
    calc((var(--mx, 50%) - 50%) * -0.055)
    calc((var(--my, 50%) - 50%) * -0.04);
  transition:translate .9s var(--ease-soft);
}
/* ⚠️ The amount is NOT written here. --rdr-z is the breakpoint's zoom (see
   .rdr-photo img in styles.css) and the push is a proportion of it, so the
   drift is the same gesture at every size instead of a fixed 0.1 that is a
   nudge on a desktop and a lurch on a phone. 1.054 is the 1.85 -> 1.95 the
   desktop plate had. */
@keyframes rdrPush{
  0%,100%{scale:var(--rdr-z,1.85);}
  50%    {scale:calc(var(--rdr-z,1.85) * 1.054);}
}

/* A slow gold light crossing the black panel, and again — faster — on hover */
.rdr-panel{position:relative;overflow:hidden;}
.rdr-sweep{
  position:absolute;inset:0;
  pointer-events:none;
  background:linear-gradient(104deg,
    rgba(250,238,200,0) 38%, rgba(250,238,200,.14) 48%, rgba(250,238,200,0) 58%);
  background-size:280% 100%;
  background-position:160% 0;
  animation:rdrSweep 11s var(--ease-soft) 3s infinite;
}
@keyframes rdrSweep{
  0%     {background-position:160% 0;}
  45%,100%{background-position:-120% 0;}
}
.rdr-card:hover .rdr-sweep{animation-duration:4.5s;}

/* The quote mark breathes with the sweep */
.rdr-quote{
  position:relative;z-index:1;
  animation:rdrQuote 11s var(--ease-soft) 3s infinite;
}
@keyframes rdrQuote{
  0%,100%{opacity:.9;}
  46%    {opacity:1;text-shadow:0 0 22px rgba(250,238,200,.5);}
}
.rdr-text,.rdr-by{position:relative;z-index:1;}

/* The byline rule draws across */
.rdr-by{border-top-color:transparent;}
.rdr-by::before{
  content:"";
  position:absolute;top:0;left:0;right:0;
  height:1px;
  background:linear-gradient(90deg, rgba(198,164,85,.9), rgba(198,164,85,.15));
  transform:scaleX(0);
  transform-origin:left;
  transition:transform 1s var(--ease-out) .7s;
}
.rdr-card.is-in .rdr-by::before{transform:scaleX(1);}

.rdr-card:hover{
  box-shadow:0 44px 84px -38px rgba(20,30,36,.55);
}

/* ---------- The three cards ----------

   ⚠️ THE CARD'S RESPONSE LIVES IN styles.css NOW, NOT HERE, and that is a
     correction rather than a preference. The cards became per-card accent
     colours driven by --ac; the suite that used to sit here answered in
     champagne for all three and, because this file loads AFTER styles.css,
     it won every conflict silently. Three of them mattered:

       · .scard::after was a champagne wash rising from the foot. The accent
         system uses ::after for the thread along the top edge, so the wash
         replaced the thread outright and nothing was drawn.
       · .scard:hover .scard-ico set a gold box-shadow over the accent one.
       · .scard:hover .scard-ico svg turned the glyph gold — which on a
         filled dark disc is a white icon going invisible on hover.

     One owner per pseudo-element, and the owner is the file that knows the
     colour. What is left here is the one effect that is still additive and
     still colour-neutral: the specular sweep across the disc. */

/* The icon disc takes a specular sweep. White on any accent, so it needs no
   --ac of its own. */
.scard-ico{position:relative;overflow:hidden;}
.scard-ico::after{
  content:"";
  position:absolute;inset:0;
  border-radius:50%;
  background:linear-gradient(102deg,
    rgba(255,255,255,0) 34%, rgba(255,255,255,.55) 48%, rgba(255,255,255,0) 62%);
  background-size:260% 100%;
  background-position:190% 0;
  transition:background-position 1s var(--ease);
}
@media (hover:hover){ .scard:hover .scard-ico::after{background-position:-100% 0;} }
.scard-ico svg{position:relative;z-index:1;}

/* ---------- The closing band ---------- */

/* ⚠️ No lift here, and the reason is not this element. The band contains
   its own CTA, so translating the band translated the button inside it —
   measured at matrix(1,0,0,1,0,-3) with the pointer on the button. Setting
   transform:none on a button is not enough to hold it still; an ancestor
   that moves moves the target just as surely. The band answers with light
   instead. */
.story-band{transition:box-shadow .5s var(--ease);}
.story-band:hover{
  box-shadow:0 34px 62px -34px rgba(20,16,8,.75);
}
/* The gold linework at the two ends drifts across, very slowly */
.story-band::after{
  background-size:200% 100%;
  animation:bandDrift 26s linear infinite;
}
@keyframes bandDrift{
  0%  {background-position:0 0;}
  100%{background-position:200% 0;}
}

/* The play button sends a ring out, the way the hero's does */
.sb-play{position:relative;transition:transform .5s var(--ease-back), border-color .4s var(--ease);}
.sb-play::before{
  content:"";
  position:absolute;inset:-1.5px;
  border-radius:50%;
  border:1.5px solid rgba(198,164,85,.7);
  animation:sbRing 3.4s var(--ease-soft) infinite;
}
@keyframes sbRing{
  0%  {transform:scale(1);opacity:.75;}
  70% {transform:scale(1.5);opacity:0;}
  100%{transform:scale(1.5);opacity:0;}
}
.story-band:hover .sb-play{transform:scale(1.08);border-color:rgba(214,182,106,1);}
.sb-play svg{transition:transform .45s var(--ease-back);}
.story-band:hover .sb-play svg{transform:scale(1.12);}

/* ---------- Motion off ---------- */
@media (prefers-reduced-motion:reduce){
  .story-title em{animation:none;background-image:linear-gradient(var(--gold-accent),var(--gold-accent));}
  .story-mark{animation:none;}
  .rdr-photo img{animation:none;translate:none;transition:none;}
  .rdr-sweep,.sb-play::before{display:none;}
  .rdr-quote{animation:none;}
  .story-band::after{animation:none;}
  .rule-eyebrow::before,
  .rule-eyebrow::after{width:26px;transition:none;}
  .story-ask::before{transform:none;transition:none;}
  .ask-badge{opacity:1;scale:1;transition:none;}
  .rdr-seam{transform:scaleY(1);transition:none;}
  .rdr-by::before{transform:scaleX(1);transition:none;}
  /* ⚠️ .scard::after IS NOT MOTION AND MUST NOT BE HIDDEN HERE. It used to be
     a champagne wash that only ever appeared on hover; it is the card's top
     thread now, and it is on screen at rest. Hiding it takes a colour out of
     the design for every reader who asks for less movement. Its transition
     goes; the thread stays, drawn full rather than part-drawn so it does not
     sit looking unfinished with nothing able to complete it. */
  .scard::after{transform:none;transition:none;}
  .scard-ico::after{display:none;}
  .scard-ico,.scard-div,.scard-title b,.story-band,.sb-play{transition:none;}
}

@media (max-width:1024px){
  /* Stacked, the photo sits above the panel — there is no vertical split for
     the seam to trace. */
  .rdr-seam{display:none;}
}

@media (prefers-reduced-motion:reduce){
  .hero-title em{
    animation:none;
    background-image:linear-gradient(var(--gold-accent),var(--gold-accent));
  }
}


/* =========================================================
   OFF-SCREEN SECTIONS STOP MOVING

   ⚠️ AN ANIMATION DOES NOT STOP WHEN IT SCROLLS OUT OF VIEW. It keeps its
   place in the frame loop for as long as it is in the document, and on this
   page that is a lot: measured at rest on the first screen, 37 infinite
   animations were running, 33 of them on the title card alone — 16 motes, 5
   twinkles, 4 pings, 4 swirls. Scroll to the FAQ and all 33 are still going,
   behind two screens of other content, costing style recalc on every frame.

   Measured over one 5s scroll before this rule: 933.9ms of style recalc at
   1440 and a main thread busy 4899ms out of 5000. That is what "not smooth"
   is — not the easing curves.

   ⚠️ animation-play-state, NOT animation:none. Pausing holds the animation
   where it is and resumes it when the section comes back; "none" would snap
   every mid-flight element to its resting value and play entrances again from
   the start each time you scrolled past.

   ⚠️ !important, AND IT IS WHAT THIS IS FOR. The declarations being overruled
   are on the elements themselves and many are inside media blocks; a
   categorical rule needs a categorical mechanism. The same note is in
   README.md against the phone-mask bug, which a per-index list caused.

   ⚠️ NOT ON THE HEADER, and not on anything fixed. .site-header, the
   scroll-progress bar and .to-top are outside <main>'s sections and are never
   given this class — a paused progress bar is a broken progress bar.
   ========================================================= */
.is-idle,
.is-idle *,
.is-idle *::before,
.is-idle *::after{
  animation-play-state:paused !important;
}


/* =========================================================
   THE MOTION BUDGET — how many particles actually drift

   Measured, parked 55% down the page: 215 animations running at once, and the
   three biggest groups were all decorative particles —

     22  .coll-motes i      collMote, 30s, infinite
     20  .agrav-drops i     agravRise, 28s, infinite
     16  .tc-motes i        tcMote, infinite

   58 of them. Switching just those three off and measuring the same 5s scroll:
   style recalc 897.8 -> 596.2ms and the main thread 3638 -> 2193ms at 390.
   A third off the style budget, from decoration nobody can count.

   ⚠️ EVERY THIRD ONE, NOT THE LAST THIRD. Each particle carries its own
   inline --d, --dur and position, so the set is a designed scatter. Hiding a
   contiguous tail would have left a bald patch where those positions were;
   :nth-child(3n) takes them out evenly and the scatter still reads as a
   scatter.

   ⚠️ display:none, NOT animation:none. A hidden element is not styled, not
   laid out and not painted; one that is merely not animating is still all
   three, every frame, for no visible return.

   ⚠️ THE PHONE KEEPS THE FEWEST, and that is where it matters — the 390 run
   above is the one that improved most, and a 6px mote on a 390px screen is
   the least visible thing on the page.
   ========================================================= */
.tc-motes i:nth-child(3n),
.coll-motes i:nth-child(3n),
.agrav-drops i:nth-child(3n){display:none;}

@media (max-width:900px){
  .tc-motes i:nth-child(even),
  .coll-motes i:nth-child(even),
  .agrav-drops i:nth-child(even){display:none;}
}

@media (max-width:620px){
  .tc-motes i:not(:nth-child(4n+1)),
  .coll-motes i:not(:nth-child(4n+1)),
  .agrav-drops i:not(:nth-child(4n+1)){display:none;}
}
