/* ============================================================
   Scrub Daddy — Landing Hero
   Ivory-grey bg · Scrub Daddy yellow point · Poppins everywhere
   ============================================================ */

:root{
  --bg:#FAFAFA; --surface:#FFFFFF; --ink:#16140F; --muted:#6B6760;
  --brand:#FFCE00; --brand-2:#FF5436; --line:#E4E2DB; --radius:28px; --nav-h:72px;
  --accent:#FF5436;        /* current theme accent (JS overrides per use-case) */
  --bg-fade:400ms;

  /* Figma design tokens (Landing Page · S01) */
  --sd-blue:#0C5AAD;          /* blue/600  */
  --sd-title-blue:#2C77C8;    /* S01 title: line1 text + "Satisfying" stroke */
  --sd-yellow-300:#FFDF52;    /* yellow/300 */
  --sd-yellow-400:#FECF00;    /* display fill base */
  --sd-gray-900:#171717;      /* gray/900  */
  --sd-gray-800:#262626;      /* gray/800  */
  --sd-gray-700:#404040;      /* gray/700  */
  --sd-gray-600:#525252;      /* gray/600  */
  --sd-gray-300:#D4D4D4;      /* gray/300 — disabled pill */
  --sd-gray-200:#E5E5E5;      /* gray/200 — picker frame */
  --sd-gray-100:#F5F5F5;      /* gray/100 — disabled label */
  --sd-gray-50:#FAFAFA;       /* gray/50   */
  --sd-yellow-500:#FECF00;    /* yellow/500 — progress fill */
  --sd-grid-line:rgba(229,229,229,.4);   /* #E5E5E5 at 40% (normal blend) — the one BG stroke on every screen */
  --sd-grid-cell:calc(100vw / 32);      /* 32 square columns across the viewport (Figma 1:47 "BG") */
  /* 4-point sparkle grid inside the display type (Figma fill texture), on a 12px square
     pitch at 114px type → 0.105em, so it scales with the headline.
     Turned up from the Figma sample on request: the stars were #FFDF52, the same colour
     as the wash laid over them, so across the top half of the word they cancelled out
     and the texture only surfaced near the baseline. White at 62% lightens whatever it
     lands on instead, so the grid reads over the wash and the base alike, and the arms
     are fattened (control points pushed off the centre, which is what made them
     hairline-thin). The two knobs, if this wants dialling: fill-opacity here and how far
     the control points sit off 10 10 — past about 2 the arms fuse and the grid stops
     reading as sparkles and starts reading as a quilt. */
  --sd-sparkle:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='%23FFFFFF' fill-opacity='.45' d='M10 1Q11.2 8.8 19 10Q11.2 11.2 10 19Q8.8 11.2 1 10Q8.8 8.8 10 1Z'/%3E%3C/svg%3E");

  /* step transition (S02 ⇄ S03 ⇄ S04 ⇄ S05) — one clock and one ease for every panel */
  --step-dur:.5s;
  --step-ease:cubic-bezier(.4, 0, .2, 1);
  --step-shift:8%;
}

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

html{ scroll-behavior:smooth; background:var(--bg); }

body{
  margin:0;
  font-family:'Montserrat',system-ui,sans-serif;
  background:transparent;          /* the themed layer (.theme-bg) shows through */
  color:var(--ink);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
  transition:color var(--bg-fade) ease;
}

/* Themed background layer — crossfades when a use-case is picked. It also carries the
   BG stroke grid, so S01 and S02 keep the same grid while the hero layer fades out.
   (S03–S05 are opaque fixed panels and draw the same grid on their own ::before.) */
.theme-bg{
  position:fixed; inset:0; z-index:-1;
  background-color:var(--bg);
  background-image:
    linear-gradient(to right, var(--sd-grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--sd-grid-line) 1px, transparent 1px);
  background-size:var(--sd-grid-cell) var(--sd-grid-cell);
  transition:background-color var(--bg-fade) ease;
  pointer-events:none;
}

/* Lenis smooth scroll */
html.lenis, html.lenis body{ height:auto; }
.lenis.lenis-smooth{ scroll-behavior:auto !important; }
.lenis.lenis-stopped{ overflow:hidden; }

a{ color:inherit; text-decoration:none; }

/* Visible keyboard focus */
:focus-visible{
  outline:3px solid var(--brand-2);
  outline-offset:3px;
  border-radius:6px;
}

/* ---------- Buttons / sponge-press pills ---------- */
.btn{
  --press-x:1; --press-y:1;
  display:inline-flex; align-items:center; justify-content:center;
  font-family:inherit; font-weight:600; font-size:1rem; line-height:1;
  padding:16px 28px; border-radius:999px; cursor:pointer;
  border:2px solid transparent; white-space:nowrap;
  transform:scale(var(--press-x),var(--press-y));
  transition:transform .2s cubic-bezier(.34,1.56,.64,1), background-color .2s, color .2s, border-color .2s;
  will-change:transform;
}
.btn--ink{ background:var(--ink); color:#fff; }
.btn--ghost{ background:transparent; color:var(--ink); border-color:var(--ink); }

.btn:hover{ --press-x:1.04; --press-y:.96; }
.btn:active{ --press-x:.96; --press-y:1.04; }
.btn--ghost:hover{ background:var(--ink); color:#fff; }

/* ---------- GNB (Figma 23:1122) ----------
   The comp fills the bar with #FAFAFA at 60% and blends it MULTIPLY, which is why it
   reads as a pale wash over the white hero but as a faint darkening of whatever tint a
   step is using. mix-blend-mode cannot do that here — the bar is fixed with a z-index,
   so it owns a stacking context and the blend would never reach the page behind it.
   Compositing black at 1.18% is the exact algebraic equivalent for any backdrop:
     multiply(250) x 60% + 40%  =  250/255 x .6 + .4  =  .98824  =  1 - .01176
   Over white it lands on #FCFCFC, the same colour the literal fill gives. */
.gnb{
  position:fixed; inset:0 0 auto 0; z-index:100;
  height:var(--nav-h);
  display:flex; align-items:center;
  background:rgba(0,0,0,.0118);
  backdrop-filter:blur(3.5px);
  -webkit-backdrop-filter:blur(3.5px);
  border-bottom:1px solid #F5F5F5;
  transition:backdrop-filter .2s ease;
}
/* Scrolling only deepens the blur — the comp has one fill for every step. */
.gnb.is-stuck{
  backdrop-filter:blur(10px) saturate(1.2);
  -webkit-backdrop-filter:blur(10px) saturate(1.2);
}
/* Figma 60:1130: 40px spacer | logo | 40px language icon, space-between. A 1fr/auto/1fr
   grid keeps the logo dead centre while its width animates between the two variants. */
.gnb__inner{
  width:100%; margin:0 auto; padding:0 64px;
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
}
.gnb__logo{ grid-column:2; }
.gnb__lang{
  grid-column:3; justify-self:end;
  width:40px; height:40px; padding:0; border:0; background:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  border-radius:999px; transition:background-color .15s ease;
}
/* hover&pressed (Figma 61:1150/61:1154): #F5F5F5 at 50%, MULTIPLY, in a pill.
   Same stacking-context problem as the bar itself (see .gnb), so it is swapped for
   its exact equivalent: 245/255 x .5 + .5 = .98039 -> black at 1.96%. */
.gnb__lang:hover, .gnb__lang:active{ background-color:rgba(0,0,0,.0196); }
/* ENG shows the globe (61:1148); KOR its own mark (61:1152) — 19.5px art centred
   in the 24px icon box, as the comp's 12.5% inset + 4.17% stroke bleed works out. */
.gnb__lang img{ display:block; width:24px; height:24px; }
.gnb__lang .gnb__lang-kor{ width:19.5px; height:19.5px; margin:2.25px; display:none; }
html[lang="ko"] .gnb__lang-eng{ display:none; }
html[lang="ko"] .gnb__lang .gnb__lang-kor{ display:block; }
/* Clipping window onto whichever logo artwork is showing (Figma 32:1087).
   --logo-t drives the S01 -> S02 collapse: 0 = Variant2 (80×48 wordmark),
   1 = Default (40×40 sponge mark). script.js scrubs it off the pin timeline;
   everything past S02 pins it to 1. */
.gnb__logo{
  --logo-t:0;
  display:block; position:relative;
  width:calc(80px + (40px - 80px) * var(--logo-t));
  height:calc(48px + (40px - 48px) * var(--logo-t));
  overflow:hidden;
}
/* Both are anchored on their CENTRE rather than a top/left offset, so they stay put
   while the box above resizes between the two variants mid-scroll. */
.gnb__logo-art{ position:absolute; max-width:none; object-fit:cover; }
/* Variant2: 101.333×57 bleeding out of the 80×48 frame, centred (its 57 tall art is
   dead centre of the 48 window, so top:50% reproduces the comp's top:-4.5px). */
.gnb__logo-full{
  left:calc(50% - .33px); top:50%; width:101.333px; height:57px;
  transform:translate(-50%,-50%) scale(calc(1 - .06 * var(--logo-t)));
  opacity:calc(1 - var(--logo-t));
}
/* Default: 40×38.678 art tilted -11.74°, sitting +1.017 / -0.326 off the frame centre */
.gnb__logo-mark{
  left:calc(50% + 1.017px); top:calc(50% - .326px); width:40px; height:38.678px;
  transform:translate(-50%,-50%) rotate(-11.74deg) scale(calc(.94 + .06 * var(--logo-t)));
  opacity:var(--logo-t);
}
/* the overlay steps are entered by button, not scroll, so they get a real transition */
body.scrubbing .gnb__logo{ transition:width .35s cubic-bezier(.6,0,.2,1); }
body.scrubbing .gnb__logo-art{ transition:opacity .35s ease, transform .35s cubic-bezier(.6,0,.2,1); }
@media (prefers-reduced-motion:reduce){
  .gnb__logo, .gnb__logo-art{ transition:none !important; }
}

.gnb__nav{ display:flex; align-items:center; gap:26px; margin-left:18px; }
.gnb__nav a{ font-weight:500; color:var(--ink); position:relative; }
.gnb__nav a:not(.gnb__cart)::after{
  content:""; position:absolute; left:0; right:0; bottom:-6px; height:2px;
  background:var(--brand-2); transform:scaleX(0); transform-origin:left;
  transition:transform .2s ease;
}
.gnb__nav a:not(.gnb__cart):hover::after{ transform:scaleX(1); }

.gnb__cart{ display:flex; align-items:center; position:relative; }
.gnb__cart-count{
  position:absolute; top:-7px; right:-9px;
  background:var(--brand-2); color:#fff;
  font-size:.62rem; font-weight:700; line-height:1;
  min-width:16px; height:16px; padding:0 4px;
  border-radius:999px; display:flex; align-items:center; justify-content:center;
}

.gnb__actions{ margin-left:auto; display:flex; align-items:center; gap:14px; }
.gnb__cta{ padding:11px 20px; font-size:.92rem; }

/* ---------- Pinned hero ⇄ selector ---------- */
.pinwrap{ position:relative; }
/* Columns mirror the Figma frame: 748px copy/list + 666px stage, pushed to the
   outer edges. At 1920 the 1680px track lands the left edge on the design's 120px. */
.pinwrap__inner{
  height:100vh; min-height:560px;
  max-width:1680px; margin:0 auto;
  padding:calc(var(--nav-h) + 12px) 32px 40px;
  display:grid; grid-template-columns:minmax(0,748px) minmax(0,666px);
  justify-content:space-between; gap:40px; align-items:center;
}

/* S02 hands over to S03: it drifts a little left and fades, on the same clock and ease
   as the step panels (see "Step transition" near the end of the file). It used to fly
   the full -110% off screen, which is the "whole screen flips over" feel we dropped. */
.pinwrap__inner{ transition:transform var(--step-dur) var(--step-ease), opacity var(--step-dur) var(--step-ease); }
body.scrubbing .pinwrap__inner{ transform:translateX(calc(-1 * var(--step-shift))); opacity:0; }

/* left column holds both panels, overlapped; GSAP crossfades them on scroll */
.panel-stack{ position:relative; align-self:stretch; }
.panel{
  position:absolute; inset:0;
  display:flex; flex-direction:column; justify-content:center;
  will-change:transform,opacity;
}
.panel--selector{ opacity:0; visibility:hidden; } /* revealed by scroll (or by .no-pin) */
.panel--how{ opacity:0; visibility:hidden; }        /* revealed after a card is picked */
/* During How It Works we hide the selector via a class (NOT gsap) so the pinned
   hero↔selector timeline keeps control of #panelSelector. !important beats the
   timeline's inline autoAlpha; removing the class hands control straight back. */
body.step-how .panel--selector{ opacity:0 !important; visibility:hidden !important; }

/* Back button on the How-It-Works panel (mirrors the old scrub back button) */
.how__back{
  align-self:flex-start; display:inline-flex; align-items:center; gap:6px;
  margin:0 0 16px; padding:7px 14px 7px 10px; border-radius:999px; cursor:pointer;
  font:inherit; font-weight:600; font-size:.85rem; color:var(--ink);
  background:rgba(0,0,0,.05); border:1px solid var(--line); transition:background-color .2s, transform .2s;
}
.how__back:hover{ background:rgba(0,0,0,.1); transform:translateX(-2px); }

.hero__copy{ max-width:560px; }

/* ---------- Hero physics playground ---------- */
.pinwrap{ position:relative; }
.pinwrap__inner{ position:relative; z-index:1; }        /* selector/how/stage layer */
.playground{ position:absolute; inset:0; z-index:2; touch-action:none; }
.playground canvas{ display:block; width:100%!important; height:100%!important; cursor:grab; }
.playground canvas:active{ cursor:grabbing; }

/* Hero backdrop — kept as the layer the pin timeline fades out, but it no longer paints
   anything: gray/50 and the 32-column grid now live on .theme-bg (fixed, under every
   screen) so S02 keeps the grid after the hero fades. */
.hero-bg{
  position:absolute; inset:0; z-index:0; pointer-events:none;
  background:transparent;
}

/* centered hero copy over the sponges; text lets the mouse through to the playground,
   only the scroll button is clickable */
.hero-copy{
  position:absolute; inset:0; z-index:3; pointer-events:none;
  display:flex; flex-direction:column; align-items:center; justify-content:flex-start;
  text-align:center; padding:106px 24px 40px;
}
.hero-copy .btn{ pointer-events:auto; }                 /* buttons clickable; text is not */

/* --- S01 copy stack (Figma 1:589 "text") --- */
.hero__text{
  display:flex; flex-direction:column; align-items:center; gap:48px;
}
.hero__top{
  display:flex; flex-direction:column; align-items:center; gap:8px;
}

.hero__tag{
  margin:0; padding:6px 12px; border-radius:4px;
  background:var(--sd-yellow-300);
  border:1px solid var(--sd-blue);
  color:var(--sd-blue);
  font-weight:500; font-size:14px; line-height:1.4; letter-spacing:-.02em;
  white-space:nowrap;
}

.hero__headline{
  margin:0;
  display:flex; flex-direction:column; align-items:center; gap:8px;
  font-family:'Montserrat Alternates','Montserrat',system-ui,sans-serif;
  font-weight:800; line-height:1.2;
  white-space:nowrap;
}
/* letter-spacing is declared per line so the em resolves against each line's own size */
.hero__line1{
  color:var(--sd-title-blue);
  font-size:clamp(2.1rem, 5vw, 96px);
  letter-spacing:-.02em;
}
/* Yellow gradient fill + blue outline. The ::before paints a blue, stroked copy of
   the same glyphs behind the gradient fill, so only the rim shows through. */
.hero__line2{
  /* Every derived length is a calc() off --size rather than an em: em units resolve
     against a stale font-size inside ::before, which silently shrank the outline. */
  --size:clamp(2.5rem, 5.94vw, 114px);
  /* The glyph descenders (y, g) hang below the 1.2 line box, and background-clip:text
     paints nothing outside the padding box — so the box is grown by --lead and pulled
     back with an equal negative margin, leaving layout untouched. */
  --lead:calc(var(--size) * .2);
  position:relative; display:inline-block;
  padding:var(--lead) 0;
  margin:calc(var(--lead) * -1) 0;
  font-size:var(--size);
  letter-spacing:-.02em;
  color:transparent;
  /* Three layers, in the design's own order: the sparkle texture on top, then the
     #FFDF52 wash that fades out downward, over a solid #FECF00 base. I had dropped the
     wash earlier after sampling the render and reading it as flat — get_design_context
     shows it is really there (yellow/300 from 4.38% to transparent at 121.17%), so the
     word is lighter at the top than at the baseline. */
  background-image:
    var(--sd-sparkle),
    linear-gradient(180deg, var(--sd-yellow-300) 4.3796%, rgba(255,223,82,0) 121.17%),
    linear-gradient(90deg, var(--sd-yellow-400), var(--sd-yellow-400));
  background-size:calc(var(--size) * .105) calc(var(--size) * .105), 100% 100%, 100% 100%;
  background-repeat:repeat, no-repeat, no-repeat;
  -webkit-background-clip:text; background-clip:text;
}
/* Blue rim, 13px at 114px type (.114 of the size).
   NOT -webkit-text-stroke: its joins are mitred with no way to change them, so every
   acute corner (S, f, y, g) grew a spike. Instead the blue copy is dilated by a ring
   of 64 shadows at radius --rim — a disc dilation, which rounds every join by
   construction. 64 and not 32: at 32 the samples sit ~2.5px apart on the rim and the
   stroke terminals visibly facet. The last shadow is the slight drop under the letters. */
/* Rounded display outline — one rule, both display words (S01 "Satisfying",
   S03 "Done!"). 64 shadows on a circle of radius --rim is a disc dilation, so every
   join comes out round; -webkit-text-stroke only offers mitred joins, which spike.
   NOTE: this cannot live in a custom property — var() inside one is substituted at
   the :root declaration, where --rim does not exist, so the whole value dies. */
.hero__line2::before,
.s3__doneText::before{
  text-shadow:
    calc(var(--rim) *  1.0000) calc(var(--rim) *  0.0000) 0 var(--rim-color),
    calc(var(--rim) *  0.9952) calc(var(--rim) *  0.0980) 0 var(--rim-color),
    calc(var(--rim) *  0.9808) calc(var(--rim) *  0.1951) 0 var(--rim-color),
    calc(var(--rim) *  0.9569) calc(var(--rim) *  0.2903) 0 var(--rim-color),
    calc(var(--rim) *  0.9239) calc(var(--rim) *  0.3827) 0 var(--rim-color),
    calc(var(--rim) *  0.8819) calc(var(--rim) *  0.4714) 0 var(--rim-color),
    calc(var(--rim) *  0.8315) calc(var(--rim) *  0.5556) 0 var(--rim-color),
    calc(var(--rim) *  0.7730) calc(var(--rim) *  0.6344) 0 var(--rim-color),
    calc(var(--rim) *  0.7071) calc(var(--rim) *  0.7071) 0 var(--rim-color),
    calc(var(--rim) *  0.6344) calc(var(--rim) *  0.7730) 0 var(--rim-color),
    calc(var(--rim) *  0.5556) calc(var(--rim) *  0.8315) 0 var(--rim-color),
    calc(var(--rim) *  0.4714) calc(var(--rim) *  0.8819) 0 var(--rim-color),
    calc(var(--rim) *  0.3827) calc(var(--rim) *  0.9239) 0 var(--rim-color),
    calc(var(--rim) *  0.2903) calc(var(--rim) *  0.9569) 0 var(--rim-color),
    calc(var(--rim) *  0.1951) calc(var(--rim) *  0.9808) 0 var(--rim-color),
    calc(var(--rim) *  0.0980) calc(var(--rim) *  0.9952) 0 var(--rim-color),
    calc(var(--rim) *  0.0000) calc(var(--rim) *  1.0000) 0 var(--rim-color),
    calc(var(--rim) * -0.0980) calc(var(--rim) *  0.9952) 0 var(--rim-color),
    calc(var(--rim) * -0.1951) calc(var(--rim) *  0.9808) 0 var(--rim-color),
    calc(var(--rim) * -0.2903) calc(var(--rim) *  0.9569) 0 var(--rim-color),
    calc(var(--rim) * -0.3827) calc(var(--rim) *  0.9239) 0 var(--rim-color),
    calc(var(--rim) * -0.4714) calc(var(--rim) *  0.8819) 0 var(--rim-color),
    calc(var(--rim) * -0.5556) calc(var(--rim) *  0.8315) 0 var(--rim-color),
    calc(var(--rim) * -0.6344) calc(var(--rim) *  0.7730) 0 var(--rim-color),
    calc(var(--rim) * -0.7071) calc(var(--rim) *  0.7071) 0 var(--rim-color),
    calc(var(--rim) * -0.7730) calc(var(--rim) *  0.6344) 0 var(--rim-color),
    calc(var(--rim) * -0.8315) calc(var(--rim) *  0.5556) 0 var(--rim-color),
    calc(var(--rim) * -0.8819) calc(var(--rim) *  0.4714) 0 var(--rim-color),
    calc(var(--rim) * -0.9239) calc(var(--rim) *  0.3827) 0 var(--rim-color),
    calc(var(--rim) * -0.9569) calc(var(--rim) *  0.2903) 0 var(--rim-color),
    calc(var(--rim) * -0.9808) calc(var(--rim) *  0.1951) 0 var(--rim-color),
    calc(var(--rim) * -0.9952) calc(var(--rim) *  0.0980) 0 var(--rim-color),
    calc(var(--rim) * -1.0000) calc(var(--rim) *  0.0000) 0 var(--rim-color),
    calc(var(--rim) * -0.9952) calc(var(--rim) * -0.0980) 0 var(--rim-color),
    calc(var(--rim) * -0.9808) calc(var(--rim) * -0.1951) 0 var(--rim-color),
    calc(var(--rim) * -0.9569) calc(var(--rim) * -0.2903) 0 var(--rim-color),
    calc(var(--rim) * -0.9239) calc(var(--rim) * -0.3827) 0 var(--rim-color),
    calc(var(--rim) * -0.8819) calc(var(--rim) * -0.4714) 0 var(--rim-color),
    calc(var(--rim) * -0.8315) calc(var(--rim) * -0.5556) 0 var(--rim-color),
    calc(var(--rim) * -0.7730) calc(var(--rim) * -0.6344) 0 var(--rim-color),
    calc(var(--rim) * -0.7071) calc(var(--rim) * -0.7071) 0 var(--rim-color),
    calc(var(--rim) * -0.6344) calc(var(--rim) * -0.7730) 0 var(--rim-color),
    calc(var(--rim) * -0.5556) calc(var(--rim) * -0.8315) 0 var(--rim-color),
    calc(var(--rim) * -0.4714) calc(var(--rim) * -0.8819) 0 var(--rim-color),
    calc(var(--rim) * -0.3827) calc(var(--rim) * -0.9239) 0 var(--rim-color),
    calc(var(--rim) * -0.2903) calc(var(--rim) * -0.9569) 0 var(--rim-color),
    calc(var(--rim) * -0.1951) calc(var(--rim) * -0.9808) 0 var(--rim-color),
    calc(var(--rim) * -0.0980) calc(var(--rim) * -0.9952) 0 var(--rim-color),
    calc(var(--rim) *  0.0000) calc(var(--rim) * -1.0000) 0 var(--rim-color),
    calc(var(--rim) *  0.0980) calc(var(--rim) * -0.9952) 0 var(--rim-color),
    calc(var(--rim) *  0.1951) calc(var(--rim) * -0.9808) 0 var(--rim-color),
    calc(var(--rim) *  0.2903) calc(var(--rim) * -0.9569) 0 var(--rim-color),
    calc(var(--rim) *  0.3827) calc(var(--rim) * -0.9239) 0 var(--rim-color),
    calc(var(--rim) *  0.4714) calc(var(--rim) * -0.8819) 0 var(--rim-color),
    calc(var(--rim) *  0.5556) calc(var(--rim) * -0.8315) 0 var(--rim-color),
    calc(var(--rim) *  0.6344) calc(var(--rim) * -0.7730) 0 var(--rim-color),
    calc(var(--rim) *  0.7071) calc(var(--rim) * -0.7071) 0 var(--rim-color),
    calc(var(--rim) *  0.7730) calc(var(--rim) * -0.6344) 0 var(--rim-color),
    calc(var(--rim) *  0.8315) calc(var(--rim) * -0.5556) 0 var(--rim-color),
    calc(var(--rim) *  0.8819) calc(var(--rim) * -0.4714) 0 var(--rim-color),
    calc(var(--rim) *  0.9239) calc(var(--rim) * -0.3827) 0 var(--rim-color),
    calc(var(--rim) *  0.9569) calc(var(--rim) * -0.2903) 0 var(--rim-color),
    calc(var(--rim) *  0.9808) calc(var(--rim) * -0.1951) 0 var(--rim-color),
    calc(var(--rim) *  0.9952) calc(var(--rim) * -0.0980) 0 var(--rim-color),
    0 var(--rim-drop, 0px) 0 var(--rim-color);
}

.hero__line2::before{
  content:attr(data-text);
  position:absolute; left:0; top:var(--lead); z-index:-1;
  color:var(--sd-title-blue);
  --rim:calc(var(--size) * .114);
  --rim-color:var(--sd-title-blue);
  --rim-drop:calc(var(--size) * .02);   /* the slight blue lip under the letters */
}

.hero__sub{
  margin:0;
  color:var(--sd-gray-600); font-weight:400;
  font-size:clamp(1rem, 1.25vw, 24px); line-height:1.4; letter-spacing:-.02em;
}
/* Each sentence is an atomic box: one line when there is room, otherwise the break
   lands between the two sentences instead of after "Then find your". */
.hero__sub span{ display:inline-block; }

/* Scroll-down pill (Figma 1:597). Held back until the sponge pile has landed
   (playground fires scrub:sponges-settled), then it fades in and keeps pulsing so
   it reads against the busy pile behind it. */
/* 40px above the frame bottom, centred. Centring uses auto margins rather than a
   translate so the reveal/hover transforms below stay free to animate. */
.hero__scroll{
  /* Low in the hero, per the design — which means it lands on the sponge pile. It is
     above the canvas in z, so it is never occluded. No white ring (removed on request);
     the blue pulse ring below is the only halo. */
  position:absolute; left:0; right:0; bottom:40px; margin-inline:auto; z-index:4;
  flex:none; pointer-events:none;
  display:flex; align-items:center; justify-content:center;
  width:40px; height:40px; border-radius:99px;
  background:var(--sd-title-blue);   /* #2C77C8 — same blue as the S01 title (design spec, 2026-09-16) */
  box-shadow:0 4px 12px rgba(0,0,0,.20);
  opacity:0; transform:translateY(10px);
  transition:opacity .45s ease, transform .45s cubic-bezier(.34,1.56,.64,1);
}
.hero__scroll.is-ready{
  pointer-events:auto;
  opacity:1; transform:none;
  animation:sd-scroll-ring 1.9s ease-out infinite;
}
.hero__scroll img{ display:block; width:18px; height:18px; }
.hero__scroll.is-ready img{ animation:sd-scroll-bob 1.9s ease-in-out infinite; }
.hero__scroll:hover{ background:#0a4d94; }
.hero__scroll:active{ transform:scale(.94); }
/* Scrolling on to the selector retires the cue. Declared after .is-ready so it wins,
   and it drops opacity rather than visibility so the existing fade still plays. */
.hero__scroll.is-gone{ opacity:0; pointer-events:none; }
.hero__scroll.is-gone,
.hero__scroll.is-gone img{ animation:none; }

/* halo ping — carries the resting shadow through so it never flickers off.
   The ping is #4F9CEE at 50% (design request) fading out over the sweep. */
@keyframes sd-scroll-ring{
  0%   { box-shadow:0 4px 12px rgba(0,0,0,.20), 0 0 0 3px  rgba(79,156,238,.5); }
  70%  { box-shadow:0 4px 12px rgba(0,0,0,.20), 0 0 0 18px rgba(79,156,238,0); }
  100% { box-shadow:0 4px 12px rgba(0,0,0,.20), 0 0 0 0    rgba(79,156,238,0); }
}
@keyframes sd-scroll-bob{
  0%,100%{ transform:translateY(0); opacity:1; }
  50%    { transform:translateY(4px); opacity:.55; }
}

/* the persistent single sponge is hidden during the hero (the playground is the visual);
   the pin timeline fades it in as you scroll to the selector */
body:not(.no-pin) #stage{ opacity:0; visibility:hidden; }

/* Step eyebrow — still used by the How It Works panel (S04), which is not redesigned yet */
.sel__eyebrow{
  margin:0 0 14px; font-weight:600; font-size:.78rem; letter-spacing:.14em;
  text-transform:uppercase; color:var(--accent);
}

/* ---------- S02 Mess selector (Figma 5:62 · left panel 5:658) ----------
   The Figma frame is 1080 tall and the panel needs 880 of it. The vh terms below hit
   the design numbers exactly at 1080 and compress under it — without them the CTA
   falls off the bottom on a ~820px-tall laptop. */
.panel--selector{
  --sel-gap-lg:clamp(24px, 6.67vh, 72px);   /* 72 at 1080 — text ↔ list block */
  --sel-gap-md:clamp(16px, 4.44vh, 48px);   /* 48 at 1080 — list ↔ CTA */
  --sel-gap-sm:clamp(8px,  1.48vh, 16px);   /* 16 at 1080 — between rows */
  display:flex; flex-direction:column; gap:var(--sel-gap-lg);
  /* `safe` matters on short screens: once the content is taller than the panel,
     plain `center` pushes the top of it up behind the fixed nav. Safe centring
     falls back to flex-start instead, so the tag always stays below the bar. */
  justify-content:center;
  justify-content:safe center;
}

.sel__text{ display:flex; flex-direction:column; gap:var(--sel-gap-sm); align-items:flex-start; }
.sel__top{ display:flex; flex-direction:column; gap:var(--sel-gap-sm); align-items:flex-start; }

.sel__tag{
  margin:0; padding:6px 12px; border-radius:4px;
  background:var(--sd-yellow-300); border:1px solid var(--sd-blue);
  color:var(--sd-blue);
  font-weight:500; font-size:14px; line-height:1.4; letter-spacing:-.02em;
  white-space:nowrap;
}
.sel__title{
  margin:0;
  font-family:'Montserrat Alternates','Montserrat',system-ui,sans-serif;
  font-weight:700; font-size:clamp(1.9rem,2.5vw,48px);
  line-height:1.2; letter-spacing:-.02em; color:var(--sd-gray-800);
  white-space:nowrap;
}
.sel__sub{
  margin:0; color:var(--sd-gray-600);
  font-weight:400; font-size:clamp(1rem,1.04vw,20px);
  line-height:1.4; letter-spacing:-.02em;
}

.sel__bottom{ display:flex; flex-direction:column; gap:var(--sel-gap-md); width:100%; }
.sel__list{ display:flex; flex-direction:column; gap:var(--sel-gap-sm); width:100%; }

/* Idle row */
.mess{
  display:flex; align-items:center; gap:24px;
  width:100%; min-height:clamp(56px, 8.7vh, 94px); padding:16px 24px;
  border-radius:8px;
  background:rgba(255,255,255,.5);
  border:1px solid rgba(229,229,229,.4);
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
  font-family:inherit; text-align:left; cursor:pointer;
  transition:background-color .2s ease, border-color .2s ease, color .2s ease;
}
.mess__left{ display:flex; align-items:center; gap:12px; flex:0 0 auto; }
.mess__icon{ width:28px; height:28px; object-fit:cover; flex:0 0 auto; }
.mess__label{
  width:200px;
  font-weight:600; font-size:20px; line-height:1.4; letter-spacing:-.02em;
  color:var(--sd-gray-700);
}
.mess__desc{
  font-weight:500; font-size:16px; line-height:1.4; letter-spacing:-.02em;
  color:var(--sd-gray-600);
}
.mess:hover{ background:rgba(255,255,255,.78); border-color:rgba(163,163,163,.5); }

/* Selected row */
.mess[aria-checked="true"]{
  background:#fff;
  border-color:var(--sd-gray-600);
  backdrop-filter:none; -webkit-backdrop-filter:none;
}
.mess[aria-checked="true"] .mess__label{ color:var(--sd-gray-900); }
.mess[aria-checked="true"] .mess__desc{ color:var(--sd-gray-700); }

/* CTA — right-aligned under the list, disabled until a mess is picked */
.sel__cta{ display:flex; align-items:center; justify-content:flex-end; width:100%; }
.pill{
  display:flex; align-items:center; justify-content:center;
  height:48px; padding:16px 20px; border:0; border-radius:999px;
  background:var(--sd-yellow-300); color:var(--sd-gray-800);
  font-family:inherit; font-weight:600; font-size:16px; line-height:1.4;
  letter-spacing:-.02em; white-space:nowrap; cursor:pointer;
  transition:background-color .2s ease, color .2s ease, transform .2s cubic-bezier(.34,1.56,.64,1);
}
.pill:hover:not(:disabled){ transform:translateY(-2px); }
.pill:active:not(:disabled){ transform:translateY(0) scale(.97); }
.pill:disabled{
  background:var(--sd-gray-300); color:var(--sd-gray-50);
  cursor:not-allowed; transform:none;
}

/* ---------- Dark theme legibility (only for DARK use-case backgrounds) ---------- */
body.theme-active:not(.theme-light){ color:#EFEDE7; }
body.theme-active:not(.theme-light) .sel__title{ color:#fff; }
body.theme-active:not(.theme-light) .sel__sub{ color:rgba(255,255,255,.66); }
/* (S02 rows dropped from here — every mess tint is light, so this branch never
   applies to the selector any more.) */
body.theme-active:not(.theme-light) .gnb__word,
body.theme-active:not(.theme-light) .gnb__nav a{ color:#fff; }
/* light use-case background (e.g. Glass cup #D1E8F3) keeps the default dark ink text */

/* (Removed: .hero__eyebrow / .hero__headline / .hero__sub / .hero__cta / .hero__hint —
   the pre-Figma hero styles. S01 now lives in the "S01 copy stack" block above.) */

/* Stage / canvas */
.hero__stage{
  position:relative;
  width:100%; height:min(72vh,640px);
  min-height:380px;
  touch-action:none;                       /* no page scroll/zoom while pressing the sponge */
}
.hero__stage canvas{
  display:block; width:100%!important; height:100%!important;
  cursor:grab;
  position:relative; z-index:2;            /* between back and front foam layers */
}
.hero__stage canvas:active{ cursor:grabbing; }

/* Contact shadow under the sponge (driven by JS: widens as it flattens) */
.sponge-shadow{
  position:absolute; z-index:0;
  left:50%; bottom:9%;
  width:42%; height:5%;
  transform:translateX(-50%);
  background:radial-gradient(ellipse at center, rgba(22,20,15,.22), rgba(22,20,15,0) 70%);
  opacity:.16; pointer-events:none;
  filter:blur(2px);
}


/* Foam layers — back sits behind the sponge, front in front of it */
.foam-layer{ position:absolute; inset:0; pointer-events:none; }
.foam-back{ z-index:1; filter:blur(1.4px); opacity:.92; }   /* softened for depth */
.foam-front{ z-index:3; filter:blur(.4px); }                /* slight blur per spec */
.hero__loading{
  position:absolute; inset:0; margin:auto;
  width:120px; height:120px; border-radius:50%;
  background:var(--brand);
  opacity:.35;
  animation:pulse 1.2s ease-in-out infinite;
}
.hero__stage.is-ready .hero__loading{ display:none; }
@keyframes pulse{ 0%,100%{ transform:scale(.85); } 50%{ transform:scale(1.05); } }

/* Foam bubbles (spawned on click) */
.foam{
  position:absolute; bottom:0; left:50%;
  width:var(--s,32px); height:var(--s,32px);
  border-radius:50%;
  background:radial-gradient(circle at 35% 30%, #fff, #fff 38%, rgba(255,251,240,.78)); /* white → cream */
  box-shadow:0 0 0 1px rgba(0,0,0,.04), inset -3px -4px 6px rgba(0,0,0,.07);
  pointer-events:none; will-change:transform,opacity;
}

/* ---------- S03 — Scrub It Clean (Figma 8:241 · left 8:838 · scene 8:867) ----------
   Full-screen overlay that slides in from the right over the selector. Per-mess art,
   picker tint and card frame are set from script.js as custom properties. */
.scrub{
  position:fixed; inset:0; z-index:60;
  /* flex-start + a full-height inner, like S04: the inner owns the height, so overflow
     goes out of the bottom only. Plain `center` let the block grow past BOTH edges once
     it outgrew the window, which slid the tag under the GNB.
     overflow is HIDDEN, not auto: the sponge on the cursor is absolutely positioned and
     roams the whole screen, so near the footer its box hangs past the bottom edge — with
     a scrollbar the page would gain one the moment the cursor went near Next, and the
     15px it takes shrank the whole layout a touch. The stacked <1024 layout below turns
     scrolling back on, where the sponge is not on the cursor anyway. */
  display:flex; align-items:flex-start; overflow:hidden;
  background:var(--scrub-bg, var(--sd-gray-50));   /* opaque: it covers the selector */
  /* the slide / fade itself is the shared step transition further down the file */
}
/* same BG stroke as every other screen; the tint stays on .scrub itself */
.scrub::before{
  content:""; position:absolute; inset:0; pointer-events:none;
  background-image:
    linear-gradient(to right, var(--sd-grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--sd-grid-line) 1px, transparent 1px);
  background-size:var(--sd-grid-cell) var(--sd-grid-cell);
}

.scrub__inner{
  position:relative;                               /* above the ::before grid */
  /* what is left for the columns once the nav and the two paddings are paid for
     (772 — the comp's card — at 1080 with an 80px bar) */
  --s3-avail:calc(100vh - var(--nav-h) - 52px);
  min-height:100%;
  width:100%; max-width:1680px; margin:0 auto;
  padding:calc(var(--nav-h) + 12px) 32px 40px;
  display:grid; grid-template-columns:minmax(0,748px) minmax(0,772px);
  justify-content:space-between; gap:40px; align-items:center;
}

/* ----- left column (Figma 8:838, 748×880, space-between) ----- */
.s3{
  display:flex; flex-direction:column; justify-content:space-between;
  gap:clamp(24px, 4.4vh, 48px);
  height:min(880px, calc(100vh - var(--nav-h) - 92px));
}
.s3__top{ display:flex; flex-direction:column; gap:clamp(20px, 3.7vh, 40px); }
.s3__text{ display:flex; flex-direction:column; gap:16px; align-items:flex-start; }
.s3__head{ display:flex; flex-direction:column; gap:16px; align-items:flex-start; }

.s3__tag{
  margin:0; padding:6px 12px; border-radius:4px;
  background:var(--sd-yellow-300); border:1px solid var(--sd-blue); color:var(--sd-blue);
  font-weight:500; font-size:14px; line-height:1.4; letter-spacing:-.02em; white-space:nowrap;
}
.s3__title{
  margin:0;
  font-family:'Montserrat Alternates','Montserrat',system-ui,sans-serif;
  font-weight:700; font-size:clamp(1.9rem, 2.5vw, 48px);
  line-height:1.2; letter-spacing:-.02em; color:var(--sd-gray-800);
}
.s3__sub{
  margin:0; color:var(--sd-gray-600); font-weight:400;
  font-size:clamp(1rem, 1.04vw, 20px); line-height:1.4; letter-spacing:-.02em;
}

.s3__bt{ display:flex; flex-direction:column; gap:clamp(24px, 4.4vh, 48px); }

/* progress: track + % share an 8px gap, the reset button sits 24px further out */
.s3__progress{ display:flex; align-items:center; gap:8px; }
.s3__track{
  flex:1 1 auto; height:4px; border-radius:999px;
  background:rgba(0,0,0,.1); overflow:hidden;
}
.s3__fill{
  display:block; height:100%; width:0%;
  background:var(--sd-yellow-500); border-radius:999px;
  transition:width .18s linear;
}
.s3__pct{
  flex:0 0 40px; text-align:right;
  font-weight:400; font-size:16px; line-height:1.4; letter-spacing:-.02em; color:var(--sd-gray-800);
}
.s3__reset{
  flex:0 0 auto; margin-left:16px;
  width:32px; height:32px; padding:8px; border:0; border-radius:999px;
  background:rgba(0,0,0,.3); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  transition:background-color .2s, transform .2s cubic-bezier(.34,1.56,.64,1);
}
.s3__reset img{ width:16px; height:16px; display:block; }
.s3__reset:hover{ background:rgba(0,0,0,.45); transform:rotate(-25deg); }

/* scrub picker */
.s3__pick{ display:flex; flex-direction:column; gap:16px; }
.s3__pickLabel{
  margin:0; display:flex; align-items:center; gap:8px;
  font-weight:600; font-size:20px; line-height:1.4; letter-spacing:-.02em; color:var(--sd-gray-700);
}
.s3__pickLabel img{ width:28px; height:28px; display:block; }
.s3__picker{
  display:flex; gap:16px; justify-content:center; padding:16px;
  border:1px solid var(--sd-gray-200); border-radius:12px;
  background:var(--s3-picker-bg, #F1EADE);
}
.s3__block{
  flex:0 1 130px; aspect-ratio:1; padding:0;
  border:1px solid transparent; border-radius:12px; overflow:hidden;
  background:transparent; cursor:pointer;
  transition:background-color .2s, border-color .2s, transform .2s cubic-bezier(.34,1.56,.64,1);
}
.s3__block img{ display:block; width:100%; height:100%; object-fit:cover; }
.s3__block:hover{ transform:translateY(-3px); }
/* The comp marks the picked scrub by BURNING its tile darker (grey on color-burn),
   not by lifting it to white — white was my own invention and read the wrong way. */
.s3__block{ position:relative; }
.s3__block[aria-checked="true"]{ border-color:rgba(0,0,0,.1); }
.s3__block[aria-checked="true"]::before{
  content:""; position:absolute; inset:0; z-index:1; border-radius:12px;
  pointer-events:none; background:#A3A3A3; mix-blend-mode:color-burn;
}


/* footer row */
.s3__foot{ display:flex; align-items:center; justify-content:space-between; }
.textbtn{
  display:flex; align-items:center; gap:4px; padding:8px 0;
  background:none; border:0; cursor:pointer;
  font-family:inherit; font-weight:500; font-size:16px; line-height:1.4;
  letter-spacing:-.02em; color:var(--sd-gray-600);
  flex-shrink:0; white-space:nowrap;   /* Korean breaks per syllable: 이전 would stack 이/전 */
}
.textbtn img{ width:20px; height:20px; display:block; }
.textbtn:hover{ color:var(--sd-gray-800); }

/* ----- right column: the scene card (Figma 8:866, 772 square, radius 24) ----- */
.s3__stage{ display:flex; align-items:center; justify-content:center; }
.s3__card{
  /* A square has to fit the window BOTH ways. Capped only by width, the comp's 772
     needed a 904-tall window (nav + paddings) — on a 1920x800 laptop it ran under the
     GNB and 12px past the bottom edge, which is the clipping the screens showed.
     Same term S04/S05 use for their stages, so all three shrink in step. */
  position:relative; width:max(280px, min(100%, 772px, var(--s3-avail, 772px))); aspect-ratio:1;
  border-radius:24px; overflow:hidden;
  isolation:isolate;            /* the grime's multiply blends with the scene, nothing behind the card */
  touch-action:none;
  container-type:inline-size;   /* lets "Done!" size itself off the card, in cqw */
}
.s3__scene,
.s3__dirt{ position:absolute; inset:0; width:100%; height:100%; display:block; }
.s3__scene{ object-fit:cover; }
.s3__dirt{ z-index:2; mix-blend-mode:multiply; }   /* grime photo multiplies onto the object */
.s3__fx{ position:absolute; inset:0; z-index:3; pointer-events:none; }
/* Moved with a transform, not left/top: repositioning a big drop-shadowed image via
   layout properties every pointermove left repaint trails (a second, ghostly sponge),
   and the card's container-type made it worse. A composited transform has no trail. */
/* Positioned against the section, not the card, so it can roam the whole screen and
   still be whole at the edges. --tool-size tracks the card (37% of it, as in the comp)
   because the sponge no longer inherits the card's width. It never takes pointer
   events: the drag is driven by the section, and the picker sits underneath it. */
.s3__sponge{
  position:absolute; z-index:70;
  width:var(--tool-size, 286px); height:auto;
  left:0; top:0; pointer-events:none;
  user-select:none; -webkit-user-drag:none;
  filter:drop-shadow(0 10px 18px rgba(0,0,0,.22));
  transform:translate3d(var(--tx,0px), var(--ty,0px), 0) scale(calc(var(--ts,1) * var(--pop,1)));
  will-change:transform;
}
.s3__card{ cursor:grab; }
.scrub.is-scrubbing .s3__card{ cursor:grabbing; }
.scrub.is-scrubbing .s3__sponge{ --ts:.96; }
/* Within 32px of a button (Next, Back) the sponge fades out in place — opacity only,
   no movement or shrink, so it never looks like it flew off — and fades back in. */
.s3__sponge{ transition:opacity .2s ease; }
.s3__sponge.is-over-ui{ opacity:0; }
/* Picking another scrub: the sponge on the cursor swells a touch and settles back,
   in place. NOT the standalone `scale` property: that is applied before `transform`,
   so it scaled the translate too and the sponge drifted down-right as it grew. The
   pop is a registered custom property multiplied into the scale() that already sits
   after the translate3d, so it grows around its own centre. */
@property --pop{ syntax:'<number>'; inherits:false; initial-value:1; }
.s3__sponge.is-boing{ animation:spongeBoing .4s ease-out both; }
@keyframes spongeBoing{
  0%  { --pop:1; }
  35% { --pop:1.12; }
  100%{ --pop:1; }
}
/* Hovering a tile in the picker tries that scrub on the cursor. The artwork is swapped
   on the same <img>, so the change would land as a hard cut — this dips it a touch and
   lets it come back, which reads as the sponge being turned over rather than replaced.
   Softer and quicker than the pick's boing: a hover is not a decision. */
.s3__sponge.is-swap{ animation:spongeSwap .22s ease-out both; }
@keyframes spongeSwap{
  0%  { --pop:.9; }
  100%{ --pop:1; }
}
@media (prefers-reduced-motion:reduce){
  .s3__sponge.is-swap,
  .s3__sponge.is-boing{ animation:none; }
}

.s3__hint{
  position:absolute; z-index:5; left:50%; bottom:24px; transform:translateX(-50%);
  margin:0; padding:8px 16px; border-radius:999px;
  background:rgba(0,0,0,.45); color:#fff;
  font-weight:500; font-size:15px; letter-spacing:-.02em; white-space:nowrap;
  transition:opacity .3s ease;
}
.s3__card.is-touched .s3__hint{ opacity:0; }

/* "Done!" (Figma 8:2140): Montserrat Alternates ExtraBold 133.2 on a 772 card, so
   17.25% of the card width — sized in cqw so it tracks the card, not the viewport.
   Blue here is blue/500 #166CC7, a shade lighter than the S01 headline's blue/600. */
.s3__doneFx{ position:absolute; inset:0; z-index:5; pointer-events:none; overflow:hidden; }
.s3__done{
  position:absolute; z-index:6; inset:0;
  display:flex; align-items:center; justify-content:center;
  pointer-events:none;
  animation:nextPop .4s cubic-bezier(.2,1.5,.4,1) both;
}
/* display:flex above would otherwise beat the UA's [hidden]{display:none} */
.s3__done[hidden]{ display:none; }
.s3__doneText{
  --size:17.25cqw;
  --rim:calc(var(--size) * .114);
  --rim-color:#166CC7;
  position:relative; display:inline-block;
  font-family:'Montserrat Alternates','Montserrat',system-ui,sans-serif;
  font-weight:800; font-size:var(--size); line-height:1.2; letter-spacing:-.02em;
  color:var(--sd-yellow-300); white-space:nowrap;
}
.s3__doneText::before{
  content:attr(data-text);
  position:absolute; left:0; top:0; z-index:-1;
  color:var(--rim-color);
}

/* "Next" unlocks only once the mess is gone */
#scrubNext:disabled{ background:var(--sd-gray-300); color:var(--sd-gray-100); cursor:not-allowed; }
@keyframes nextPop{ from{ opacity:0; transform:scale(.85) translateY(6px); } to{ opacity:1; transform:none; } }

/* scrub particles (foam + water) */
.sfx{ position:absolute; border-radius:50%; pointer-events:none; will-change:transform,opacity; }
.sfx--foam{ background:radial-gradient(circle at 35% 30%, #fff, #fff 40%, rgba(255,251,240,.78)); box-shadow:inset -2px -3px 4px rgba(0,0,0,.06); }
.sfx--water{ background:radial-gradient(circle at 40% 30%, rgba(255,255,255,.9), rgba(180,220,255,.55) 60%, rgba(120,180,255,.18)); }
.sfx--spark{ background:#fff; box-shadow:0 0 8px 2px rgba(255,255,255,.8); }

/* Party confetti for the Done! moment — ribbons, chips, triangles and dots */
.cfti{ position:absolute; pointer-events:none; will-change:transform,opacity; }
.cfti--chip{ border-radius:1px; }
.cfti--dot{ border-radius:50%; }
.cfti--tri{ width:0 !important; height:0 !important; background:none !important;
  border-left:5px solid transparent; border-right:5px solid transparent; }
/* a ribbon is an open arc: a rounded box with only its bottom edge painted */
.cfti--ribbon{ background:none !important; border-radius:50%;
  border-style:solid; border-color:transparent; }
/* ---------- No-pin fallback (PIN_ENABLED=false): static, both panels visible ---------- */
body.no-pin .pinwrap__inner{ height:auto; }
body.no-pin .panel{
  position:relative; inset:auto;
  transform:none !important; opacity:1 !important; visibility:visible !important;
  justify-content:flex-start;
}
body.no-pin .panel--selector{ margin-top:30px; }
body.no-pin #stage{ transform:none !important; }

/* ---------- Responsive ----------
   Breakpoints (design spec): Mobile 360 / Tablet 768 / Desktop 1024 / Desktop-large 1440+.
   <1024 stacks to one column (mobile + tablet); <768 holds the phone-only tweaks. ---------- */
/* Between 1024px and 1439px the left column is too narrow to hold a 200px label plus
   the longest description on one line — the description wrapped to three lines and
   pushed the rows (and the CTA) past the fold. Stack them, as the phone layout does. */
@media (min-width:1024px) and (max-width:1439px){
  .mess{ flex-direction:column; align-items:flex-start; gap:2px; padding:10px 20px; }
  .mess__label{ width:auto; font-size:18px; }
  .mess__desc{ font-size:15px; }
}

/* Short desktop screens (1024×768 and friends). One linear vh ramp cannot both hit
   the design numbers at 1080 and stay inside a 768-tall window, so the selector gets
   a tighter set below 900px of height (desktop widths only) — otherwise its CTA drops under the fold. */
@media (min-width:1024px) and (max-height:900px){
  .panel--selector{
    --sel-gap-lg:clamp(20px, 4.6vh, 48px);
    --sel-gap-md:clamp(14px, 3.2vh, 32px);
    --sel-gap-sm:clamp(6px,  1.1vh, 12px);
  }
  .mess{ min-height:clamp(52px, 7.4vh, 72px); }
  .sel__title{ font-size:clamp(1.6rem, 2.2vw, 36px); }
}

@media (max-width:1023px){
  .pinwrap__inner{
    grid-template-columns:1fr;
    height:auto; gap:18px;
    padding:calc(var(--nav-h) + 12px) 18px 48px;
  }
  /* Narrow screens have no room to lay the copy over the sponge, so everything
     stacks instead: copy → sponge → scroll cue → selector. .pinwrap drives the
     first split (the copy sits after .pinwrap__inner in the DOM), .pinwrap__inner
     the second. It stays a real box so the scrub slide-out transform still works. */
  .pinwrap{ display:flex; flex-direction:column; }
  /* The stacked page is ~2400px tall; left at inset:0 the physics box is that tall
     too, so the pile never settles and sponges drift across the copy and the
     selector. Cap it to the first screen — they pour in and land, as on desktop. */
  .playground{ bottom:auto; height:100vh; }
  .hero-copy{
    position:static; order:1; pointer-events:auto;
    padding:96px 20px 0;
  }
  /* the copy now sits above and already clears the nav, so drop the inner's top pad */
  .pinwrap__inner{ order:2; padding-top:0; }
  .hero__stage{ order:1; height:min(52vh,420px); }
  .hero__scroll{ position:static; margin:40px auto 0; }   /* in flow under the copy */
  .panel-stack{ order:3; align-self:auto; }
  .panel{
    position:relative; inset:auto;
    transform:none !important; opacity:1 !important; visibility:visible !important;
    justify-content:flex-start;
  }
  /* The stacked page forces every panel visible, which also dragged the legacy
     How-It-Works panel (Panel C) in under the selector as a half-cut heading.
     Keep it out of flow until the script actually reveals it. */
  .panel--how[aria-hidden="true"]{ display:none; }
  .panel--selector{ margin-top:26px; gap:36px; }
  .sel__title,
  .sel__tag{ white-space:normal; }
  .sel__bottom{ gap:28px; }
  /* rows stack their label above the description below the desktop breakpoint */
  .mess{ min-height:0; flex-direction:column; align-items:flex-start; gap:6px; padding:14px 16px; }
  .mess__label{ width:auto; font-size:17px; }
  .mess__desc{ font-size:14px; }

  /* S01 hero — the Figma frame is a 1920 desktop comp, so below 1024px the copy
     is allowed to wrap instead of forcing a horizontal scrollbar. */
  .hero__text{ gap:48px; }   /* same title ↔ sub gap as desktop */
  .hero__headline,
  .hero__line1,
  .hero__line2,
  .hero__sub{ white-space:normal; }
  .hero__sub{ max-width:32ch; }
  .hero__line2::before{ -webkit-text-stroke-width:.07em; }

  /* Section 4: stack, cup on top */
  /* S03 stacks: scene card first, then the copy/picker/footer under it */
  .scrub{ align-items:flex-start; overflow-y:auto; }
  .scrub__inner{ grid-template-columns:1fr; gap:24px; padding:calc(var(--nav-h) + 12px) 18px 40px; }
  .s3__stage{ order:1; }
  .s3{ order:2; height:auto; gap:24px; }
  .s3__picker{ gap:8px; padding:10px; }
  .s3__block{ flex:1 1 0; max-width:130px; }
  .s3__title{ font-size:clamp(1.6rem, 6vw, 2.2rem); }
  .s3__done{ font-size:32px; }
}
@media (max-width:767px){
  .gnb__inner{ padding:0 18px; }
  .sel__cta{ justify-content:stretch; }
  .pill{ width:100%; }
  .btn{ padding:14px 22px; font-size:.95rem; }
}

/* ---------- Section 5 — How It Works ---------- */
.how{ min-height:100vh; display:flex; align-items:center; padding:calc(var(--nav-h) + 24px) 32px 56px; }
.how__inner{
  width:100%; max-width:1280px; margin:0 auto;
  display:grid; grid-template-columns:1fr 1fr; gap:44px; align-items:center;
}
.how__left{ max-width:520px; }
.how__title{ margin:8px 0 10px; font-weight:800; letter-spacing:-.02em; line-height:1.05; font-size:clamp(1.9rem,3.6vw,2.9rem); }
.how__sub{ margin:0 0 24px; color:var(--muted); font-size:clamp(1rem,1.4vw,1.12rem); }

/* temperature control */
.temp{ margin:0 0 26px; padding:18px; border:1px solid var(--line); border-radius:20px; background:rgba(255,255,255,.55); backdrop-filter:blur(8px); }
.temp__scale{ display:flex; align-items:center; gap:12px; font-size:.85rem; font-weight:600; color:var(--muted); }
.temp__scale input[type=range]{
  flex:1; -webkit-appearance:none; appearance:none; height:8px; border-radius:999px; cursor:pointer;
  background:linear-gradient(90deg,#8FD3FF,#FFE0B0 55%,#FF9A5A);
}
.temp__scale input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none; width:22px; height:22px; border-radius:50%; background:#fff;
  border:3px solid var(--accent); box-shadow:0 2px 8px rgba(0,0,0,.2); cursor:grab;
}
.temp__scale input[type=range]::-moz-range-thumb{ width:22px; height:22px; border-radius:50%; background:#fff; border:3px solid var(--accent); cursor:grab; }
.temp__toggle{ display:inline-flex; margin:14px 0 10px; padding:3px; gap:3px; border-radius:999px; background:var(--bg); border:1px solid var(--line); }
.temp__btn{
  font-family:inherit; font-weight:600; font-size:.9rem; color:var(--muted);
  padding:7px 18px; border:none; border-radius:999px; background:transparent; cursor:pointer; transition:background-color .2s,color .2s;
}
.temp__btn.is-on{ background:var(--ink); color:#fff; }
.temp__mode{ margin:0; font-weight:700; font-size:1.05rem; color:var(--ink); }
.temp__mode::before{ content:"● "; color:var(--accent); }

.how__features{ list-style:none; margin:0 0 20px; padding:0; display:flex; flex-direction:column; gap:8px; }
.how__features li{ font-size:.95rem; color:var(--muted); padding:9px 14px; border-radius:12px; border:1px solid var(--line); background:rgba(255,255,255,.4); transition:border-color .2s, background-color .2s, transform .2s; }
.how__features li b{ color:var(--ink); font-weight:700; }
.how__features li.is-active{ border-color:var(--accent); background:rgba(255,255,255,.8); transform:translateX(3px); }

.how__surfaces{ display:flex; flex-direction:column; align-items:flex-start; gap:9px; }
.how__surfacesNote{ font-size:.84rem; font-weight:700; color:var(--ink); }
.how__surfacesNote::before{ content:"● "; color:var(--accent); }
.how__chips{ display:flex; flex-wrap:wrap; gap:7px; }
.how__chips .chip{ animation:chipIn .32s ease both; }
@keyframes chipIn{ from{ opacity:0; transform:translateY(5px) scale(.96); } to{ opacity:1; transform:none; } }
.how__surfacesLabel{ font-size:.82rem; font-weight:600; color:var(--muted); margin-right:4px; }
.chip{ font-size:.8rem; font-weight:600; color:var(--ink); padding:5px 12px; border-radius:999px; background:var(--bg); border:1px solid var(--line); }

/* sponge stage + tooltips */
.how__stageWrap{ position:relative; width:100%; height:min(72vh,620px); min-height:380px; touch-action:none; }
.how__stage{ position:absolute; inset:0; z-index:1; }
.how__stage canvas{ display:block; width:100%!important; height:100%!important; cursor:grab; }
.how__stage canvas:active{ cursor:grabbing; }
.how__tips{ position:absolute; inset:0; z-index:2; pointer-events:none; }
/* hotspots projected onto the persistent hero sponge (How It Works step) */
.stage-tips{ position:absolute; inset:0; z-index:6; pointer-events:none; opacity:0; transition:opacity .3s ease; }
/* the canvas sits at z:2 — anything in here is drawn behind the sponge and is therefore
   occluded by it, so a prop only shows where it sticks out past the silhouette */
.stage-tips--back{ z-index:1; }
.stage-tips.is-on{ opacity:1; }

/* hotspot: a big invisible hover zone with a small pulsing core dot */
.hotspot{ position:absolute; width:46px; height:46px; padding:0; border:0; background:transparent;
  cursor:pointer; pointer-events:auto; transform:translate(-50%,-50%); }
.hotspot::before{ content:""; position:absolute; left:50%; top:50%; width:16px; height:16px; transform:translate(-50%,-50%);
  border-radius:50%; background:var(--accent); border:2px solid #fff; box-shadow:0 2px 8px rgba(0,0,0,.28); }
.hotspot::after{ content:""; position:absolute; left:50%; top:50%; width:16px; height:16px; margin:-8px 0 0 -8px;
  border-radius:50%; border:2px solid var(--accent); opacity:.6; animation:hotspotPulse 1.8s ease-out infinite; }
@keyframes hotspotPulse{ 0%{ transform:scale(.7); opacity:.6; } 100%{ transform:scale(2.0); opacity:0; } }

/* ---- situation re-enactments projected onto the sponge (eyes → fingers, mouth → spoon) ---- */
.reenact{ position:absolute; z-index:7; pointer-events:none; transform:translate(-50%,-50%);
  opacity:0; transition:opacity .18s ease; }
.reenact.is-on{ opacity:1; }
/* S04's stage square is far bigger than the How-It-Works one and the props are fixed-px,
   so scale them up only there — S05 keeps the sizes it was tuned at. */
/* --prop-scale is set per frame from the stage size (see updateStageHotspots), so the
   props hold their ratio to the sponge instead of being fixed-px. The mouth box is placed
   directly on the MEASURED mouth, so it needs no nudge of its own. */
.stage-tips.for-warm .reenact{ transform:translate(-50%,-50%) scale(var(--prop-scale,1.55)); }
.reenact__svg{ width:100%; height:100%; display:block; overflow:visible;
  filter:drop-shadow(0 6px 10px rgba(0,0,0,.22)); }
.reenact__finger, .reenact__spoon, .reenact__sparkle{ transform-box:fill-box; transform-origin:center; }

/* fingers dip into the eye holes */
.reenact--eyes .reenact__finger{ transform:translateY(-46px); }
.reenact--eyes.is-on .reenact__finger--l{ animation:fingerIn .5s cubic-bezier(.3,1.5,.5,1) forwards; }
.reenact--eyes.is-on .reenact__finger--r{ animation:fingerIn .5s cubic-bezier(.3,1.5,.5,1) .07s forwards; }
@keyframes fingerIn{ 0%{ transform:translateY(-46px); } 62%{ transform:translateY(5px); } 100%{ transform:translateY(0); } }
/* In S04 the eye item plays on its own like the mouth does: the fingers slot in, hold,
   lift out and come back around, instead of dipping once and freezing. */
.stage-tips.for-warm .reenact--eyes.is-on .reenact__finger--l{ animation:fingerLoop 2.4s ease-in-out infinite; }
.stage-tips.for-warm .reenact--eyes.is-on .reenact__finger--r{ animation:fingerLoop 2.4s ease-in-out .07s infinite; }
@keyframes fingerLoop{
  0%   { transform:translateY(-46px); }
  18%  { transform:translateY(5px); }
  26%  { transform:translateY(0); }
  62%  { transform:translateY(0); }
  80%,
  100% { transform:translateY(-46px); }
}

/* The spoon goes into the smile and back out on a loop, travelling along its OWN axis
   (bowl at lower-left, handle upper-right) rather than straight down — the artwork is
   drawn at 38.5deg, so a vertical dip would read as the spoon sliding sideways.
     --bowl  : parks the artwork's bowl centre (22.8% / 76.1%) on the mouth anchor
     --out-* : 130px back up that 38.5deg axis = (0.783, -0.622) * 130 */
.reenact--mouth{ overflow:visible; }
/* the <image> lives inside the masked group; fill-box makes the % offsets resolve
   against the artwork itself, and user units here equal the box's px */
/* Travel is DOWN-LEFT to park (bowl hanging clear of the chin, where the silhouette no
   longer covers it) and UP-RIGHT to feed it in (bowl slides behind the head and the
   sponge hides it). Same 38.5deg axis, just the other way along it from the first pass. */
.reenact__spoon{ transform:translate(27.2%,-26.1%) translate(-18px,14px); }   /* parked, bowl clear of the chin */
.reenact--mouth .reenact__sparkle{ opacity:0; }
.reenact--mouth.is-on .reenact__spoon{ animation:spoonDip 1.8s ease-in-out infinite; }
.reenact--mouth.is-on .reenact__sparkle{ animation:twinkle 1.8s ease-in-out infinite; }
/* out = bowl hanging below the chin (and the handle tip past the crown, as in the key
   art); in = slid up the axis until the sponge has swallowed the bowl. Holds fed-in for
   ~30% of the cycle so the "in" reads before it comes back out. */
@keyframes spoonDip{
  0%   { transform:translate(27.2%,-26.1%) translate(-18px,14px); }
  30%  { transform:translate(27.2%,-26.1%); }
  60%  { transform:translate(27.2%,-26.1%); }
  92%,
  100% { transform:translate(27.2%,-26.1%) translate(-18px,14px); }
}
@keyframes twinkle{ 0%,22%,72%,100%{ opacity:0; transform:scale(.6); } 45%{ opacity:1; transform:scale(1); } }

/* glass tooltip */
.tip{ position:absolute; z-index:3; max-width:190px; padding:10px 13px; border-radius:14px;
  background:rgba(255,255,255,.85); border:1px solid var(--line); box-shadow:0 12px 34px rgba(0,0,0,.16);
  backdrop-filter:blur(10px); font-size:.86rem; color:var(--ink); pointer-events:none;
  opacity:0; transform:translateY(6px) scale(.96); transition:opacity var(--bg-fade) ease, transform var(--bg-fade) ease; }
.tip.is-on{ opacity:1; transform:translateY(0) scale(1); }
.tip b{ display:block; font-weight:700; margin-bottom:2px; }
.tip .tip__sub{ color:var(--muted); font-size:.8rem; }

@media (max-width:1023px){
  .how__inner{ grid-template-columns:1fr; gap:18px; }
  .how__left{ order:2; max-width:none; }
  .how__stageWrap{ order:1; height:min(52vh,420px); }
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  *{ animation-duration:.001ms!important; animation-iteration-count:1!important; transition-duration:.001ms!important; scroll-behavior:auto; }
  .hero__loading{ animation:none; }
}

/* ============================================================
   S04 — "One Sponge. Two feels."  (Figma 23:526 / 23:2464 /
   23:3114 / 23:4410 / 23:5060)
   Same full-screen overlay and 748 / 772 split as S03. All five
   frames are the identical screen; only the page tint changes,
   so that is the one thing driven per mess (--warm-bg).
   ============================================================ */
.warm{
  position:fixed; inset:0; z-index:60;
  /* the inner owns the height, so the margins below are exact; a window too short
     for even the tightest column scrolls rather than clipping the footer */
  display:flex; align-items:flex-start; overflow-y:auto;
  background:var(--warm-bg, var(--sd-gray-50));
  /* slide / fade: the shared step transition further down the file */
}
.warm::before{
  content:""; position:absolute; inset:0; pointer-events:none;
  background-image:
    linear-gradient(to right, var(--sd-grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--sd-grid-line) 1px, transparent 1px);
  background-size:var(--sd-grid-cell) var(--sd-grid-cell);
}
.warm__inner{ position:relative; }
/* Figma 1920x1080: both columns run y 120 -> 1000 — 40px clear of the 80px GNB
   on top, 80px to the bottom edge, 880 tall between them. A shorter window has
   to take that difference out of the columns, never out of these two margins:
   centring the block instead (what this used to do) let it grow past both edges,
   which slid the "See How It Works" tag under the GNB and clipped the footer. */
.warm__inner{
  --w4-avail:calc(100vh - var(--nav-h) - 120px);   /* 880 at the comp's 1080 */
  min-height:100%; width:100%; max-width:1680px; margin:0 auto;
  padding:calc(var(--nav-h) + 40px) 32px 80px;
  display:grid; grid-template-columns:minmax(0,748px) minmax(0,772px);
  justify-content:space-between; gap:40px; align-items:stretch;
}
/* the column is now exactly as tall as the comp's 880 box, so space-between is what
   opens the gap above the footer (134px in the comp at 1080) — `gap` is only the
   floor that keeps the two apart once the window gets short */
.w4{ min-height:0; display:flex; flex-direction:column; justify-content:space-between; gap:16px; }
.w4__top{ display:flex; flex-direction:column; gap:clamp(24px,7vh,80px); }   /* 80 in the comp, tightened on short screens */
.w4__text{ display:flex; flex-direction:column; align-items:flex-start; gap:12px; }   /* title -> sub is 12 (23:3063) */
.w4__text .s3__tag{ margin-bottom:4px; }   /* ...but tag -> title stays 16 (23:3064) */
.w4__content{ display:flex; flex-direction:column; gap:24px; }

/* "Perfect for" + the tag row, which sits hard right on the 748 column */
.w4__perfect{ display:flex; align-items:center; justify-content:space-between; gap:24px; }
.w4__perfectLabel{
  margin:0; font-weight:600; font-size:20px; line-height:1.4;
  letter-spacing:-.02em; color:#404040;
}
.w4__chips{ margin:0; padding:0; list-style:none; display:flex; flex-wrap:wrap; gap:8px; }
.w4__chips li{
  padding:4px 8px; border-radius:6px;
  /* per-mess fill / stroke / text from Figma 4-1 … 4-5 (WARM_CHIPS in script.js sets the vars on .warm) */
  background:var(--w4-chip-bg, #F5DED9); border:1px solid var(--w4-chip-line, #EDB5A9);
  color:var(--w4-chip-ink, #BD2000); font-weight:400; font-size:14px; line-height:1.2; letter-spacing:-.02em;
}

/* accordion — the surfaces are the page tint darkened, so one rule fits all five tints */
.w4__acc{ display:flex; flex-direction:column; gap:16px; }
.w4__item{
  border-radius:12px; background:var(--w4-card, rgba(0,0,0,.04));
  border:1px solid var(--sd-gray-200); overflow:hidden;
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
  /* the comp pads the ITEM (16 all round) and stacks heading + body with a 4px gap;
     the temperature item opens that gap to 12 (Figma 23:3078 / 23:3093 / 23:3100) */
  padding:16px; display:flex; flex-direction:column; gap:4px;
}
.w4__item[data-item="temp"]{ gap:12px; }
/* The items are dealt out one at a time (script.js: advanceWarmFlow). display:flex above
   would beat the UA's [hidden] rule, so it is restated; a dealt item eases in. */
.w4__item[hidden]{ display:none; }
.w4__item.is-in{ animation:w4ItemIn .7s ease-out both; }
@keyframes w4ItemIn{ from{ opacity:0; transform:translateY(12px); } to{ opacity:1; transform:none; } }
.w4__itemHead{ margin:0; }
.w4__itemBtn{
  width:100%; padding:0; border:0; background:none; cursor:pointer;
  display:flex; align-items:center; gap:12px; text-align:left;
  font-family:inherit; font-weight:600; font-size:18px; line-height:1.4;
  letter-spacing:-.02em; color:#404040;   /* gray/700 in BOTH states — the heading never dims */
}
/* 24px disc + the exported tick: yellow/500 with a white tick when this item holds the
   selection, black-20% with a gray/200 tick when it does not (Figma 23:446 / 23:448) */
.warm__mark{
  flex:none; width:24px; height:24px; border-radius:999px;
  background:rgba(5,5,5,.2) url("./assets/S04/icon/check-off.svg") center/24px 24px no-repeat;
  transition:background-color .2s;
}
.w4__item.is-sel .warm__mark{
  background-color:var(--sd-yellow-400);        /* #FECF00 */
  background-image:url("./assets/S04/icon/check-on.svg");
}
/* Every panel stays up whether or not its item is selected — the comps never collapse
   one, they grey it out. Temperature: 16+28 | 12 | 54+16+92+4 | 16 = 240 with the border.
   Eye / mouth: 16+28 | 4 | 20+4 | 16 = 90 (Figma 23:3093 / 23:3100). */
.w4__panel{ padding:0; }
.w4__item[data-item="temp"] .w4__panel{ display:flex; flex-direction:column; gap:16px; padding-bottom:4px; }
/* M/14 gray/600, indented past the 24px mark + 12px gap (23:3098: pl 36, pb 4) */
.w4__note{ margin:0 0 4px 36px; font-size:14px; font-weight:500; line-height:1.4; letter-spacing:-.02em; color:#525252; }

/* warm / cold segmented control (714 x 54, split in half) */
.w4__switch{
  display:grid; grid-template-columns:1fr 1fr;
  background:rgba(255,255,255,.15); border-radius:12px; overflow:hidden;
}
.w4__tempBtn{
  display:flex; align-items:center; justify-content:center; gap:8px;
  padding:16px 12px; border:0; background:none; cursor:pointer;
  font-family:inherit; font-weight:600; font-size:16px; line-height:1.4;
  letter-spacing:-.02em; color:#A3A3A3;   /* gray/400 */
  border-radius:12px; transition:background-color .25s, color .25s;
}
.w4__tempBtn.is-on{ background:#FF992C; color:#fff; }   /* red/200, flat */
/* cold half of the switch is blue/200 (Figma 23:1794) */
.warm.is-cold .w4__tempBtn.is-on{ background:#4F9CEE; }
/* one exported glyph for both halves — the comp only recolours it (white on, gray/400 off) */
.w4__tempIcon{
  flex:none; width:20px; height:20px;
  background:url("./assets/S04/icon/switch-off.svg") center/contain no-repeat;
}
.w4__tempBtn.is-on .w4__tempIcon{ background-image:url("./assets/S04/icon/switch-on.svg"); }

/* The comp tints this panel with grey 40% on COLOR-BURN, which is what makes it read
   darker than the card it sits in — a plain black wash lands on the wrong colour. */
.w4__desc{ position:relative; padding:20px 16px; border-radius:12px; }   /* the panel gap gives the 16 above */
.w4__desc::before{
  content:""; position:absolute; inset:0; border-radius:12px; pointer-events:none;
  background:rgba(163,163,163,.4); mix-blend-mode:color-burn;
}
.w4__desc > *{ position:relative; }
.w4__descTitle{
  margin:0; display:flex; align-items:center; gap:4px;
  font-weight:600; font-size:16px; line-height:1.4; letter-spacing:-.02em; color:#262626;
}
/* Style Icon — fire (15.048 x 17.067 centred in 24) in warm, snowflake (24 x 24) in cold */
.w4__descIcon{
  flex:none; width:24px; height:24px;
  background:url("./assets/S04/icon/style-fire.svg") center/15.048px 17.067px no-repeat;
}
.warm.is-cold .w4__descIcon{
  background-image:url("./assets/S04/icon/style-cold.svg"); background-size:24px 24px;
}
.w4__descBody{ margin:8px 0 0 8px; font-size:14px; font-weight:500; line-height:1.4; letter-spacing:-.02em; color:#525252; }

/* ---- temperature item not selected (Figma 23:1818) ----
   The panel stays put; the switch drops back to two Off halves and every word in the
   description goes gray/400. The heading, the card and the burn-tinted description
   plate are untouched — only the copy and the active fill change. */
.w4__item[data-item="temp"]:not(.is-sel) .w4__tempBtn.is-on,
.warm.is-cold .w4__item[data-item="temp"]:not(.is-sel) .w4__tempBtn.is-on{
  background:none; color:#A3A3A3;
}
.w4__item[data-item="temp"]:not(.is-sel) .w4__tempBtn.is-on .w4__tempIcon{
  background-image:url("./assets/S04/icon/switch-off.svg");
}
.w4__item[data-item="temp"]:not(.is-sel) .w4__descTitle,
.w4__item[data-item="temp"]:not(.is-sel) .w4__descBody{ color:#A3A3A3; }

/* right: the 772 square that the persistent sponge is moved into */
.w4__stage{
  /* Sized off its WIDTH, never off a percentage height: a % height inside an
     auto-sized grid row resolves to auto, which collapsed the square (and the
     canvas in it) to nothing the moment the content outgrew the window. */
  position:relative; width:max(280px, min(100%, 772px, var(--w4-avail))); aspect-ratio:1; height:auto;
  /* the comp bottom-aligns the square in its 880 column (23:1814 is justify-end),
     so it shares the footer's 80px bottom margin and hangs 108px below the text */
  justify-self:end; align-self:end; border-radius:12px; overflow:hidden;
  border:1px solid var(--sd-gray-200);   /* gray/200 frame, like the S03 picker (23:3111) */
  background:#F6E0D3;                    /* the warm card's ground, in case a layer is late */
}
/* ---- S04 utensil layers: back → utensil → front, stacked over the parked 3D sponge ----
   The renders were made at the stage's own pose and framing, so the 2D stack lands
   exactly on top of the 3D sponge and the swap is invisible; the canvas is hidden
   underneath while the stack is up. Whichever utensil is up is parked in the pose the
   designer drew and slides out along its own axis by --ox/--oy (px, set per frame from
   the stage size — script.js picks the axis to match the utensil). */
.w4layers{ position:absolute; inset:0; z-index:9; pointer-events:none; opacity:0; visibility:hidden;
  /* Sized so the utensil's far end (the designer parks it past the render's edge on the
     Original/Mommy/Baby scrubs) stays inside the card instead of being clipped by it.
     Keep in step with WARM_FILL_BOOST in script.js — the 3D sponge under this stack
     is framed by that, and the two must match or the swap to the stack shows a jump.
     (.97 matched the 3D sponge at boost 1.22; this is that ratio scaled by ~.86) */
  --w4-layers-scale:.83;
  transform:scale(var(--w4-layers-scale)); transform-origin:50% 50%;
  transition:opacity .22s ease, visibility 0s linear .22s; }
.w4layers.is-on{ opacity:1; visibility:visible; transition:opacity .22s ease; }
.w4layers__back, .w4layers__front, .w4layers__tint, .w4layers__sticks{
  position:absolute; left:0; top:0; width:100%; height:100%; display:block; }
/* The spoon (ten of the thirteen scrubs): one image dropped into the box the designer
   parked it in, --sx/--sy/--sw/--sh as fractions of the stage. */
.w4layers__spoon{
  position:absolute; display:block;
  left:calc(var(--sx, .3) * 100%); top:calc(var(--sy, .3) * 100%);
  width:calc(var(--sw, .5) * 100%); height:calc(var(--sh, .4) * 100%);
  transform:translate(var(--ox, -80px), var(--oy, 64px));
}
.w4layers__spoon[hidden]{ display:none; }
.w4layers.is-on .w4layers__spoon{ animation:w4spoon 1.8s ease-in-out infinite; }
/* out = pulled down-left along the handle so the bowl hangs clear of the chin;
   in = the Figma pose (bowl in the smile). Holds fed-in ~30% of the cycle. */
@keyframes w4spoon{
  0%        { transform:translate(var(--ox, -80px), var(--oy, 64px)); }
  30%, 60%  { transform:none; }
  92%, 100% { transform:translate(var(--ox, -80px), var(--oy, 64px)); }
}
/* The chopsticks (jessie / piglet / tigger — Figma 66:1100 / 66:1127 / 66:1169).
   One .w4layers__stick per chopstick, laid out in the 2048 render's own coordinates:
   --cx/--cy are the box's left/top as a fraction of it, and the box is always the
   same 111.979 x 1640.313 at 47.32° that the designer used (Figma 66:1109). The <img>
   inset repeats Figma's stroke bleed so the artwork lands where the vector does. */
.w4layers__stick{
  position:absolute;
  left:calc(var(--cx, .5) * 100%); top:calc(var(--cy, .23) * 100%);
  width:5.46772%;    /* 111.979 / 2048 */
  height:80.09341%;  /* 1640.313 / 2048 */
  transform:translate(var(--ox, -80px), var(--oy, 64px)) rotate(47.32deg);
}
.w4layers__stick img{
  position:absolute; display:block;
  left:-12.35%; top:-.72%; width:124.7%; height:101.57%;
}
.w4layers.is-on .w4layers__stick{ animation:w4stick 1.8s ease-in-out infinite; }
/* out = pulled down-left along the stick so the tip hangs clear of the chin;
   in = the Figma pose (tip in the smile). Holds fed-in ~30% of the cycle. */
@keyframes w4stick{
  0%        { transform:translate(var(--ox, -80px), var(--oy, 64px)) rotate(47.32deg); }
  30%, 60%  { transform:rotate(47.32deg); }
  92%, 100% { transform:translate(var(--ox, -80px), var(--oy, 64px)) rotate(47.32deg); }
}
/* the second stick trails the first a touch, so the pair reads as one hand, not one object */
.w4layers.is-on .w4layers__stick + .w4layers__stick{ animation-delay:.09s; }
/* the temperature wash, masked to the sponge pixels of each layer so the utensil stays clean */
.w4layers__tint{ background:var(--w4-tint, transparent);
  -webkit-mask:var(--mask) center / 100% 100% no-repeat; mask:var(--mask) center / 100% 100% no-repeat; }
.w4__stage.has-layers #stage canvas{ visibility:hidden; }
@media (prefers-reduced-motion:reduce){
  .w4layers.is-on .w4layers__spoon{ animation:none; transform:none; }
  .w4layers.is-on .w4layers__stick{ animation:none; transform:rotate(47.32deg); }
}

/* The card is a field of flames for the warm half of the switch and a field of
   snowflakes for the cold half (Figma 23:1815). Both live as full-size layers
   behind the sponge and CROSS-FADE — swapping the background-image cut hard.
   The incoming field also settles from a hair larger, so it reads as arriving. */
.w4__stage::before, .w4__stage::after{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  background:center/cover no-repeat;
  transition:opacity .7s ease, transform .9s cubic-bezier(.22,1,.36,1);
}
.w4__stage::before{ background-image:url("./assets/S04/stage/warm-bg.png"); opacity:1; transform:none; }
.w4__stage::after { background-image:url("./assets/S04/stage/cold-bg.png"); opacity:0; transform:scale(1.04); }
.warm.is-cold .w4__stage::before{ opacity:0; transform:scale(1.04); }
.warm.is-cold .w4__stage::after { opacity:1; transform:none; }
@media (prefers-reduced-motion:reduce){
  .w4__stage::before, .w4__stage::after{ transition:none; transform:none; }
}
/* min-height:0 undoes .hero__stage's 380px floor: inside this square the box is
   sized by the insets, and that floor made it 336x380 (squashed sponge, 2px of
   overflow) as soon as the window got short enough — i.e. with devtools docked. */
.warm .w4__stage #stage{ position:absolute; inset:10%; z-index:1; width:auto; height:auto; min-height:0; opacity:1; visibility:visible; }   /* above the two field layers */

@media (max-width:1023px){
  .warm__inner{ height:auto; grid-template-columns:1fr; gap:24px; padding:calc(var(--nav-h) + 12px) 18px 40px; align-items:start; }
  .warm{ align-items:flex-start; overflow-y:auto; }
  .w4{ height:auto; gap:24px; }
  .w4__top{ gap:32px; }
  .w4__stage{ order:-1; justify-self:stretch; height:auto; width:100%; max-width:772px; }
  .w4__perfect{ flex-direction:column; align-items:flex-start; gap:12px; }
}


/* ============================================================
   S05 — "Meet Your Match"  (Figma 23:7701)
   Same overlay and 748 / 772 split as S04. The page tint and the
   card colour follow the mess exactly as S04 does (--match-bg,
   --m5-card = the S03 picker surface).
   ============================================================ */
.match{
  position:fixed; inset:0; z-index:70;
  display:flex; align-items:flex-start; overflow-y:auto;
  background:var(--match-bg, var(--sd-gray-50));
  /* slide / fade: the shared step transition further down the file */
}
.match::before{
  content:""; position:absolute; inset:0; pointer-events:none;
  background-image:
    linear-gradient(to right, var(--sd-grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--sd-grid-line) 1px, transparent 1px);
  background-size:var(--sd-grid-cell) var(--sd-grid-cell);
}
.match__inner{
  --w4-avail:calc(100vh - var(--nav-h) - 120px);   /* 880 at the comp's 1080 */
  position:relative; min-height:100%; width:100%; max-width:1680px; margin:0 auto;
  padding:calc(var(--nav-h) + 40px) 32px 80px;
  display:grid; grid-template-columns:minmax(0,748px) minmax(0,772px);
  justify-content:space-between; gap:40px; align-items:stretch;
}
.m5{ min-height:0; display:flex; flex-direction:column; justify-content:space-between; gap:16px; }
.m5__top{ display:flex; flex-direction:column; gap:clamp(24px,7vh,80px); }   /* 80 in the comp */
.m5__text{ display:flex; flex-direction:column; align-items:flex-start; gap:12px; }   /* title -> sub 12 */
.m5__text .s3__tag{ margin-bottom:4px; }                                             /* tag -> title 16 */
.m5__contents{ display:flex; flex-direction:column; gap:20px; }

/* "Recommended for you..." — yellow/500 disc with the exported 18px glyph */
.m5__reco{
  margin:0; display:flex; align-items:center; gap:12px;
  font-weight:600; font-size:20px; line-height:1.4; letter-spacing:-.02em; color:#404040;
}
.m5__recoMark{
  flex:none; width:24px; height:24px; border-radius:999px;
  background:var(--sd-yellow-400) url("./assets/S05/icon/reco.svg") center/18px 18px no-repeat;
}

/* info card: 8px frame, six 52-tall rows on a 3-column grid, hairlines inset 12px */
.m5__table{
  margin:0; padding:8px; border-radius:12px;
  background:var(--m5-card, #F1EADE); border:1px solid var(--sd-gray-200);
  display:flex; flex-direction:column;
}
.m5__row{
  position:relative; display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:8px;
  padding:16px; font-size:14px; line-height:1.4; letter-spacing:-.02em;
}
.m5__row + .m5__row::before{
  content:""; position:absolute; left:12px; right:12px; top:-1px; height:1px;
  background:rgba(0,0,0,.05);
}
.m5__row dt{ margin:0; font-weight:500; color:#737373; }   /* gray/500 */
.m5__row dd{ margin:0; font-weight:600; color:#262626; grid-column:2 / span 2; }   /* gray/800; the comp's third column is empty, so long values may run into it */
.m5__compare{ display:flex; justify-content:flex-end; }

/* right: 772 card — 16 / 16 / 8 padding, switch then the 654 illustration */
.m5__stage{
  position:relative; box-sizing:border-box;
  width:max(280px, min(100%, 772px, var(--w4-avail))); aspect-ratio:1; height:auto;
  justify-self:end; align-self:end;
  padding:16px 16px 8px; border-radius:12px; overflow:hidden;
  border:1px solid var(--sd-gray-200); background:var(--m5-card, #F1EADE);
  display:flex; flex-direction:column; align-items:center; gap:40px;
}
.m5__switch{
  position:relative; flex:none; width:100%; display:grid; grid-template-columns:repeat(3,1fr);
  background:rgba(255,255,255,.15); border-radius:12px; overflow:hidden;
}
/* the yellow/500 pill is ONE element that slides to the active third (--ed-i = 0/1/2) */
.m5__switch::before{
  content:""; position:absolute; top:0; left:0; width:calc(100% / 3); height:100%;
  border-radius:12px; background:var(--sd-yellow-400);
  transform:translateX(calc(var(--ed-i, 0) * 100%));
  transition:transform .4s cubic-bezier(.22,1,.36,1);
}
.m5__switch.no-anim::before{ transition:none; }   /* entering the step: no slide from stale spot */
.m5__edBtn{
  position:relative; z-index:1;
  padding:16px 12px; border:0; background:none; border-radius:12px; cursor:pointer;
  font-family:inherit; font-weight:600; font-size:16px; line-height:1.4;
  letter-spacing:-.02em; color:#A3A3A3;   /* gray/400 off */
  transition:color .25s;
}
.m5__edBtn.is-on{ color:#262626; }   /* gray/800 on the pill */
/* the illustration box is a 654 square in the comp; everything inside is placed in
   percentages of it so the whole thing scales with the card on shorter windows */
.m5__illust{ position:relative; flex:1 1 0; min-height:0; aspect-ratio:1; max-width:100%; perspective:1200px; }
/* hover tilt: the wrapper rotates up to ±7° toward the pointer, the scrub sits 40px
   in front of the pack so it parallaxes, and a soft highlight tracks the pointer */
.m5__tilt{
  position:absolute; inset:0; transform-style:preserve-3d;
  transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));
  transition:transform .6s cubic-bezier(.22,1,.36,1);
}
.m5__illust.is-tilting .m5__tilt{ transition:transform .12s ease-out; }
.m5__glare{
  position:absolute; left:14.22%; top:6.116%; width:71.428%; aspect-ratio:467.139 / 574.501;
  border-radius:25px; pointer-events:none; opacity:0; transition:opacity .4s ease;
  background:radial-gradient(circle at var(--gx,50%) var(--gy,35%), rgba(255,255,255,.32), rgba(255,255,255,0) 55%);
  mix-blend-mode:screen; transform:translateZ(1px);
}
.m5__illust.is-tilting .m5__glare{ opacity:1; }
.m5__sponge{ transform:translateZ(40px); }
@media (prefers-reduced-motion:reduce){
  .m5__tilt{ transform:none !important; transition:none; }
  .m5__glare{ display:none; }
}
.m5__pack{
  position:absolute; left:14.22%; top:6.116%; width:71.428%; height:auto; display:block;   /* 467 x 574.5 @ 93,40 */
}
.m5__sponge{
  position:absolute; left:17.737%; top:25.994%; width:64.526%; height:auto; display:block;  /* 422 square @ 116,170 */
}
/* the 3D blister pack (script.js match3d): the WebGL canvas fills the illustration box,
   OrbitControls turn it on drag, so the box must own its pointer gestures.
   .is-3d on the illustration box picks this over the key art (Stuck-on Food only). */
.m5__3d{ position:absolute; inset:0; }
.m5__3d canvas{ display:block; width:100%; height:100%; cursor:grab; touch-action:none; }
.m5__3d canvas:active{ cursor:grabbing; }
.m5__illust:not(.is-3d) .m5__3d, .m5__illust:not(.is-3d) .m5__hint{ display:none; }
.m5__illust.is-3d .m5__tilt{ display:none; }
.m5__hint{
  position:absolute; left:50%; bottom:6px; transform:translateX(-50%); margin:0;
  padding:6px 12px; border-radius:999px; background:rgba(255,255,255,.7);
  font-size:12px; font-weight:500; letter-spacing:-.02em; color:#6B6760;
  pointer-events:none; white-space:nowrap; opacity:.9;
}

@media (max-width:1023px){
  .match__inner{ height:auto; grid-template-columns:1fr; gap:24px; padding:calc(var(--nav-h) + 12px) 18px 40px; align-items:start; }
  .match{ align-items:flex-start; overflow-y:auto; }
  .m5{ height:auto; gap:24px; }
  .m5__top{ gap:32px; }
  .m5__stage{ order:-1; justify-self:stretch; height:auto; width:100%; max-width:772px; }
}

/* ---- S05 "Compare Another Scrubbers" modal (Figma 23:8374) ----
   Lives inside .match (whose transform makes it the containing block, and it
   is the full viewport anyway). White 40% + 6px blur dim, 1080 popup centred. */
.cmp{
  position:fixed; inset:0; z-index:5;
  display:flex; align-items:center; justify-content:center; padding:24px;
}
.cmp[hidden]{ display:none; }
.cmp__dim{
  position:absolute; inset:0; background:rgba(255,255,255,.4);
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
}
.cmp__popup{
  position:relative; width:min(1080px, 100%); max-height:100%; overflow:auto;
  background:#fff; border:1px solid var(--sd-gray-200); border-radius:20px;
  filter:drop-shadow(0 4px 5px rgba(0,0,0,.05));
}
.cmp__head{ display:flex; align-items:center; justify-content:space-between; padding:24px 24px 4px; }
.cmp__title{
  margin:0; font-weight:600; font-size:20px; line-height:1.4; letter-spacing:-.02em; color:#0A0A0A;   /* gray/950 */
}
.cmp__close{
  width:40px; height:40px; padding:10px; border:0; border-radius:999px; background:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center; transition:background-color .2s;
}
.cmp__close img{ width:28px; height:28px; display:block; }
.cmp__close:hover{ background:rgba(0,0,0,.05); }
.cmp__body{ padding:24px; }

/* four equal columns; yellow/300 header, gray/50 label column, gray/200 hairlines */
.cmp__table{
  width:100%; table-layout:fixed; border-collapse:separate; border-spacing:0;
  border:1px solid var(--sd-gray-200); border-radius:10px; overflow:hidden;
  font-size:14px; line-height:1.4; letter-spacing:-.02em; color:#262626; text-align:center;
}
.cmp__table th, .cmp__table td{
  padding:20px 16px; font-weight:500; vertical-align:middle;
  border-top:1px solid var(--sd-gray-200); border-right:1px solid var(--sd-gray-200);
}
.cmp__table th{ font-weight:600; }
.cmp__table tr > :last-child{ border-right:0; }
.cmp__table thead th{ padding:16px; background:var(--sd-yellow-300); border-top:0; border-right-color:#fff; }
.cmp__table tbody th{ background:var(--sd-gray-50); }
/* product row: 159 tall, box shots on #F2F1EF, the two Baby boxes split the cell */
.cmp__product th, .cmp__product td{ height:159px; padding:0; }
.cmp__product td{ background:#F2F1EF; }
.cmp__pic{ display:block; position:relative; width:98.805px; height:138.425px; margin:0 auto; overflow:hidden; }
.cmp__pic img{ position:absolute; left:-22.17%; top:-26.57%; width:144.33%; height:144.33%; max-width:none; }
.cmp__pic--mommy img{ left:-21.57%; top:-24.7%; width:143.14%; height:143.14%; }
.cmp__product td.cmp__pair{ background:none; }
.cmp__half{ display:inline-block; vertical-align:top; position:relative; width:50%; height:159px; overflow:hidden; }
.cmp__half img{ position:absolute; left:-5.9%; top:-17.02%; width:111.79%; height:126.21%; max-width:none; }
.cmp__half--boy img{ left:-8.86%; top:-20.47%; width:117.72%; height:132.9%; }
/* entrance: the dim fades in while the popup rises from 48px below; .is-open is
   added one frame after un-hiding so the transition actually runs, and removed
   before hiding so the exit plays in reverse */
.cmp__dim{ opacity:0; transition:opacity .3s ease; }
.cmp__popup{
  opacity:0; transform:translateY(48px);
  transition:opacity .3s ease, transform .45s cubic-bezier(.22,1,.36,1);
}
.cmp.is-open .cmp__dim{ opacity:1; }
.cmp.is-open .cmp__popup{ opacity:1; transform:none; }
@media (prefers-reduced-motion:reduce){
  .cmp__dim, .cmp__popup{ transition:none; }
}

@media (max-width:1023px){
  .cmp{ padding:12px; }
  .cmp__body{ padding:12px; overflow-x:auto; }
  .cmp__table{ min-width:720px; }
}

/* ---- S04 -> S05 entrance (Figma has no motion spec; this is the house style) ----
   The section itself slides in from the right (the .match transition). While it
   does, the left column builds top-down — text, "Recommended", the six rows one
   after another, the compare link, the footer — and the right card rises from
   40px below. Re-entering replays it because the class toggles. */
@keyframes m5-rise{ from{ opacity:0; transform:translateY(18px); } to{ opacity:1; transform:none; } }
@keyframes m5-rise-card{ from{ opacity:0; transform:translateY(40px); } to{ opacity:1; transform:none; } }
.match.is-active .m5__text,
.match.is-active .m5__reco,
.match.is-active .m5__row,
.match.is-active .m5__compare,
.match.is-active .m5 > .s3__foot{
  animation:m5-rise .55s cubic-bezier(.22,1,.36,1) both;
}
.match.is-active .m5__text{ animation-delay:.10s; }
.match.is-active .m5__reco{ animation-delay:.22s; }
.match.is-active .m5__row:nth-child(1){ animation-delay:.28s; }
.match.is-active .m5__row:nth-child(2){ animation-delay:.33s; }
.match.is-active .m5__row:nth-child(3){ animation-delay:.38s; }
.match.is-active .m5__row:nth-child(4){ animation-delay:.43s; }
.match.is-active .m5__row:nth-child(5){ animation-delay:.48s; }
.match.is-active .m5__row:nth-child(6){ animation-delay:.53s; }
.match.is-active .m5__compare{ animation-delay:.62s; }
.match.is-active .m5 > .s3__foot{ animation-delay:.66s; }
.match.is-active .m5__stage{ animation:m5-rise-card .7s cubic-bezier(.22,1,.36,1) .25s both; }
@media (prefers-reduced-motion:reduce){
  .match.is-active .m5__text, .match.is-active .m5__reco, .match.is-active .m5__row,
  .match.is-active .m5__compare, .match.is-active .m5 > .s3__foot, .match.is-active .m5__stage{ animation:none; }
  .m5__switch::before{ transition:none; }
}

/* ============================================================
   Step transition — S02 ⇄ S03 ⇄ S04 ⇄ S05
   ------------------------------------------------------------
   The steps used to fly the full width of the screen past each other, which read as
   "the whole page flips over" on every button. They now travel 8% and cross-fade, on
   one duration and one ease, so a step reads as the content changing rather than the
   screen leaving.

   Three states, and they make both directions fall out for free:

     rest    +8%, transparent   — not visited yet, waiting off to the right
     .is-active     0%, opaque  — the step you are on
     .is-under      -8%, transparent  — visited, parked just off to the left

   Forward: the new panel gets .is-active (comes in from +8%) while the old one gets
   .is-under (drifts out to -8%). Back: the current panel simply loses .is-active (so it
   returns to its resting +8%) while the previous one loses .is-under (returns from -8%).
   Nothing has to be animated by hand in either direction, which is what made the S05 →
   S04 return stumble: two GSAP tweens, a fade and a dim, all on their own clocks.

   Every panel carries the same mess tint as the page behind it, so the half-way point of
   the cross-fade is two contents over one continuous ground, not two backgrounds fighting.
   visibility is switched with a 0s transition delayed by the duration: it flips on
   instantly on the way in, and only after the fade on the way out, so a resting panel
   never sits invisibly over the live one catching hovers. */
.scrub,
.warm,
.match{
  transform:translateX(var(--step-shift)); opacity:0; visibility:hidden; pointer-events:none;
  transition:transform var(--step-dur) var(--step-ease),
             opacity   var(--step-dur) var(--step-ease),
             visibility 0s linear var(--step-dur);
}
.scrub.is-active,
.warm.is-active,
.match.is-active{
  transform:none; opacity:1; visibility:visible; pointer-events:auto;
  transition:transform var(--step-dur) var(--step-ease),
             opacity   var(--step-dur) var(--step-ease),
             visibility 0s;
}
/* declared last so it wins over .is-active, which the panel underneath keeps */
.scrub.is-under,
.warm.is-under,
.match.is-under{
  transform:translateX(calc(-1 * var(--step-shift))); opacity:0; visibility:hidden; pointer-events:none;
  transition:transform var(--step-dur) var(--step-ease),
             opacity   var(--step-dur) var(--step-ease),
             visibility 0s linear var(--step-dur);
}
@media (prefers-reduced-motion:reduce){
  /* no travel at all — just the cross-fade, quickly */
  .scrub, .warm, .match,
  .scrub.is-active, .warm.is-active, .match.is-active,
  .scrub.is-under, .warm.is-under, .match.is-under,
  body.scrubbing .pinwrap__inner{ transform:none; }
  .scrub, .warm, .match{ transition:opacity .2s linear, visibility 0s linear .2s; }
  .scrub.is-active, .warm.is-active, .match.is-active{ transition:opacity .2s linear, visibility 0s; }
  .scrub.is-under, .warm.is-under, .match.is-under{ transition:opacity .2s linear, visibility 0s linear .2s; }
}

/* ============================================================
   Short desktop windows (S03 / S04 / S05)
   ------------------------------------------------------------
   The three step screens are drawn from 1920x1080 comps: a 748 + 772 split inside a
   column that runs 40px under the 80px GNB to 80px off the bottom edge. On a laptop
   that 880-tall box does not exist, and three separate things went wrong:

   • S03's card was capped by WIDTH only, so the 772 square ran under the GNB and past
     the bottom edge (it needs a 904-tall window). It is height-capped now, in the base
     rule, with the same term S04/S05 use.
   • S04 hands out its three items one at a time, so the column GROWS as you press
     through them — the third one tipped the overlay into a scrollbar mid-flow, and the
     15px it takes shrank the stage (668 -> 661) as it appeared.
   • S05's left column (six-row table + reco + footer) is the tallest in the flow and
     pushed its card and footer past the bottom edge.

   The fix ramps in two steps. First the page margins: below 1000px tall they are worth
   more as column height than as breathing room, and handing them back grows --w4-avail
   by the same amount, so the stage square gets BIGGER instead of the overlay scrolling.
   Then the stacks themselves — paddings and gaps, never the type — for the shortest
   windows. Above 1000px tall none of this applies and the comps are untouched. */
@media (min-width:1024px) and (max-height:1000px){
  .warm__inner,
  .match__inner{
    --w4-avail:calc(100vh - var(--nav-h) - 56px);
    padding:calc(var(--nav-h) + 16px) 32px 40px;
  }
  .w4__top{ gap:clamp(20px, 4.6vh, 80px); }
  .w4__content{ gap:20px; }
  .w4__acc{ gap:12px; }
  .m5__top{ gap:clamp(16px, 3vh, 80px); }
  .m5__contents{ gap:12px; }
  .m5__row{ padding:10px 16px; }
  .m5__reco{ font-size:18px; }
}
@media (min-width:1024px) and (max-height:900px){
  .warm__inner,
  .match__inner{
    --w4-avail:calc(100vh - var(--nav-h) - 40px);
    padding:calc(var(--nav-h) + 12px) 32px 28px;
  }
  .w4{ gap:12px; }
  .w4__top{ gap:clamp(16px, 3.4vh, 80px); }
  .w4__acc{ gap:10px; }
  .w4__perfect{ gap:16px; }
  .w4__item{ padding:12px; }
  .w4__desc{ padding:14px 16px; }
  .m5{ gap:12px; }
  .m5__contents{ gap:10px; }
  .m5__row{ padding:8px 16px; }
}
@media (min-width:1024px) and (max-height:820px){
  .w4__item{ padding:10px 12px; }
  .w4__desc{ padding:10px 14px; }
  .w4__acc{ gap:8px; }
  .m5__row{ padding:6px 16px; }
  .m5__compare{ font-size:14px; }
}
/* 1280x720-class windows: the tightest desktop we support. S04 is the one that still
   does not fit once all three items are out, so its stack gives up the last of its
   padding (type sizes stay untouched), and S02's CTA gets a hair more room. */
@media (min-width:1024px) and (max-height:760px){
  .warm__inner,
  .match__inner{
    --w4-avail:calc(100vh - var(--nav-h) - 32px);
    padding:calc(var(--nav-h) + 12px) 32px 20px;
  }
  .w4{ gap:8px; }
  .w4__text{ gap:8px; }
  .w4__acc{ gap:6px; }
  .w4__item{ padding:8px 12px; }
  .w4__desc{ padding:8px 12px; }
  /* S02's five rows plus the CTA are a couple of px over at 720 — the rows give it
     back. Below 1440 they already stack their label over the description, so it is
     their padding that sets the height, not min-height. */
  .panel--selector{ --sel-gap-md:clamp(12px, 3.4vh, 48px); }
  .mess{ min-height:clamp(44px, 6.4vh, 72px); padding:8px 20px; }
}
