:root{
  --bg:#717171;
  --gold:#c3a567;
  --white:#ffffff;
  --footer:#5c5b5b;
  --page-w:1920px;
  --page-h:5322px;
}

*{box-sizing:border-box; margin:0; padding:0;}
html{ -webkit-text-size-adjust:100%; background:var(--bg); }
body{
  font-family:'Montserrat','Segoe UI',Arial,sans-serif;
  color:var(--white);
  background:var(--bg);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
img{display:block; max-width:none;}
a{color:inherit; text-decoration:none;}

/* ---------- Scale wrapper ---------- */
/* width:100vw (not 100%) on purpose: 100% would shrink by the native
   scrollbar's width (~15-17px), so the scale-to-fit-1920 system below
   computed e.g. 1903/1920 ≈ .991 instead of exactly 1 at a 1920px monitor —
   a hair off 1:1 is enough to force the browser to resample every image/
   font at a fractional size, reading as a soft/blurry page site-wide. 100vw
   ignores the scrollbar the same way clientWidth would if there were none,
   so the design renders pixel-perfect at 1920 again; body's own
   overflow-x:hidden (below) clips the ~1px sliver this can overhang by. */
/* overflow-y:hidden explicit, not left to default: with only overflow-x set,
   the UA computes overflow-y as auto (spec quirk), and .page's *layout* box
   stays at its full unscaled --page-h (transform:scale doesn't shrink layout
   size, only paint) — taller than wrap's own JS-set (scaled) height, so that
   auto-Y was quietly growing its own scrollbar and eating into clientWidth
   at every scale below 1, the same width-off-by-scrollbar problem as above
   just smaller and everywhere instead of only at 1920. */
/* overflow:clip (not just hidden) so the wrap is NOT a scroll container at all:
   an overflow:hidden box is still programmatically scrollable, and the browser
   scrolls IT (not the window) to reveal a #hash target on arrival from the
   catalog nav or a focused contact-form field — shifting the scaled .page up
   while window/Lenis stay at 0 and desyncing the whole hero. clip removes that
   scroll container entirely. Older Safari (<16) ignores clip and falls back to
   the preceding hidden, where the JS guard (pinWrap in main.js) covers it. */
.page-wrap{ position:relative; width:100vw; overflow:hidden; overflow:clip; }
.page{
  position:relative;
  width:var(--page-w);
  height:var(--page-h);
  background:var(--bg);
  /* clip, not hidden: like .page-wrap, an overflow:hidden .page is still
     programmatically scrollable, so the browser scrolls IT to reveal a #hash
     target on arrival from the catalog nav — shifting all the hero content up
     (~210px) while window/Lenis stay at 0, so scrolling to the top no longer
     shows the header. .page has ~210px of natural overflow below --page-h
     (the footer logo), which is what made it scrollable. clip removes the
     scroll container; older Safari falls back to hidden + the JS guard. */
  overflow:hidden;
  overflow:clip;
  transform-origin:top left;
}

/* Sits above .hero-bottle (which lives outside .page for pinning), scrolls
   normally with the page like everything inside .page does — same
   transform:scale trick as .page itself, kept in sync in JS. */
.pinned-front-layer{
  position:absolute; left:0; top:0;
  transform-origin:top left;
  z-index:3;
  pointer-events:none;
}
.pinned-front-layer a, .pinned-front-layer button{ pointer-events:auto; }

/* helpers */
.c-gold{ color:var(--gold); }
.c-white{ color:var(--white); }
.b{ font-weight:700; }
.r{ font-weight:400; }

/* ---------- Watermark (flattened Figma raster w/ baked-in blur, opaque on --bg) ---------- */
.wm-full{
  position:absolute;
  left:129px; top:179px; width:1622px; height:765px;
  pointer-events:none;
}

/* ---------- Header ---------- */
.logo-header{
  position:absolute;
  left:152px; top:28px;
  width:188.578px; height:27.207px;
  z-index:5;
}
.nav{ position:absolute; left:0; top:0; width:100%; height:0; z-index:5; }
.nav-link{
  position:absolute; top:39px;
  font-family:'Montserrat'; font-weight:300;
  font-size:16px; line-height:normal; letter-spacing:.9px;
  color:var(--white); white-space:nowrap;
}
.nav-link:nth-child(1){ left:688px; }
.nav-link:nth-child(2){ left:835px; }
.nav-link:nth-child(3){ left:958px; }
.nav-link:nth-child(4){ left:1115px; }

.pill{
  position:absolute;
  background:var(--gold);
  border:none; cursor:pointer;
  border-radius:9px;
  height:42.5px;
  display:flex; align-items:center; justify-content:center;
  z-index:5;
}
.pill-order{ left:1591.29px; top:29.25px; width:148.42px; }
.pill-catalog{ left:440px; top:307.25px; width:148.42px; }
.nav-cta{
  font-family:'Montserrat'; font-weight:600;
  font-size:16px; letter-spacing:.9px; color:var(--white); white-space:nowrap;
}

/* ---------- Hero copy ---------- */
.hero-h1{
  position:absolute; left:432px; top:86px;
  font-family:'Montserrat'; font-weight:700;
  font-size:81.6px; line-height:normal; white-space:nowrap;
}
.hero-h1 .r{ font-family:'Montserrat'; }
.hero-h1 .b{ font-family:'Montserrat'; }

.hero-bottle{
  /* Absolute inside page-wrap (like .pinned-front-layer), so it scrolls
     together with the hero and its overlay text and simply leaves the
     viewport as you scroll down — instead of being pinned and drifting
     across the whole site. Exact left/top/width/height set in JS from
     currentScale. */
  position:absolute;
  z-index:2;
  /* Wrapper stays transparent to pointers; only the canvas below catches drags,
     so header nav (z-index:5) and the pinned buttons (z-index:3) that sit ABOVE
     the canvas keep receiving their clicks. */
  pointer-events:none;
}
#bottleCanvas{
  width:100%; height:100%; display:block;
  /* Drag-orbit target: the canvas itself receives pointer events so the user can
     spin the model. Nav / buttons overlap at a higher z-index and win the hit
     test, so they remain clickable. touch-action:none lets a drag rotate on
     mobile instead of scrolling (see mobile.css). */
  pointer-events:auto;
  touch-action:none;
  /* Soft edge safety net: if scroll drift ever nudges the model near the
     frustum boundary, it fades out instead of hard-clipping against the
     canvas' rectangular edge. Invisible in the normal centered pose. */
  -webkit-mask-image: radial-gradient(ellipse 72% 72% at 50% 50%, #000 78%, transparent 100%);
  mask-image: radial-gradient(ellipse 72% 72% at 50% 50%, #000 78%, transparent 100%);
}

.hero-reliab{
  position:absolute; left:1189px; top:485px;
  font-family:'Montserrat'; font-weight:700;
  font-size:81.6px; line-height:normal; white-space:nowrap;
  text-align:right;
  text-shadow:2px 0 0 rgba(0,0,0,0.25);
  z-index:3;
}
.hero-reliab .b{ font-family:'Montserrat'; font-weight:700; }
.hero-reliab .r{ font-family:'Montserrat'; font-weight:400; }

/* Bottom hero group — "25+"/подпись/абзац (left column) and "Энергия и защита"
   (.hero-reliab, right). These base top values are the RAISED position used on
   ~1920-class screens, so the group keeps some air above the fold; the
   @media (min-width:3200px) block below restores the original lower position on
   4K, where the taller window has the room. */
.hero-25{
  position:absolute; left:150px; top:625px;
  font-family:'Montserrat'; font-weight:600;
  font-size:81.6px; line-height:normal; color:var(--gold);
}
.hero-25-sub{
  position:absolute; left:150px; top:713px;
  font-family:'Montserrat'; font-weight:400;
  font-size:18px; letter-spacing:.9px; color:var(--white);
}
.hero-25-p{
  position:absolute; left:150px; top:742px; width:315px;
  font-family:'Montserrat'; font-weight:400; font-size:14px; line-height:normal; color:var(--white);
}
.hero-right-p{
  position:absolute; left:1336px; top:673.34px; width:361px;
  font-family:'Montserrat'; font-weight:400; font-size:14px; line-height:normal; color:var(--white);
}

/* On very wide screens (4K, >=3200px) the window is tall enough that the hero
   bottom group doesn't crowd the fold, so keep it at its original (lower)
   position. The raised base values above apply to ~1920-class screens, where
   the shorter window would otherwise push this group against the bottom edge. */
@media (min-width: 3200px){
  .hero-reliab{ top:545px; }
  .hero-25{ top:685px; }
  .hero-25-sub{ top:773px; }
  .hero-25-p{ top:802px; }
  .hero-right-p{ top:733.34px; }
}

/* ---------- Range ---------- */
/* left:366 centers the block in the 1920 design (block is 1188 wide, so
   (1920-1188)/2 = 366 gives equal 366px gutters). Centered via `left`, not a
   transform, because .range-h also carries the .reveal class whose own
   transform (translateY) would clobber a translateX centering. Lines stay
   left-aligned as designed. */
.range-h{
  position:absolute; left:366px; top:984px;
  font-family:'Montserrat'; font-weight:400;
  font-size:81.6px; line-height:normal; color:var(--white); white-space:nowrap;
}
.range-h .c-gold{ font-weight:700; }
.range-h .big{ font-size:135px; }
.range-h p{ margin:0; }

/* ---------- Cards ---------- */
.cards-lead{
  position:absolute; left:150px; top:1481.34px; width:307px; height:49px;
  font-family:'Montserrat'; font-weight:400; font-size:14px; line-height:normal; color:var(--white);
}
.card{
  position:absolute; top:1484px;
  width:357.459px; height:374.17px;
  background:var(--gold);
  border-radius:18.164px;
  box-shadow:0 2.906px 2.906px 0 rgba(255,224,166,0.35);
}
.card-1{ left:593px; }
.card-2{ left:1003.5px; }
.card-3{ left:1412.54px; }

.card-title{
  position:absolute;
  font-family:'Montserrat'; font-weight:600;
  font-size:32.347px; line-height:normal; letter-spacing:1.6174px; color:var(--white);
}
.ct-1{ left:617px; top:1516px; width:242.998px; }
.ct-2{ left:1035px; top:1516px; width:264px; }
.ct-3{ left:1436px; top:1516px; width:264px; }
.card-title p{ margin:0; }

.card-num{
  position:absolute; top:1657px;
  font-family:'Montserrat'; font-weight:600;
  font-size:174.058px; line-height:normal; letter-spacing:8.7029px;
  color:var(--white); opacity:.46;
}
.cn-1{ left:738px; width:226px; }
.cn-2{ left:1115px; width:255px; }
.cn-3{ left:1522px; width:255px; }

.card-lorem{
  position:absolute; width:286px;
  font-family:'Montserrat'; font-weight:400;
  font-size:10.597px; line-height:normal; color:var(--white);
}
.cl-1{ left:617px; top:1610px; }
.cl-2{ left:1035px; top:1644px; }
.cl-3{ left:1436px; top:1605px; }

/* ---------- Standards ---------- */
.std-h{
  position:absolute; left:150px; top:1917px; width:1429px;
  font-family:'Montserrat'; font-weight:700; font-size:81.6px; line-height:normal;
}
.std-h .r{ font-weight:400; }

/* ---------- Splash composition ----------
   Layer order (back to front): ring line -> splash image -> ring dots/labels.
   This makes the ring visually duck behind the splash on the side where
   the golden liquid covers it, and stay on top elsewhere (real z-order,
   not a crop) since splash.png is transparent outside the liquid shape. */
.splash-wrap{
  position:absolute; left:501.02px; top:1918px; /* crown lands at base y~2173, matching the mockup. (Figma node 150:64 reports y=2313.95, but that is the node's internal box, not the visual top — using it pushed the splash ~330px too low.) 60px added on top of the mockup-matched value to open a gap under .std-h. */
  width:1096.975px; height:1088.01px;
  display:flex; align-items:center; justify-content:center;
  z-index:2;
}
.splash-img{
  width:838.26px; height:820.864px;
  object-fit:cover;
  opacity:0;
  transform:rotate(-23.63deg) scale(.92);
  transition:opacity 1.3s ease, transform 1.5s cubic-bezier(.2,.7,.2,1);
}
.splash-img.in{ opacity:1; transform:rotate(-23.63deg) scale(1); }

/* Ring — ellipse fitted to the approved mockup (passes through all 3 dots).
   center (868,2653), semi-axes 352×139, tilted -31.5°. Placed in an SVG that
   uses base-pixel coords (viewBox == position box) so it scales with .page.
   Relying on splash.png alpha for occlusion turned out unreliable (the arc
   drifts on/off the opaque wave as the two get tuned independently), so the
   ring sits in FRONT the whole way (z-index:3) and main.js punches a fixed
   gap in it with stroke-dasharray over the short stretch on the left that
   crosses the splash's pale inner curl — a real crop, not alpha-dependent.
   It's an open ellipse: the gap is never bridged, even mid draw-in. See main.js.
   NOTE: because overflow:visible is set, moving THIS box/viewBox alone does
   NOT move the ellipse on screen — the ellipse's own cx/cy (in index.html)
   is what actually positions it. Box/viewBox here just need to stay in sync
   with cx/cy for cleanliness; they aren't load-bearing for the visual result. */
.ring-svg{
  position:absolute; left:555px; top:2410px;
  width:626px; height:446px;
  overflow:visible; z-index:3; pointer-events:none;
}
.ring-path{ opacity:.9; }

.ring-dot{
  position:absolute; width:55.531px; height:55.531px;
  z-index:3; /* in front of .splash-wrap */
  opacity:0; transform:scale(.3);
  transform-origin:center;
  transition:opacity .9s ease, transform 1s cubic-bezier(.2,.9,.3,1.4);
}
.ring-dot.in{ opacity:1; transform:scale(1); }
.ring-dot[data-d="bottom"]{ left:762.61px; top:2763.4px; }
.ring-dot[data-d="left"]{ left:567.33px; top:2634.1px; }
.ring-dot[data-d="top"]{ left:1105px; top:2365px; }

.marker{
  position:absolute;
  font-family:'Montserrat'; font-weight:400;
  font-size:11.711px; line-height:normal; color:var(--white); z-index:3;
  opacity:0; transform:translateY(10px);
  transition:opacity 1.1s ease, transform 1.1s ease;
}
.marker.in{ opacity:1; transform:none; }
.marker p{ margin:0; }
.mk-top{ left:1172px; top:2376px; width:275px; }
.mk-left{ left:349px; top:2650px; width:209px; }
.mk-bottom{ left:669px; top:2830px; width:283px; }

/* ---------- Технологии ---------- */
.tech-h{
  position:absolute; top:2819px; left:657px; width:1113px;
  font-family:'Montserrat'; font-weight:700; font-size:81.6px; line-height:normal;
  text-align:right;
}
.tech-h .r{ font-weight:400; }
.tech-p{
  position:absolute; left:722px; top:3028px; width:1055px; height:49px;
  font-family:'Montserrat'; font-weight:400; font-size:14px; line-height:normal; color:var(--white);
}

/* ---------- "Найдите масло" — подбор продукта (Figma 343:4xx) ---------- */
/* Warm gold glow behind the canister — two blurred gold ellipses in the mockup,
   approximated with soft radial gradients (no filter:blur, keeps scroll cheap). */
.find-wm{
  position:absolute; left:150px; top:3678px; width:1487px; height:213px;
  z-index:0; pointer-events:none; /* opacity is baked into the PNG (~20%) */
}
.find-glow{
  position:absolute; border-radius:50%; z-index:0; pointer-events:none;
  background:radial-gradient(circle, rgba(195,165,103,0.40) 0%, rgba(195,165,103,0.16) 34%, rgba(195,165,103,0) 66%);
}
.find-glow-1{ left:1091px; top:3220px; width:600px; height:600px; }
.find-glow-2{ left:1152px; top:3471px; width:600px; height:600px; }
/* Canister: the wrap slides in from the right (+ slight tilt & fade) when it
   scrolls into view (JS adds .in), while the inner <img> floats forever — two
   separate transforms so they don't fight. */
.find-canister-wrap{
  position:absolute; left:1181px; top:3162px; width:596px; height:850px;
  z-index:1; pointer-events:none;
  opacity:0; transform:translateX(110px) rotate(5deg);
  transition:opacity 1.7s ease, transform 2s cubic-bezier(.2,.7,.2,1);
  will-change:opacity, transform;
}
.find-canister-wrap.in{ opacity:1; transform:translateX(0) rotate(0deg); }
.find-canister{
  width:100%; height:100%; object-fit:contain; display:block;
  animation:find-float 4.5s ease-in-out infinite;
}
@keyframes find-float{
  0%,100%{ transform:translateY(0); }
  50%{ transform:translateY(-16px); }
}
.find-h{
  position:absolute; left:146px; top:3416px;
  font-family:'Montserrat'; font-weight:400; font-size:81.6px; line-height:normal;
  color:var(--white); white-space:nowrap; z-index:2;
}
.find-h .c-gold{ font-weight:700; }
.find-p{
  position:absolute; left:150px; top:3724px; width:661px;
  font-family:'Montserrat'; font-weight:400; font-size:14px; line-height:normal; color:var(--white); z-index:2;
}
/* Heading/paragraph "grow from below": AE-style Scale 0% -> 100% with the
   anchor point pinned to the bottom-left, so the text scales up in place
   from its own baseline instead of fading, sliding, or masking. */
.find-rise{
  transform:scale(0); transform-origin:0% 100%;
  transition:transform 1.8s cubic-bezier(.22,1,.36,1);
  will-change:transform;
}
.find-rise.in{ transform:scale(1); }
.pill-find{ left:662px; top:3784px; width:148.42px; z-index:5; }

/* ---------- Contacts ---------- */
.contacts-h{
  position:absolute; left:143px; top:4034px; /* +872 for the "Найдите масло" block inserted above */
  font-family:'Montserrat'; font-weight:700; font-size:81.6px; line-height:normal; color:var(--gold);
}
.contacts-info{
  position:absolute; left:150px; top:4133px; width:720px;
  font-family:'Montserrat'; font-weight:400; font-size:20px; line-height:normal; color:var(--white);
}
.contacts-info p{ margin:0 0 20px; }
.contacts-info a{ text-decoration:underline; }

.banner{
  position:absolute; left:149px; top:4401px; width:547px; height:247px;
  border-radius:25px; overflow:hidden;
}
.banner img{ width:100%; height:100%; object-fit:cover; }
.banner-tint{ position:absolute; inset:0; background:rgba(135,135,135,0.2); }

/* form — .field positioned at each label's Y; underline lands ~52px below */
.contact-form{ position:absolute; left:0; top:0; width:100%; height:0; }
.field{ position:absolute; left:952px; width:818px; display:block; }
.field span{
  position:absolute; left:0; top:0;
  font-family:'Montserrat'; font-weight:400; font-size:20px; color:var(--white); opacity:.53;
  pointer-events:none; transition:opacity .2s;
}
.field input{
  display:block; margin-top:16px;
  width:818px; height:36px;
  background:transparent; border:none;
  border-bottom:1px solid rgba(255,255,255,0.75);
  font-family:'Montserrat'; font-size:20px; color:var(--white); outline:none;
}
.field input:focus{ border-bottom-color:var(--gold); }
.field input:focus ~ span,
.field input:not(:placeholder-shown) ~ span{ opacity:0; }
.field-name{ top:4158px; }
.field-email{ top:4271px; }
.field-phone{ top:4390px; }
.field-region{ top:4504px; }
.field-msg{ top:4620px; }
.pill-submit{ left:952px; top:4720px; width:148.42px; }
.form-legal{
  position:absolute; left:952px; top:4775px; width:818px;
  font-family:'Montserrat'; font-weight:400; font-size:10px; line-height:normal; color:var(--white); opacity:.53;
}

/* ---------- Footer ---------- */
/* Contacts through footer are all shifted +60px vs. the original mockup-
   matched values (was contacts-h:3222, footer-bar:4099, etc — see git-style
   diff of the numbers below) to restore breathing room that the API/SAE
   section's own +60 shift ate into: .tech-p's declared height:49px doesn't
   match its real wrapped-text height (~73px, confirmed via Range().getBoun
   dingClientRect() — the box's own rect lies about how tall the content
   actually renders), so pushing tech-p down without also pushing Contacts
   left the real paragraph text just ~1px from "Контакты" — found via this
   same measurement technique, not visually (a plain getBoundingClientRect on
   the paragraph itself would have missed it, since it reports the box's
   declared 49px, not the overflowing rendered text). --page-h bumped
   4254->4314 in :root to match, so the scrollable area still reaches the
   (now lower) footer — .page has overflow:hidden and main.js sizes the
   scroll wrapper off this value. */
/* Transform-free marker at the top of the footer region — the scroll-reveal
   in main.js measures progress off this stable point (the logo/texts below
   are translated every frame, so measuring them would feed back on itself). */
.footer-anchor{
  position:absolute; left:0; top:4902px; width:1px; height:1px;
  pointer-events:none;
}
/* Footer plate — darker band that marks the footer zone. Spans from the anchor
   to the page bottom, sits behind the logo/fine print, and rises with them. */
.footer-bar{
  position:absolute; left:0; top:4902px; width:1920px; height:420px;
  background:var(--footer); z-index:0;
  will-change:transform, opacity;
}
/* Big OILRIGHT wordmark (Group (5).png, native 1617x234). Sits above the
   fine print; both rise into view on scroll (opacity/translateY written
   inline by the footer reveal in main.js — no CSS transition, it's scrubbed
   to scroll, not a one-shot). */
.footer-logo{
  position:absolute; left:150px; top:4950px; width:1620px; height:auto;
  z-index:1; pointer-events:none; will-change:transform, opacity;
}
/* The footer logo is a link back up to the hero (scrolls to the top of the
   page). Re-enable pointer events (the base .footer-logo disables them) and
   make the <img> fill the anchor so its box/position is unchanged. */
.footer-logo-link{ pointer-events:auto; cursor:pointer; display:block; }
.footer-logo-link img{ display:block; width:100%; height:auto; }
.footer-legal{
  position:absolute; left:150px; top:5242px; width:626px;
  font-family:'Montserrat'; font-weight:400; font-size:10.597px; line-height:normal; color:var(--white); opacity:.66; z-index:1;
  will-change:transform, opacity;
}
.footer-name{
  position:absolute; left:940px; top:5250px;
  font-family:'Montserrat'; font-weight:400; font-size:10.597px; color:var(--white); opacity:.66; z-index:1;
  will-change:transform, opacity;
}
.footer-rights{
  position:absolute; left:1611px; top:5240px;
  font-family:'Montserrat'; font-weight:400; font-size:10.597px; color:var(--white); opacity:.66; z-index:1; white-space:pre-wrap;
  will-change:transform, opacity;
}
.footer-policy{
  position:absolute; left:1553px; top:5266px;
  font-family:'Montserrat'; font-weight:400; font-size:10.597px; color:var(--white); opacity:.66; z-index:1;
  will-change:transform, opacity;
}

/* ============ PRELOADER ============ */
#preloader{
  position:fixed; inset:0; z-index:1000;
  background:var(--bg);
  display:flex; align-items:center; justify-content:center;
  transition:opacity .6s ease, visibility .6s ease;
}
#preloader.hidden{ opacity:0; visibility:hidden; }
.preloader-inner{
  display:flex; flex-direction:column; align-items:center;
  transform:translateY(8px);
}
.preloader-logo{
  width:340px; height:49px; /* keep logo aspect 269.4/38.87 — svg has preserveAspectRatio:none */
  opacity:0;
  transform:scale(.86) translateY(10px);
  filter:drop-shadow(0 10px 30px rgba(0,0,0,0.25));
  animation:logo-in 1s cubic-bezier(.2,.7,.2,1) forwards;
}
.preloader-bar{
  margin-top:44px;
  width:320px; height:4px;
  border-radius:99px;
  background:rgba(255,255,255,0.18);
  overflow:hidden;
  opacity:0;
  animation:bar-appear .6s ease .5s forwards;
}
.preloader-bar-fill{
  display:block; height:100%; width:0%;
  border-radius:99px;
  background:linear-gradient(90deg, #c3a567, #e6cf9a);
  transition:width .3s ease;
}
.preloader-hint{
  display:none;
  margin-top:28px; max-width:420px;
  font-family:'Montserrat','Segoe UI',Arial,sans-serif; font-weight:400;
  font-size:15px; line-height:1.5; text-align:center; color:rgba(255,255,255,0.9);
}
.preloader-hint b{ color:var(--gold); font-weight:600; }

@keyframes logo-in{
  0%{ opacity:0; transform:scale(.86) translateY(10px); }
  60%{ opacity:1; }
  100%{ opacity:1; transform:scale(1) translateY(0); }
}
@keyframes bar-appear{ to{ opacity:1; } }

/* ============ SCROLL REVEAL ============
   Fade + rise + a touch of blur-out — the blur is what pushes this from a
   plain slide-fade into something that reads as "premium"; the expo-out
   curve (cubic-bezier(.16,1,.3,1)) decelerates hard right at the end so it
   still feels crisp/settled rather than mushy despite the added blur. */
.reveal{
  opacity:0; transform:translateY(36px); filter:blur(6px);
  transition:opacity .95s cubic-bezier(.16,1,.3,1), transform .95s cubic-bezier(.16,1,.3,1), filter .95s cubic-bezier(.16,1,.3,1);
  transition-delay:var(--reveal-delay, 0s);
}
.reveal.is-visible{ opacity:1; transform:translateY(0); filter:blur(0); }


/* Same family as .reveal, but a separate class on purpose — .std-h must NOT
   be caught by observeReveals()'s blanket `.reveal` query in main.js (it
   reveals itself via the 'cards:done' event instead, see CARDS/6b in
   main.js). Deliberately lighter/quicker than .reveal, not heavier: this
   text already waits behind the whole cards pin sequence, so its own
   entrance needs to feel prompt, not add another slow beat on top. */
.std-h-gate{
  opacity:0; transform:translateY(8px);
  transition:opacity .16s cubic-bezier(.22,1,.36,1), transform .16s cubic-bezier(.22,1,.36,1);
}
.std-h-gate.is-visible{ opacity:1; transform:translateY(0); }

/* Cards' hidden/rising state is driven entirely by GSAP (see CARDS in
   main.js, gsap.set/timeline) — no CSS class needed here, GSAP writes
   the transform/opacity inline every scrub frame. */

/* ============ HERO STAGED INTRO ============
   After the preloader, the first screen builds up piece by piece
   (canister first, then the copy around it) — see HERO_SEQ in main.js. */
.hero-anim{
  opacity:0; transform:translateY(34px);
  transition:opacity 1.05s ease, transform 1.2s cubic-bezier(.2,.7,.2,1);
  will-change:opacity, transform;
}
.hero-anim.a-bottle{ transform:translateY(34px) scale(.955); }
.hero-anim.a-fade{ transform:none; }            /* watermark / nav — fade only */
.hero-anim.in{ opacity:1; transform:none; }
