/* ═══════════════════════════════════════════════════════════════════════
   THE LINEN STANDARD — premium front-end prototype
   "We are not selling laundry. We are selling freedom from laundry."

   CONTRAST, computed from the brief's palette before a line was written.
   These decide which colour may ever carry type:
       deep teal on ivory   10.76      white on deep teal    11.40
       taupe on ivory        5.85      powder on deep teal    9.68
       linen blue on ivory   2.99  ✗   deep teal on blue      3.59  (large only)
       coral on ivory        2.69  ✗   deep teal on coral     3.99  (large only)
                                       deep teal on orange    5.23
   So blue, coral and orange are DECORATION — fills, rules, illustration.
   Type on light is deep teal or taupe; type on deep teal is white or powder;
   type on a saturated ground is deep teal at display size only.
   ═══════════════════════════════════════════════════════════════════════ */

:root{
  /* Every blue below is the LAUNDRY wordmark's own colour or a step along its
     hue. #47A5BC was read straight off the artwork — the flat interior of
     those glyphs — and the rest of the ramp is that same 191.8° hue at
     different lightness, so the tints, rules, fills and dark ink all belong to
     one colour rather than three teals that happen to sit near each other. */
  --blue:#47A5BC;          /* fills, rules, illustration — never small type */
  --blue-tx:#2C6978;       /* 5.86 on ivory — the text-safe step on that hue */
  --deep:#47A5BC;          /* the blue as a ground */
  --deep-2:#3F96AC;        /* its shading partner */
  --on-deep:#FFFFFF;
  /* --ink was a near-black with a teal cast, left over from the original
     brief's "deep teal". It now sits high enough on the logo's own hue to
     read as blue rather than as black — 7.78 on ivory, so it still carries
     body copy. --brand is the deep end of that same ramp: the darkest step
     where white type still clears 4.5, which is what every filled button
     needs. Both are the logo blue, one step and two steps down. */
  --ink:#235561;
  --brand:#347C8E;
  --coral:#E97868;
  --orange:#F69A44;
  --powder:#DBECF0;
  --powder-2:#F1F7F9;
  --ivory:#FAF8F5;
  --taupe:#735C54;
  --white:#FFFFFF;
  --line:#E7E0D8;
  --line-2:rgba(255,255,255,.34);   /* rules on the blue grounds */

  --disp:"Plus Jakarta Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --font:"DM Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --serif:"Instrument Serif",Georgia,serif;

  --maxw:1320px;
  --gut:clamp(20px,4vw,48px);
  --sect:clamp(28px,4.8vh,92px);
  /* a wider break for the two sections that open straight off the bottom of a
     dense one — the testimonials after the calculator, the FAQ after them */
  --sect-lg:clamp(52px,10vh,168px);

  --r-sm:14px;
  --r:20px;
  --r-lg:24px;
  --rp:999px;

  --nav-h:92px;

  --e:cubic-bezier(.22,1,.36,1);
  --e-stop:cubic-bezier(.32,.72,0,1);
  --d-fast:180ms; --d:320ms; --d-slow:620ms; --stagger:70ms;
  --float:1;                 /* zeroed under reduced motion, stops every loop */
  --ring:inset 0 0 0 1px var(--line);
  --shadow:inset 0 0 0 1px var(--line);
  --shadow-lg:0 1px 2px rgba(21,63,74,.04), 0 18px 44px -26px rgba(21,63,74,.22);
}
@media (prefers-reduced-motion:reduce){
  :root{ --d-fast:0ms; --d:0ms; --d-slow:0ms; --stagger:0ms; --float:0; }
}

*,*::before,*::after{ box-sizing:border-box; }
html{ scroll-behavior:smooth; -webkit-text-size-adjust:100%; background:var(--ivory); }
[id]{ scroll-margin-top:calc(var(--nav-h) + 20px); }
@media (prefers-reduced-motion:reduce){ html{ scroll-behavior:auto; } }

body{
  margin:0; background:var(--ivory); color:var(--ink);
  font-family:var(--font); font-size:clamp(16px,1.1vw,19px); line-height:1.62;
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
img,svg{ max-width:100%; height:auto; display:block; }
p{ margin:0; }
a{ color:inherit; }
button{ font:inherit; }
h1,h2,h3,h4{ margin:0; font-family:var(--disp); font-weight:800;
  letter-spacing:-.03em; line-height:1.06; color:var(--ink); }
::selection{ background:var(--ink); color:var(--ivory); }
:where(a,button,input,summary,[tabindex]):focus-visible{
  outline:3px solid var(--blue); outline-offset:3px; border-radius:8px;
}

.wrap{ width:100%; max-width:var(--maxw); margin-inline:auto; padding-inline:var(--gut); }
.sect{ padding-block:var(--sect); }
.sect--break{ padding-top:var(--sect-lg); }

/* ── type scale, straight from the brief ──────────────────────────── */
.t-hero{ font-size:clamp(34px,min(5.4vw,8.4vh),84px); line-height:1.02; letter-spacing:-.035em; }
.t-sect{ font-size:clamp(23px,min(4.1vw,5.6vh),58px); line-height:1.06; letter-spacing:-.032em; }
.t-card{ font-size:clamp(18px,min(1.9vw,2.9vh),30px); line-height:1.15; letter-spacing:-.025em; }
.t-lead{ font-size:clamp(14.5px,min(1.35vw,2vh),19px); line-height:1.5; color:var(--taupe); }
.t-sm  { font-size:clamp(14px,1vw,16px); line-height:1.55; }
.ital{ font-family:var(--serif); font-style:italic; font-weight:400;
  letter-spacing:-.01em; }
.mut{ color:var(--taupe); }
.num{ font-variant-numeric:lining-nums tabular-nums; }

.sr{ position:absolute; width:1px; height:1px; margin:-1px; padding:0; border:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
.skip{ position:fixed; top:.6rem; left:.6rem; z-index:120; transform:translateY(-250%);
  background:var(--ink); color:var(--white); font-weight:700; font-size:14px;
  text-decoration:none; padding:.8rem 1.3rem; border-radius:var(--rp);
  transition:transform var(--d) var(--e); }
.skip:focus{ transform:none; }

/* ── section heads, one rhythm everywhere ───────────────────────────
   `ch` is sized by the element's OWN font, so a max-width in ch on the
   WRAPPER resolves against the 19px body text and crushes the 64px
   heading inside it into a ~240px column. Measure on the heading. */
.head h2{ max-width:17ch; }
.head--wide h2{ max-width:21ch; }
.head .t-lead{ margin-top:clamp(.55rem,1.3vh,1.05rem); max-width:52ch; }
.head--c{ margin-inline:auto; text-align:center; }
.head--c .t-lead{ margin-inline:auto; }

/* ── buttons ──────────────────────────────────────────────────────── */
.btn{
  font-family:var(--disp); font-size:15px; font-weight:700; line-height:1;
  padding:0 1.7rem; min-height:54px; border-radius:var(--rp); border:2px solid transparent;
  cursor:pointer; text-decoration:none; white-space:nowrap;
  display:inline-flex; align-items:center; justify-content:center; gap:.6rem;
  transition:background-color var(--d) var(--e), color var(--d) var(--e),
             border-color var(--d) var(--e), transform var(--d) var(--e),
             box-shadow var(--d) var(--e);
}
.btn svg{ transition:transform var(--d) var(--e-stop); }
.btn:hover svg{ transform:translateX(4px); }
.btn--p{ background:var(--brand); color:var(--white); }
.btn--p:hover{ background:#2C6978; transform:translateY(-2px); box-shadow:var(--shadow); }
.btn--p:active{ transform:translateY(0); }
.btn--g{ background:transparent; color:var(--brand); border-color:rgba(52,124,142,.34); }
.btn--g:hover{ border-color:var(--brand); background:rgba(52,124,142,.06); transform:translateY(-2px); }
/* the ghost button standing on the logo blue: white would read 2.85:1 there */
.btn--on-deep{ color:var(--white); border-color:rgba(255,255,255,.46); }
.btn--on-deep:hover{ border-color:var(--white); background:rgba(255,255,255,.12); }
.btn--w{ background:var(--white); color:var(--brand); }
.btn--w:hover{ background:var(--powder); transform:translateY(-2px); box-shadow:var(--shadow); }
.btn--sm{ min-height:46px; font-size:14px; padding:0 1.25rem; }
.btn:disabled{ opacity:.42; cursor:not-allowed; transform:none !important; }

/* ── nav ──────────────────────────────────────────────────────────── */
.nav{ position:fixed; top:0; left:0; right:0; z-index:90;
  transition:background-color var(--d) var(--e), box-shadow var(--d) var(--e),
             border-color var(--d) var(--e); border-bottom:1px solid transparent; }
.nav.stuck{ background:rgba(255,255,255,.92); border-bottom-color:var(--line);
  box-shadow:0 1px 20px -6px rgba(21,63,74,.16); }
@supports (backdrop-filter:blur(8px)){
  /* the frost lives on a child: backdrop-filter on .nav itself would make it
     the containing block for the fixed mobile drawer and crush it to 49px */
  .nav.stuck{ background:rgba(255,255,255,.72); }
  .nav__bg{ position:absolute; inset:0; z-index:-1; backdrop-filter:blur(14px); }
}
.nav__in{ display:flex; align-items:center; gap:1.5rem;
  min-height:var(--nav-h); transition:min-height var(--d) var(--e); }
.nav.stuck .nav__in{ min-height:74px; }
.brand{ flex:none; display:flex; align-items:center; min-height:56px; }
.brand img{ height:clamp(40px,4.6vw,56px); width:auto;
  transition:height var(--d) var(--e); }
.nav.stuck .brand img{ height:clamp(34px,3.9vw,46px); }
.links{ display:flex; align-items:center; gap:.4rem; margin-left:auto; }
.links a{ font-family:var(--disp); font-size:15px; font-weight:600; text-decoration:none;
  color:var(--ink); padding:.55rem .85rem; border-radius:var(--rp); position:relative;
  transition:background-color var(--d-fast) linear, color var(--d-fast) linear; }
.links a::after{ content:""; position:absolute; left:50%; bottom:.3rem; translate:-50% 0;
  width:5px; height:5px; border-radius:50%; background:var(--coral);
  opacity:0; transition:opacity var(--d) var(--e); }
.links a:hover{ background:rgba(21,63,74,.06); }
.links a[aria-current="true"]::after{ opacity:1; }
.nav__cta{ flex:none; margin-left:.4rem; }
.links .only-m{ display:none; }

.burger{ display:none; width:48px; height:48px; border-radius:50%; background:var(--white);
  border:1px solid var(--line); cursor:pointer; margin-left:auto; flex:none;
  flex-direction:column; align-items:center; justify-content:center; gap:5px; }
.burger span{ display:block; width:18px; height:2px; background:var(--ink); border-radius:2px;
  transition:transform var(--d) var(--e-stop), opacity var(--d-fast) linear; }
body.open .burger span:nth-child(1){ transform:translateY(7px) rotate(45deg); }
body.open .burger span:nth-child(2){ opacity:0; }
body.open .burger span:nth-child(3){ transform:translateY(-7px) rotate(-45deg); }

/* ── reveals: CSS keeps them VISIBLE; script parks them ───────────── */
.rv{ opacity:1; }
.rv.armed{ opacity:0; transform:translateY(26px); }
.rv.armed.in{ opacity:1; transform:none;
  transition:opacity var(--d-slow) var(--e), transform var(--d-slow) var(--e);
  transition-delay:calc(var(--i,0) * var(--stagger)); }
@media (prefers-reduced-motion:reduce){
  .rv,.rv.armed{ opacity:1 !important; transform:none !important; }
}

/* ── floating illustrated accents, from the logo ──────────────────── */
.drift{ animation:drift calc(var(--t,7s) / max(var(--float),.001)) ease-in-out infinite; }
.drift--b{ animation-delay:-2.3s; }
.drift--c{ animation-delay:-4.1s; }
@keyframes drift{
  0%,100%{ transform:translate3d(0,0,0) rotate(0deg); }
  50%    { transform:translate3d(0,-13px,0) rotate(7deg); }
}
@media (prefers-reduced-motion:reduce){ .drift{ animation:none; } }

/* ── curved section dividers ──────────────────────────────────────── */
.curve{ display:block; width:100%; height:clamp(40px,5vw,90px); }
.curve path{ fill:currentColor; }

/* ═════════ 1. HERO ═════════
   Rebuilt. The previous one was the default generated-page scaffold —
   eyebrow pill, oversized headline, two buttons, a shadowed form card,
   a floating badge on the photo and drifting sparkles. Six stacked
   blocks and three kinds of decoration.

   Now: four elements and one photograph that runs to the edge of the
   screen. The availability field IS the primary CTA rather than sitting
   under a duplicate of it, and "see how it works" is a text link, not a
   second pill competing with the first.
   ───────────────────────────────────────────────────────────────── */
.hero{ position:relative; border-bottom:1px solid var(--line);
  padding-top:var(--nav-h); }
/* The hero is the page's biggest moment, so it takes the whole screen: the
   nav band, the scene, and the numbered strip add up to one viewport and
   no more — which is also what keeps it inside the fits-one-screen rule. */
.hero__g{ display:grid; grid-template-columns:minmax(0,1.04fr) minmax(0,.96fr);
  align-items:stretch;
  min-height:clamp(340px, calc(100svh - var(--nav-h) - 78px), 900px); }
/* the left column aligns to .wrap's content edge at every width — a hero
   that starts on a different margin from the rest of the page is the sort
   of thing that reads as "templated" without anyone saying why */
.hero__c{ display:flex; flex-direction:column; justify-content:center;
  padding-block:clamp(1.25rem,3.6vh,3rem);
  padding-left:max(var(--gut), calc((100vw - var(--maxw)) / 2 + var(--gut)));
  padding-right:clamp(1.5rem,4vw,4rem); }
.hero__k{ font-family:var(--disp); font-size:13.5px; font-weight:700;
  color:var(--taupe); margin-bottom:clamp(.9rem,1.6vw,1.3rem); }
.hero h1{ max-width:12ch; }
.hero__lead{ margin-top:clamp(.7rem,1.7vh,1.3rem); max-width:44ch; }

/* availability field — one control, and it is the primary action */
.zip{ margin-top:clamp(1rem,2.6vh,1.9rem); max-width:31rem; }
.zip__f{ display:flex; gap:.5rem; background:var(--white);
  border:1px solid var(--line); border-radius:var(--rp); padding:6px;
  transition:border-color var(--d-fast) linear; }
.zip__f:focus-within{ border-color:var(--brand); }
.zip__in{ flex:1 1 auto; min-width:0; }
.zip__in input{ width:100%; min-height:52px; border:0; background:transparent;
  padding:0 1.1rem; font-family:var(--font); font-size:16px; font-weight:500;
  color:var(--ink); outline:0; font-variant-numeric:lining-nums; }
.zip__in input::placeholder{ color:#9C948C; font-weight:400; }
.zip__f .btn{ flex:none; min-height:52px; }
.zip__err{ display:block; margin-top:.6rem; font-size:13.5px; font-weight:500;
  color:#B4452F; min-height:1.2em; }
.zip__f:has(input[aria-invalid="true"]){ border-color:#B4452F; }
.zip__note{ margin-top:.55rem; font-size:14px; color:var(--taupe); }

/* secondary action as a link, not a second pill */
.tlink{ display:inline-flex; align-items:center; gap:.5rem; margin-top:clamp(.6rem,1.7vh,1.15rem);
  font-family:var(--disp); font-size:15px; font-weight:700; color:var(--ink);
  text-decoration:none; min-height:44px;
  border-bottom:2px solid transparent; align-self:flex-start;
  transition:border-color var(--d) var(--e); }
.tlink svg{ transition:transform var(--d) var(--e-stop); }
.tlink:hover{ border-bottom-color:var(--ink); }
.tlink:hover svg{ transform:translateX(4px); }

/* one photograph, running off the right edge of the screen */
.hero__fig{ position:relative; overflow:hidden; background:var(--powder); }
.hero__fig img{ position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; object-position:58% 42%; will-change:transform; }

/* a hairline band instead of a floating badge — states what the service
   is without pretending to be a data point */
.hero__strip{ border-top:1px solid var(--line); }
.hero__strip ol{ list-style:none; margin:0; padding:0; display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr)); }
.hero__strip li{ padding:clamp(.6rem,1.5vh,1.15rem) clamp(1rem,2vw,1.75rem);
  border-left:1px solid var(--line); display:flex; align-items:baseline; gap:.75rem;
  font-family:var(--disp); font-weight:700; font-size:clamp(15px,1.25vw,18px);
  letter-spacing:-.02em; }
.hero__strip li:first-child{ border-left:0; padding-left:0; }
.hero__strip em{ font-style:normal; font-family:var(--font); font-weight:400;
  font-size:13px; color:var(--taupe); font-variant-numeric:lining-nums; }

/* ═════════ 2. TRANSFORMATION (scrollytelling) ═════════ */
/* Was #0E262C — a near-black that read as "the dark section" rather than as
   the brand. It is the logo blue now, the same flood as the time section, with
   the photographs coming through at the open end of the veil. */
.tx{ position:relative; background:var(--blue); color:var(--white);
  height:calc(var(--stages,3) * 100vh); }
/* The copy used to sit ON the photographs behind a veil. With the ground now
   the logo blue rather than a near-black, that veil had to run at .97 to hold
   white type — and a light blue at .97 over bright interior photography is not
   a veil, it is a haze: the pictures disappeared. Split instead, the same way
   the hero, How It Works and the time section are split. The blue is flat and
   the photographs are the photographs. */
.tx__pin{ position:sticky; top:0; height:100vh; height:100svh; overflow:hidden;
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.02fr);
  align-items:stretch; }
.tx__bg{ position:relative; overflow:hidden; background:var(--deep-2); order:2; }
.tx__fr{ position:absolute; inset:0; opacity:0; will-change:opacity; }
.tx__fr:first-child{ opacity:1; }
.tx__fr img{ width:100%; height:100%; object-fit:cover; will-change:transform; }
/* Measured, not eyeballed. Glyph rectangles collected with
   Range.getClientRects(), the ground re-screenshotted with the text hidden,
   98th-percentile luminance sampled inside each rect. The horizontal veil
   works on desktop, where the copy lives in the left 45%; on a phone the
   copy spans the full width and the same gradient left it at 2.14:1.
   Desktop and phone need genuinely different scrim SHAPES. */
/* only a breath of the blue at the seam, so the photograph does not start on a
   hard vertical line against the panel */
.tx__veil{ position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(90deg, rgba(71,165,188,.5) 0%,
             rgba(71,165,188,.12) 9%, rgba(71,165,188,0) 22%); }
@media (max-width:1000px){
  /* stacked: the photograph sits above the copy, so the blend runs downward
     from the panel into the top of the picture */
  .tx__veil{ background:linear-gradient(180deg, rgba(71,165,188,.5) 0%,
             rgba(71,165,188,.12) 12%, rgba(71,165,188,0) 26%); }
}
.tx__in{ position:relative; z-index:2; width:100%;
  display:flex; flex-direction:column; justify-content:center;
  padding-top:calc(var(--nav-h) + clamp(1rem,2.4vh,2.25rem));
  padding-bottom:clamp(1rem,2.4vh,2.25rem);
  padding-left:max(var(--gut), calc((100vw - var(--maxw)) / 2 + var(--gut)));
  padding-right:clamp(1.5rem,4vw,4rem);
  max-width:none; margin:0; order:1; }
/* same rule as .head — the cap belongs on the heading, not the box */
.tx__head h2{ max-width:15ch; font-size:clamp(28px,3.6vw,54px); }
.tx__head h2{ color:var(--white); }
.tx__head .t-lead{ color:var(--white); margin-top:.9rem; max-width:44ch; }
.tx__steps{ position:relative; min-height:10.5rem; margin-top:clamp(1.1rem,2.2vw,1.9rem);
  max-width:40rem; }
.txs{ position:absolute; inset:0; opacity:0; pointer-events:none;
  transform:translate3d(0,14px,0); will-change:opacity,transform;
  transition:opacity 260ms var(--e), transform 260ms var(--e); }
.txs.on{ opacity:1; transform:none; }
@media (prefers-reduced-motion:reduce){
  .txs{ transition:opacity 120ms linear; transform:none; }
}
/* powder on the logo blue is 1.73 — on this panel the step label is white,
   like everything else sitting on it */
.txs__n{ display:inline-flex; align-items:center; gap:.7rem; font-family:var(--disp);
  font-size:13px; font-weight:700; color:var(--white); }
.txs__n b{ width:34px; height:34px; border-radius:50%; background:var(--coral);
  color:#0E262C; display:grid; place-items:center;
  /* 20px/800 clears the large-text threshold, where deep-on-coral is 3.99
     against a 3.0 requirement. At 14px it needed 4.5 and failed. */
  font-size:20px; font-weight:800; line-height:1; }
.txs p{ margin-top:.85rem; font-family:var(--disp); font-weight:700;
  font-size:clamp(20px,2.1vw,32px); line-height:1.18; letter-spacing:-.03em;
  color:var(--white); max-width:22ch; }
.tx__rail{ height:4px; border-radius:4px; background:rgba(255,255,255,.2);
  overflow:hidden; margin-top:clamp(1.1rem,2.2vw,1.8rem); max-width:40rem; }
.tx__rail i{ display:block; height:100%; width:100%; background:var(--coral);
  border-radius:4px; transform:scaleX(0); transform-origin:left center; }
.tx__cta{ margin-top:clamp(1.1rem,2.2vw,1.75rem); opacity:0; pointer-events:none;
  will-change:opacity; transition:opacity 300ms var(--e); }
.tx__cta.on{ opacity:1; pointer-events:auto; }

/* ═════════ 3. HOW IT WORKS ═════════
   Pinned, like the services rail: the scene locks to the viewport and
   scrolling advances the step rather than carrying you past it. Below the
   mobile breakpoint it unpins and the steps advance from their own block.
   ───────────────────────────────────────────────────────────── */
.how{ position:relative; height:calc(100svh + var(--n,3) * 64svh); }
.how__pin{ position:sticky; top:0; height:100vh; height:100svh; overflow:hidden;
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.04fr);
  align-items:stretch; }
/* the photograph runs to the LEFT edge of the screen and the full height of
   the scene — mirroring the hero and the time section. A rounded panel with
   a drop shadow in the middle of the layout was the framed-photo problem
   again. */
.how__c{ display:flex; flex-direction:column; justify-content:center;
  padding-top:calc(clamp(1rem,2.4vh,2.25rem) + var(--nav-h));
  padding-bottom:clamp(1rem,2.4vh,2.25rem);
  padding-right:max(var(--gut), calc((100vw - var(--maxw)) / 2 + var(--gut)));
  padding-left:clamp(1.5rem,4vw,4rem); }
.how .t-sect{ font-size:clamp(22px,min(3.1vw,4.9vh),44px); }
.how .t-lead{ font-size:clamp(13.5px,min(1.1vw,1.8vh),17px); }

.how__fig{ position:relative; overflow:hidden; background:var(--powder); }
/* Same curtain as the Time stage: each step's photograph is masked from the
   bottom and rises over the one before it, which stays fully opaque beneath.
   .on is applied to every frame up to the current step, so walking backwards
   retracts the curtain instead of dissolving to the wrong picture. */
.how__fr{ position:absolute; inset:0; clip-path:inset(100% 0 0 0);
  transition:clip-path 780ms var(--e); }
.how__fr:first-child,.how__fr.on{ clip-path:inset(0 0 0 0); }
.how__fr img{ width:100%; height:100%; object-fit:cover;
  transform:scale(1.05) translate3d(0,3%,0); transition:transform 1500ms var(--e); }
.how__fr:first-child img,.how__fr.on img{ transform:none; }
@media (prefers-reduced-motion:reduce){
  .how__fr{ transition:none; }
  .how__fr img,.how__fr.on img{ transform:none; transition:none; }
}
/* the active step, stated on the picture itself */
.how__tag{ position:absolute; z-index:2; left:0; right:0; bottom:0;
  padding:clamp(2.5rem,6vw,5rem) clamp(1.25rem,3vw,2.25rem) clamp(1rem,2.4vh,1.6rem);
  background:linear-gradient(to top, rgba(8,32,38,.86) 0%, rgba(8,32,38,.5) 48%,
             rgba(8,32,38,0) 100%);
  font-family:var(--disp); font-weight:700; color:var(--white);
  font-size:clamp(15px,min(1.5vw,2.3vh),21px); letter-spacing:-.02em; }
/* There is no separate rail element any more. One used to run the full height
   of the list at left:0 while each dot was placed at a negative offset built
   from a DIFFERENT clamp, so their centres drifted ~4px apart and the drift
   changed with the viewport — and the full-height line left an orphan stub
   above the first dot and below the last. Now every step draws its own
   connector down to the next one, so the segments start and end exactly on the
   dots by construction and there is nothing left over at either end. */
.how__steps{ --dot:13px; --dot-gap:clamp(1.15rem,1.8vw,1.75rem);
  --dot-y:calc(clamp(.45rem,1.25vh,1rem) + .55rem);
  --dot-x:calc(-1 * (var(--dot) + var(--dot-gap)) + var(--dot) / 2);
  position:relative; padding-left:calc(var(--dot) + var(--dot-gap));
  margin-top:clamp(.9rem,2.2vh,1.75rem); }
.hws{ position:relative; padding-block:clamp(.45rem,1.25vh,1rem); cursor:pointer;
  border:0; background:none; text-align:left; width:100%; display:block;
  color:inherit; font:inherit; }
.hws::before{ content:""; position:absolute; left:var(--dot-x); top:var(--dot-y);
  width:var(--dot); height:var(--dot); margin-left:calc(var(--dot) / -2);
  border-radius:50%; background:var(--blue);
  transition:background-color var(--d) var(--e), scale var(--d) var(--e-stop); }
/* the connector from this step's dot down to the next step's dot */
.hws::after{ content:""; position:absolute; left:var(--dot-x); width:3px;
  margin-left:-1.5px; border-radius:3px; background:var(--blue);
  top:calc(var(--dot-y) + var(--dot));
  bottom:calc(-1 * var(--dot-y));
  transition:background-color var(--d) var(--e); }
.hws:last-child::after{ display:none; }
/* steps past the current one are still to come; the current one's own outgoing
   connector belongs to what has not happened yet, so it greys too */
.hws[aria-selected="true"]::before{ background:var(--coral); scale:1.15; }
.hws[aria-selected="true"]::after,
.hws[aria-selected="true"] ~ .hws::after,
.hws[aria-selected="true"] ~ .hws::before{ background:var(--line); }
.hws__k{ font-family:var(--disp); font-size:13px; font-weight:700; color:var(--blue-tx); }
/* .72, not .4 — at .4 the two steps you are not on drop to 2.23:1 and stop
   being readable, which reads as broken rather than as de-emphasised. The
   current step is carried by the coral dot and its open paragraph. */
.hws h3{ margin-top:.25rem; font-size:clamp(16px,min(1.9vw,3vh),26px);
  opacity:.82; transition:opacity var(--d) var(--e); }
.hws p{ margin-top:.35rem; color:var(--taupe); max-width:44ch; font-size:clamp(13.5px,min(1vw,1.7vh),16px);
  max-height:0; overflow:hidden; opacity:0;
  transition:max-height 520ms var(--e), opacity var(--d) var(--e), margin-top var(--d) var(--e); }
.hws[aria-selected="true"] h3{ opacity:1; }
.hws[aria-selected="true"] p{ max-height:9rem; opacity:1; }
/* The CTA row sits BELOW the pinned scene rather than inside it. Keeping it
   in the pin cost ~90px of the single screen the scene has to fit into, and
   on a 640px window that pushed the heading under the fixed nav and let
   overflow:hidden clip the bottom — which is what read as "buggy". */
.how__end{ background:var(--ivory); border-top:1px solid var(--line); }
.how__end .wrap{ display:flex; flex-wrap:wrap; align-items:center; justify-content:center;
  gap:.6rem 1.4rem; padding-block:clamp(.9rem,2.4vh,1.6rem); }
.how__end p{ font-family:var(--disp); font-weight:700; font-size:clamp(15px,min(1.4vw,2.2vh),20px); }

/* ═════════ 4. SERVICES ═════════
   A horizontal rail driven by vertical scroll: the section pins and the
   track travels sideways as you scroll down. The card size is derived from
   the VIEWPORT HEIGHT, not its width, which is what guarantees the whole
   scene — heading, cards and progress — fits on one screen at any size.
   Below the mobile breakpoint it unpins and becomes a native swipe.
   ───────────────────────────────────────────────────────────── */
.svcs{ position:relative; background:var(--ivory); overflow-x:clip; }
.svcs__pin{ position:sticky; top:0; height:100vh; height:100svh;
  display:flex; flex-direction:column; justify-content:center;
  gap:clamp(1.1rem,2.2vw,2rem); overflow:hidden;
  padding-top:var(--nav-h); padding-bottom:clamp(.75rem,2vw,1.5rem); }
.svcs .t-sect{ font-size:clamp(27px,3.2vw,48px); }
.svcs .t-lead{ font-size:clamp(15px,1.15vw,18px); }
.svc__top{ display:flex; align-items:flex-end; justify-content:space-between;
  gap:2rem; flex-wrap:wrap; }
.arrows{ display:flex; gap:.6rem; flex:none; }
.arrow{ width:52px; height:52px; border-radius:50%; background:var(--white);
  border:1px solid var(--line); color:var(--ink); cursor:pointer;
  display:grid; place-items:center;
  transition:background-color var(--d) var(--e), transform var(--d) var(--e),
             border-color var(--d) var(--e), opacity var(--d) var(--e); }
.arrow:hover:not(:disabled){ background:var(--ink); color:var(--white);
  border-color:var(--ink); transform:scale(1.06); }
.arrow:disabled{ opacity:.32; cursor:not-allowed; }

.rail{ position:relative; }
.rail__t{ display:flex; gap:clamp(.9rem,1.6vw,1.5rem); will-change:transform;
  padding-left:max(var(--gut), calc((100vw - var(--maxw)) / 2 + var(--gut)));
  padding-right:var(--gut); }
/* The rail was a flex item that shrank when the scene got tight, and
   overflow:hidden then sliced the card titles and links off the bottom.
   Now the rail owns an explicit height and the MEDIA absorbs whatever is
   left after the text, so the whole card always fits and nothing clips. */
.svcs .rail{ overflow:hidden; flex:0 0 auto; min-height:0;
  height:clamp(290px,50svh,560px); }
.svcs .rail__t{ height:100%; align-items:stretch; }
.svcs .svc{ display:flex; flex-direction:column; height:100%; }
.svcs .svc__f{ flex:1 1 auto; min-height:110px; height:auto; }
.svcs .svc h3,.svcs .svc__d,.svcs .svc__l{ flex:none; }
/* trailing spacer: padding-right on a flex scroll container is not included
   in scrollWidth, so travel came up one gutter short and card 4 stopped
   10px past the edge. A zero-content flex item is counted. */
.svcs .rail__t::after{ content:""; flex:0 0 var(--gut); }
/* The services rail is driven by vertical scroll; the testimonial rail keeps
   native horizontal scrolling. Rewriting the shared .rail__t for services
   removed the overflow-x:auto this one depended on, and it silently spilled
   2436px off the page instead of scrolling. */
#qTrack{ overflow-x:auto; scroll-snap-type:x mandatory; scroll-behavior:smooth;
  padding-bottom:1rem; scrollbar-width:none; -ms-overflow-style:none; }
#qTrack::-webkit-scrollbar{ display:none; }
#qRail.drag #qTrack{ scroll-behavior:auto; cursor:grabbing; scroll-snap-type:none; }
@media (prefers-reduced-motion:reduce){ #qTrack{ scroll-behavior:auto; } }
/* Width from viewport WIDTH so only ~2.5 cards are ever on screen — that is
   what gives the rail something to travel. Height of the MEDIA is capped in
   svh so the whole card still fits the pinned scene on a short viewport. */
.svc{ flex:0 0 clamp(260px,34vw,480px); border:0; background:none; padding:0;
  cursor:pointer; text-align:left; color:inherit; font:inherit; }
.svc__f{ position:relative; border-radius:var(--r); overflow:hidden;
  height:clamp(190px,37svh,430px); background:var(--powder); }
.svc__f img{ width:100%; height:100%; object-fit:cover;
  transition:transform 760ms var(--e); }
.svc:hover .svc__f img,.svc:focus-visible .svc__f img{ transform:scale(1.055); }
.svc__go{ position:absolute; right:12px; bottom:12px; width:48px; height:48px;
  border-radius:50%; background:var(--white); color:var(--ink);
  display:grid; place-items:center; opacity:0; transform:translateY(10px) scale(.9);
  transition:opacity var(--d) var(--e), transform var(--d) var(--e-stop); }
.svc:hover .svc__go,.svc:focus-visible .svc__go{ opacity:1; transform:none; }
.svc h3{ margin-top:.85rem; font-size:clamp(18px,1.7vw,26px); }
.svc__d{ margin-top:.35rem; color:var(--taupe); font-size:15px; max-width:32ch;
  display:block; }
.svc__l{ margin-top:.6rem; font-family:var(--disp); font-size:14px; font-weight:700;
  color:var(--blue-tx); display:inline-flex; align-items:center; gap:.4rem; }
.svc:hover .svc__l svg{ transform:translateX(4px); }
.svc__l svg{ transition:transform var(--d) var(--e-stop); }
.bar{ height:3px; border-radius:3px; background:var(--line); overflow:hidden;
  max-width:22rem; }
.bar i{ display:block; height:100%; background:var(--ink); border-radius:3px;
  width:30%; }
.svcs__foot{ display:flex; align-items:center; justify-content:space-between;
  gap:1.5rem; flex-wrap:wrap; }

/* ═════════ 5. PREFERENCES ═════════ */
/* was a rounded picture in a box beside the controls — the same "framed
   photo" problem the time section had. Full bleed to the right edge,the
   chip on the image{ background:var(--powder-2); overflow-x:clip; }
/* Symmetric padding,unlike the pinned scenes. This section scrolls past
   normally{ display:flex; flex-direction:column; justify-content:center;
  padding-block:clamp(1.5rem,4.4vh,3.25rem);
  padding-left:max(var(--gut), calc((100vw - var(--maxw)) / 2 + var(--gut)));
  padding-right:clamp(1.5rem,4vw,4rem); }
.grp{ margin-bottom:clamp(.5rem,1.3vh,1.05rem); }
.grp__k{ font-family:var(--disp); font-size:12.5px; font-weight:700; color:var(--taupe);
  margin-bottom:clamp(.3rem,.8vh,.6rem); }
.opts{ display:flex; gap:.7rem; flex-wrap:wrap; }
.opt{ flex:1 1 0; min-width:9.5rem; background:var(--white); border:1px solid var(--line);
  border-radius:var(--r-sm); padding:clamp(.55rem,1.3vh,.9rem) 1.15rem; cursor:pointer; text-align:left;
  display:flex; align-items:center; gap:.8rem; color:var(--ink); min-height:44px;
  transition:border-color var(--d) var(--e), background-color var(--d) var(--e),
             transform var(--d) var(--e), box-shadow var(--d) var(--e); }
.opt:hover{ transform:translateY(-2px); box-shadow:var(--shadow); }
.opt[aria-pressed="true"]{ border-color:var(--brand); background:var(--brand); color:var(--white); }
.opt b{ font-family:var(--disp); font-size:15.5px; font-weight:700; display:block; }
.chip{ position:absolute; left:clamp(1rem,2.4vw,2rem); bottom:clamp(1rem,2.4vw,2rem);
  z-index:2; background:rgba(255,255,255,.96); border-radius:var(--rp);
  padding:.75rem 1.4rem; font-family:var(--disp); font-weight:700; font-size:15px;
  color:var(--ink); box-shadow:var(--shadow); white-space:nowrap;
  max-width:calc(100% - 28px); overflow:hidden; text-overflow:ellipsis;
  transition:opacity var(--d) var(--e); }

/* ═════════ 6. RECLAIMED TIME ═════════
   The four photographs were sitting in a rounded box with the caption in a
   separate block underneath — a picture frame, not a scene. Now the panel
   bleeds to the right edge of the screen and runs the full height of the
   section, matching the hero's language, and the caption sits ON the
   photograph so the whole right half changes as one thing.
   ───────────────────────────────────────────────────────────── */
/* Scroll-driven rather than click-driven: the section pins and scrolling
   moves through the four activities. The list stays clickable and scrolls
   to that position, the way the how-it-works steps do. */
.time{ background:var(--deep); color:var(--on-deep); position:relative;
  overflow-x:clip; height:calc(100svh + var(--n,4) * 58svh); }
.time__pin{ position:sticky; top:0; height:100vh; height:100svh; overflow:hidden; }
.time h2,.time h3{ color:var(--on-deep); }
.time .t-sect{ font-size:clamp(27px,3.4vw,52px); }
/* On a ground this light the ink cannot be faded and stay readable — 82%
   lands at 4.15:1. Secondary copy on the blue keeps full ink and takes its
   hierarchy from size and weight instead. */
.time .t-lead{ color:var(--on-deep); }
.time__g{ display:grid; grid-template-columns:minmax(0,.86fr) minmax(0,1fr);
  align-items:stretch; height:100%; }
.time__c{ display:flex; flex-direction:column; justify-content:center;
  /* padding tied to viewport HEIGHT so a short window gives the content room
     rather than the margins — plus the fixed nav's height reserved at the
     top, or the heading centres underneath the overlay */
  padding-top:calc(clamp(.9rem,2.2vh,2rem) + var(--nav-h));
  padding-bottom:clamp(.9rem,2.2vh,2rem);
  padding-left:max(var(--gut), calc((100vw - var(--maxw)) / 2 + var(--gut)));
  padding-right:clamp(1.5rem,4vw,4rem); }
.picks{ display:flex; flex-direction:column; gap:0;
  margin-top:clamp(.7rem,1.8vh,1.5rem); }
.pick{ background:transparent; border:0; border-top:1px solid var(--line-2);
  padding:clamp(.45rem,1.15vh,.9rem) 0; cursor:pointer; text-align:left;
  color:var(--on-deep); display:flex; align-items:center; gap:1rem; width:100%;
  transition:padding-left var(--d) var(--e), border-color var(--d) var(--e); }
.pick:last-child{ border-bottom:1px solid var(--line-2); }
/* No opacity fade on the inactive rows. On a ground this light, half-opacity
   ink lands at 2.28:1 and the unselected names stop being readable. The
   current row is marked by the rule, the indent and the arrow instead —
   which is the stronger signal anyway. */
.pick__n{ font-family:var(--disp); font-size:12.5px; font-weight:700;
  color:var(--on-deep); letter-spacing:.06em; flex:none; width:2.4ch;
  transform:translateY(-.1em); }
.pick b{ font-family:var(--disp); font-size:clamp(16px,min(2.1vw,3.2vh),28px); font-weight:700;
  display:block; letter-spacing:-.028em;
  transition:transform var(--d) var(--e); }
.pick__go{ margin-left:auto; flex:none; opacity:0; color:var(--on-deep);
  transition:opacity var(--d) var(--e), transform var(--d) var(--e-stop);
  transform:translateX(-6px); }
/* No coral bracket around the current row — two stray orange rules floating in
   the list read as a rendering fault rather than as a selection. The current
   row is the one that has stepped in and grown an arrow. */
.pick:hover{ padding-left:.6rem; }
.pick[aria-pressed="true"]{ padding-left:.6rem; }
.pick[aria-pressed="true"] .pick__go{ opacity:1; transform:none; }

.time__fig{ position:relative; overflow:hidden; background:var(--deep-2); }
/* DOM order is the stack: each frame paints above the one before it, which is
   what lets paintTime() wipe rather than cross-dissolve. The caption's own
   z-index puts it above the whole stack. The first frame starts opaque so the
   figure is never a blue rectangle before the scroll engine's first paint. */
.time__fr{ position:absolute; inset:0; opacity:0; will-change:opacity; }
.time__fr:first-child{ opacity:1; }
.time__fr img{ width:100%; height:100%; object-fit:cover; will-change:transform; }

.time__cap{ position:absolute; z-index:2; left:0; right:0; bottom:0;
  padding:clamp(3rem,7vw,6rem) clamp(1.25rem,3vw,2.5rem) clamp(1.25rem,2.4vw,2rem);
  /* measured, not guessed: the 98th-percentile luminance under these glyphs
     has to stay at or below 0.183 for white to clear 4.5:1 */
  background:linear-gradient(to top, rgba(8,32,38,.93) 0%, rgba(8,32,38,.66) 44%,
             rgba(8,32,38,0) 100%);
  font-family:var(--disp); font-weight:700; color:var(--white);
  font-size:clamp(17px,1.7vw,24px); letter-spacing:-.025em; line-height:1.3;
  transition:opacity var(--d) var(--e); }
.time__act{ margin-top:clamp(.75rem,1.9vh,1.5rem); align-self:flex-start; }

/* ═════════ 7. PRICING ═════════ */
/* The section heading lives INSIDE the left column,so the order card can
   start level with the first line of type instead of below it. stretch,not
   start: the card then runs the full height of the column it sits beside and
   the two edges line up top and bottom{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,.78fr);
  gap:clamp(1rem,2.4vw,2rem); align-items:stretch; }
/* The slack from matching the calculator's height is absorbed by the summary
   rows,a quarter each{ display:flex; flex-direction:column; flex:1; }


/* ═════════ 9. FAQ ═════════ */
.faq__g{ display:grid; grid-template-columns:minmax(0,.8fr) minmax(0,1fr);
  gap:clamp(2rem,5vw,5rem); align-items:start; }
.faq__art{ margin-top:clamp(1.75rem,3vw,2.5rem); position:relative; }
.faq__art img{ width:min(100%,22rem); border-radius:var(--r); }
.acc{ border-top:1px solid var(--line); }
.acc__i{ border-bottom:1px solid var(--line); }
.acc__q{ width:100%; border:0; background:none; cursor:pointer; text-align:left;
  display:flex; align-items:flex-start; justify-content:space-between; gap:1.25rem;
  padding-block:clamp(.5rem,1.15vh,.95rem); color:var(--ink);
  font-family:var(--disp); font-weight:700; font-size:clamp(14.5px,min(1.35vw,2vh),19px);
  line-height:1.3; letter-spacing:-.02em;
  transition:color var(--d-fast) linear; }
.acc__q:hover{ color:var(--blue-tx); }
.acc__s{ flex:none; width:28px; height:28px; border-radius:50%; background:var(--powder);
  display:grid; place-items:center; position:relative; margin-top:1px;
  transition:background-color var(--d) var(--e); }
.acc__q[aria-expanded="true"] .acc__s{ background:var(--ink); }
.acc__s::before,.acc__s::after{ content:""; position:absolute; background:var(--ink);
  border-radius:2px; transition:transform var(--d) var(--e-stop), background-color var(--d) var(--e); }
.acc__s::before{ width:13px; height:2.5px; }
.acc__s::after{ width:2.5px; height:13px; }
.acc__q[aria-expanded="true"] .acc__s::before,
.acc__q[aria-expanded="true"] .acc__s::after{ background:var(--white); }
.acc__q[aria-expanded="true"] .acc__s::after{ transform:scaleY(0); }
.acc__p{ overflow:hidden; max-height:0; transition:max-height 420ms var(--e); }
.acc__p > div{ padding-bottom:clamp(.6rem,1.3vh,1.1rem); color:var(--taupe);
  max-width:56ch; font-size:clamp(13.5px,min(1vw,1.75vh),16px); }

/* ═════════ 10. SERVICE AREA ═════════ */
.area{ background:var(--powder-2); }
.area__g{ display:grid; grid-template-columns:minmax(0,.82fr) minmax(0,1fr);
  gap:clamp(2rem,4.5vw,4.5rem); align-items:center; }
.map{ position:relative; border-radius:var(--r-lg); overflow:hidden;
  background:var(--powder); aspect-ratio:4/3; box-shadow:var(--shadow); }
.map svg{ width:100%; height:100%; display:block; }
.pin{ transform-box:fill-box; transform-origin:center bottom;
  animation:pin calc(3.4s / max(var(--float),.001)) ease-in-out infinite; }
.pin:nth-of-type(2){ animation-delay:-.9s; }
.pin:nth-of-type(3){ animation-delay:-1.8s; }
.pin:nth-of-type(4){ animation-delay:-2.5s; }
@keyframes pin{ 0%,100%{ transform:translateY(0);} 50%{ transform:translateY(-7px);} }
@media (prefers-reduced-motion:reduce){ .pin{ animation:none; } }
.van{ animation:van calc(14s / max(var(--float),.001)) linear infinite; }
@keyframes van{ 0%{ transform:translateX(-14%);} 100%{ transform:translateX(112%);} }
@media (prefers-reduced-motion:reduce){ .van{ animation:none; } }

/* ═════════ 11. FINAL CTA ═════════ */
.fin{ position:relative; background:var(--deep); color:var(--on-deep); overflow:hidden; }
.fin h2{ color:var(--on-deep); }
.fin .t-lead{ color:var(--on-deep); }
.fin__g{ display:flex; flex-direction:column; align-items:center; text-align:center;
  justify-content:center; min-height:clamp(300px,58svh,600px);
  padding-block:var(--sect); }
.fin__k{ font-family:var(--disp); font-size:13.5px; font-weight:700;
  color:var(--on-deep); letter-spacing:.08em; text-transform:uppercase;
  margin-bottom:clamp(.6rem,1.5vh,1.1rem); }
.fin__g h2{ max-width:16ch; }
.fin__g .t-lead{ max-width:46ch; color:var(--on-deep);
  margin-top:clamp(.7rem,1.7vh,1.15rem); }
.fin__acts{ display:flex; flex-wrap:wrap; justify-content:center; gap:.75rem;
  margin-top:clamp(1.1rem,2.8vh,1.9rem); }

/* ═════════ FOOTER ═════════ */
/* Ivory, not the blue. Two reasons. The lockup's own "LAUNDRY" is this exact
   blue, so on a blue footer the brand name disappears into its background.
   And with the CTA above it already blue, a blue footer made one 1000px slab
   of flat colour to close the page on. A light footer under a colour-blocked
   CTA gives the page an actual ending. */
.foot{ background:var(--ivory); color:var(--ink); border-top:1px solid var(--line); }
.foot__g{ display:grid; grid-template-columns:1.5fr 1fr 1fr 1fr;
  gap:clamp(1.75rem,3vw,3rem); padding-block:clamp(2.5rem,5vw,4.5rem); }
.foot__b img{ width:clamp(150px,16vw,200px); }
.foot__b p{ margin-top:1rem; font-size:15px; color:var(--taupe); max-width:26ch; }
.foot h4{ font-family:var(--disp); font-size:13px; font-weight:700; color:var(--ink);
  margin-bottom:.9rem; }
.foot ul{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; }
/* 44px minimum tap target — a 20px-tall link in a footer list is the
   single most common accessibility miss on a marketing page */
/* flex, not inline-flex: inline-flex shrinks to the word, so "FAQ" ended up a
   28px-wide target. Block-level makes the whole column width tappable. */
.foot li a{ font-size:15px; text-decoration:none; color:var(--taupe);
  display:flex; align-items:center; min-height:44px;
  transition:color var(--d-fast) linear; }
.foot a{ text-decoration:none; color:var(--taupe);
  transition:color var(--d-fast) linear; }
.foot a:hover{ color:var(--ink); text-decoration:underline; text-underline-offset:4px; }
.foot__end{ border-top:1px solid var(--line); padding-block:1.25rem;
  display:flex; flex-wrap:wrap; gap:.75rem 1.75rem; align-items:center;
  font-size:13.5px; color:var(--taupe); }
.foot__end p:first-child{ margin-right:auto; }

/* ═════════ BOOKING MODAL ═════════ */
.mask{ position:fixed; inset:0; z-index:100; background:rgba(11,38,44,.55);
  display:grid; place-items:center; padding:var(--gut);
  opacity:0; pointer-events:none; transition:opacity var(--d) var(--e); }
@supports (backdrop-filter:blur(4px)){ .mask{ backdrop-filter:blur(5px); } }
.mask.on{ opacity:1; pointer-events:auto; }
.bk{ background:var(--ivory); border-radius:var(--r-lg); width:min(100%,46rem);
  max-height:min(92svh,52rem); overflow:hidden; display:flex; flex-direction:column;
  box-shadow:var(--shadow-lg); transform:translateY(18px) scale(.985);
  transition:transform var(--d-slow) var(--e-stop); }
.mask.on .bk{ transform:none; }
.bk__h{ padding:clamp(1.1rem,2vw,1.5rem) clamp(1.2rem,2.4vw,2rem);
  border-bottom:1px solid var(--line); background:var(--white); flex:none; }
.bk__hr{ display:flex; align-items:center; gap:1rem; }
.bk__hr img{ height:38px; width:auto; }
.bk__x{ margin-left:auto; width:46px; height:46px; border-radius:50%; flex:none;
  border:1px solid var(--line); background:var(--white); color:var(--ink);
  cursor:pointer; display:grid; place-items:center;
  transition:background-color var(--d) var(--e), transform var(--d) var(--e); }
.bk__x:hover{ background:var(--powder); transform:rotate(90deg); }
.prog{ display:flex; gap:.4rem; margin-top:1.1rem; }
.prog i{ flex:1 1 0; height:4px; border-radius:4px; background:var(--line);
  overflow:hidden; position:relative; }
.prog i::after{ content:""; position:absolute; inset:0; background:var(--blue);
  border-radius:4px; transform:scaleX(0); transform-origin:left;
  transition:transform 420ms var(--e); }
.prog i.done::after{ transform:scaleX(1); }
.prog__k{ margin-top:.7rem; font-family:var(--disp); font-size:13px; font-weight:700;
  color:var(--taupe); }
.bk__b{ padding:clamp(1.3rem,2.6vw,2.1rem) clamp(1.2rem,2.4vw,2rem);
  overflow-y:auto; flex:1 1 auto; -webkit-overflow-scrolling:touch; }
.bk__p{ display:none; }
.bk__p.on{ display:block; animation:bkin 420ms var(--e); }
@keyframes bkin{ from{ opacity:0; transform:translateY(14px); } to{ opacity:1; transform:none; } }
@media (prefers-reduced-motion:reduce){ .bk__p.on{ animation:none; } }
.bk__p h3{ font-size:clamp(22px,2.4vw,32px); max-width:20ch; }
.bk__p > p{ margin-top:.7rem; color:var(--taupe); max-width:46ch; }
.bk__f{ padding:clamp(1rem,2vw,1.3rem) clamp(1.2rem,2.4vw,2rem);
  border-top:1px solid var(--line); background:var(--white); flex:none;
  display:flex; align-items:center; gap:.75rem; }
.bk__f .btn:last-child{ margin-left:auto; }
.pickers{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:.7rem; margin-top:1.5rem; }
.pk{ border:2px solid var(--line); background:var(--white); border-radius:var(--r-sm);
  padding:1rem; cursor:pointer; text-align:left; color:var(--ink);
  display:flex; align-items:center; gap:.85rem;
  transition:border-color var(--d) var(--e), transform var(--d) var(--e),
             box-shadow var(--d) var(--e); }
.pk:hover{ transform:translateY(-2px); box-shadow:var(--shadow); }
.pk[aria-pressed="true"]{ border-color:var(--blue); box-shadow:var(--shadow); }
.pk__f{ width:52px; height:52px; border-radius:12px; overflow:hidden; flex:none;
  background:var(--powder); }
.pk__f img{ width:100%; height:100%; object-fit:cover; }
.pk b{ font-family:var(--disp); font-size:15.5px; font-weight:700; display:block; }
.pk span{ font-size:13px; color:var(--taupe); }
.dates{ display:flex; gap:.6rem; margin-top:1.4rem; overflow-x:auto;
  padding-bottom:.5rem; scrollbar-width:none; }
.dates::-webkit-scrollbar{ display:none; }
.dt{ flex:none; width:5.4rem; border:2px solid var(--line); background:var(--white);
  border-radius:var(--r-sm); padding:.85rem .5rem; cursor:pointer; text-align:center;
  color:var(--ink); transition:border-color var(--d) var(--e), transform var(--d) var(--e); }
.dt:hover{ transform:translateY(-2px); }
.dt[aria-pressed="true"]{ border-color:var(--ink); background:var(--ink); color:var(--white); }
.dt small{ display:block; font-size:12px; opacity:.75; }
.dt b{ display:block; font-family:var(--disp); font-size:20px; font-weight:800;
  margin-top:.15rem; }
.slots{ display:flex; gap:.6rem; flex-wrap:wrap; margin-top:1.1rem; }
.sl{ border:2px solid var(--line); background:var(--white); border-radius:var(--rp);
  padding:.65rem 1.15rem; cursor:pointer; font-family:var(--disp); font-size:14px;
  font-weight:700; color:var(--ink);
  transition:border-color var(--d) var(--e), background-color var(--d) var(--e); }
.sl[aria-pressed="true"]{ border-color:var(--ink); background:var(--ink); color:var(--white); }
.ta{ width:100%; margin-top:1.1rem; min-height:6.5rem; border:2px solid var(--line);
  border-radius:var(--r-sm); padding:.9rem 1.05rem; font-family:var(--font);
  font-size:16px; color:var(--ink); background:var(--white); outline:0; resize:vertical;
  transition:border-color var(--d-fast) linear; }
.ta:focus{ border-color:var(--blue); }
.recap{ list-style:none; margin:1.5rem 0 0; padding:0; background:var(--white);
  border-radius:var(--r); overflow:hidden; box-shadow:var(--shadow); }
.recap li{ display:flex; justify-content:space-between; gap:1rem;
  padding:.95rem clamp(1rem,2vw,1.35rem); border-bottom:1px solid var(--line);
  font-size:15.5px; }
.recap li:last-child{ border-bottom:0; }
.recap span:first-child{ color:var(--taupe); }
.recap span:last-child{ font-family:var(--disp); font-weight:700; text-align:right; }
.done{ text-align:center; padding-block:1rem; }
.done__i{ width:74px; height:74px; border-radius:50%; background:var(--powder);
  display:grid; place-items:center; margin:0 auto 1.25rem; color:var(--ink); }

/* ═════════ MOBILE CTA BAR ═════════ */
.mcta{ display:none; }

/* ═══════════ RESPONSIVE ═══════════ */
@media (max-width:1000px){
.hero__g,.how__g,.time__g,.faq__g,.area__g,.fin__g{
    grid-template-columns:minmax(0,1fr); }
  .hero__g{ gap:clamp(2.25rem,5vw,3rem); }
  /* Stacked, the figure is a grid row with no intrinsic height, so the
     photograph collapsed to a sliver. The other two pinned figures already
     carry an aspect ratio; the hero needs one too, and a taller crop suits a
     portrait screen. */
  .hero__fig{ max-width:34rem; margin-inline:auto; width:100%; aspect-ratio:4/3; }
  .how__fig{ aspect-ratio:16/10; }
.time__fig{ aspect-ratio:16/10; max-width:40rem; }
  .svc{ flex:0 0 calc((100% - clamp(1rem,1.8vw,1.75rem)) / 1.75); }
  .foot__g{ grid-template-columns:1fr 1fr 1fr; }
  .foot__b{ grid-column:1/-1; }
  .tx{ height:calc(var(--stages,3) * 92vh); }
  /* One column: the picture on top, the copy under it. At 26svh the picture
     was a 219px letterbox strip pinned to the very top of the screen while
     the copy had 450px to rattle around in — which is what read as "photo too
     high, lots of dead space". The band is now 40svh, and the step box below
     is tightened so the copy still fits without the two fighting. */
  .tx__pin{ grid-template-columns:minmax(0,1fr);
    grid-template-rows:clamp(230px,36svh,330px) minmax(0,1fr); }
  .tx__steps{ min-height:8.5rem; margin-top:clamp(.8rem,1.8vh,1.2rem); }
  .tx__head h2{ font-size:clamp(26px,7.2vw,34px); }
  /* The copy column is a flex column, so anything without flex:none gets
     silently crushed when the content is a few px taller than the box — the
     4px progress rail was being squeezed to nothing. */
  .tx__rail{ margin-top:clamp(.8rem,1.8vh,1.2rem); flex:none; }
  .tx__cta{ margin-top:clamp(.8rem,1.8vh,1.2rem); flex:none; }
  .tx__head{ flex:none; }
  .tx__steps{ flex:none; }
  .tx__bg{ order:1; }
  .tx__in{ order:2; padding-top:clamp(1.25rem,3vh,2rem); }
}

@media (max-width:860px){
  .burger{ display:flex; }
  .nav__cta{ display:none; }
  .links{ position:fixed; inset:0; z-index:95; margin:0; flex-direction:column;
    align-items:stretch; justify-content:center; gap:.3rem; background:var(--ivory);
    padding:clamp(1.5rem,6vw,3rem); overflow-y:auto; overscroll-behavior:contain; }
  .links[hidden]{ display:none; }
  .links a{ font-family:var(--disp); font-size:clamp(26px,7vw,38px); font-weight:800;
    letter-spacing:-.035em; padding:.65rem .5rem; border-radius:var(--r-sm);
    min-height:62px; display:flex; align-items:center; }
  .links a::after{ display:none; }
  .links .only-m{ display:flex; margin-top:1.5rem; justify-content:center; }
  body.open{ position:fixed; left:0; right:0; width:100%; overflow:hidden; }
  body.open .burger{ position:fixed; top:14px; right:var(--gut); z-index:99; }
  body.open .mcta{ display:none !important; }
  .mcta{ display:block; position:fixed; left:12px; right:12px; z-index:88;
    bottom:calc(12px + env(safe-area-inset-bottom,0px)); }
  .mcta .btn{ width:100%; box-shadow:var(--shadow-lg); }
  body{ padding-bottom:86px; }
  .tx__pin{ padding-bottom:88px; }
  .how{ height:auto; }
  .time{ height:auto; }
  .time__pin{ position:static; height:auto; }
  /* clear the fixed CTA bar, which was covering this row's button */
  .how__end .wrap{ padding-bottom:calc(clamp(.9rem,2.4vh,1.6rem) + 76px); }
  .how__pin{ position:static; height:auto; padding-top:var(--sect);
    padding-bottom:0; }
  .how__pin{ display:block; }
  .how__fig{ aspect-ratio:16/10; }
  .how__c{ padding:var(--sect) var(--gut) 0; }
  /* Unpinned, the rail stopped being moved by page scroll — but it was still
     overflow:hidden, so there was no way to reach cards 2 to 4 at all. It
     becomes a normal swipeable track with snap points. */
  /* The picker is a chooser at this size, not a scroll story, so the current
     row has to look chosen — an indent and a small arrow do not read on a
     phone. */
  .pick[aria-pressed="true"]{ background:rgba(255,255,255,.16);
    border-radius:var(--r-sm); padding-left:.75rem; padding-right:.75rem; }
  .pick[aria-pressed="true"] b{ text-decoration:underline;
    text-decoration-thickness:2px; text-underline-offset:4px; }

  .svcs__pin{ position:static; height:auto; padding-top:var(--sect);
    padding-bottom:var(--sect); }
  /* Unpinned, the rail is no longer moved by page scroll, so it must be a real
     swipeable track — overflow:hidden here leaves cards 2 to 4 unreachable. */
  .svcs .rail{ overflow:visible; }
  .svcs .rail__t{ overflow-x:auto; scroll-snap-type:x mandatory;
    scroll-behavior:smooth; padding-bottom:1rem;
    /* Without this the snap aligns card one's left edge to the scroller's
       edge, which scrolls the track's own gutter away — the card ends up
       flush against the screen and reads as cut off, and the rail looks like
       it loaded already scrolled. Snap to the padded start instead. */
    scroll-padding-left:var(--gut);
    scrollbar-width:none; -ms-overflow-style:none; transform:none !important; }
  .svcs .rail__t::-webkit-scrollbar{ display:none; }
  .svc{ flex:0 0 calc(100% - 3.4rem); scroll-snap-align:start; }
  .svc__f{ height:auto; aspect-ratio:4/5; }
  .foot__g{ grid-template-columns:1fr 1fr; }
.opts{ flex-direction:column; }
.opt{ min-width:0; }
  .bk{ width:100%; max-height:100svh; height:100svh; border-radius:0; }
  .mask{ padding:0; }
  .pickers{ grid-template-columns:1fr; }
  .tx{ height:calc(var(--stages,3) * 86svh); }
  .tx__steps{ min-height:11rem; }
}

@media (max-width:560px){
  .hero::before{ inset:0 0 50% 0; }

  /* The field and its button stack on a narrow screen, because "Check
     Availability" beside an input is unreadable at 390px. A pill shape only
     works on one row though — stacked, the 999px radius turned the whole
     control into a lozenge with the input floating inside it. Square the
     container off and let the button keep the pill. */
  .zip__f{ flex-direction:column; align-items:stretch; gap:.5rem;
    border-radius:var(--r); padding:8px; }
  .zip__f .btn{ width:100%; }
  .zip__in input{ min-height:50px; padding:0 .9rem; }

  /* three columns of step copy at 390px clipped every label */
  .hero__strip ol{ grid-template-columns:minmax(0,1fr); }
  .hero__strip li{ border-left:0; padding-left:0; padding-right:0;
    border-top:1px solid var(--line); }
  .hero__strip li:first-child{ border-top:0; }
  .stack{ right:-2%; bottom:4%; width:clamp(94px,26%,120px); }
  .foot__g{ grid-template-columns:1fr; }
.svc{ flex:0 0 88%; }
  .how__end{ flex-direction:column; align-items:flex-start; }
  .bk__f{ flex-wrap:wrap; }
  .bk__f .btn{ flex:1 1 auto; }
}

/* ═════════ AWAITING REVIEWS ═════════
   The honest state for a business that does not have reviews yet. It takes the
   room the testimonial rail used to, so the section does not collapse. */
.await{ display:flex; flex-direction:column; align-items:center; text-align:center;
  gap:.75rem; padding:clamp(2rem,6vw,3.5rem) var(--gut);
  border:1px dashed var(--line); border-radius:var(--r-lg); background:var(--white);
  margin-top:clamp(1rem,2.4vh,1.9rem); }
.await__i{ display:grid; place-items:center; width:52px; height:52px; border-radius:50%;
  background:var(--powder); color:var(--blue-tx); }
.await__k{ font-family:var(--disp); font-weight:700; color:var(--ink);
  font-size:clamp(17px,min(1.8vw,2.7vh),22px); letter-spacing:-.02em; }
.await__m{ color:var(--taupe); max-width:46ch; font-size:clamp(14.5px,min(1.2vw,1.9vh),17px); }
