/* ---------------------------------------------------------------------------
   Fonts, self-hosted.

   These used to come from fonts.googleapis.com. That cost two preconnects, a
   stylesheet on a third-party origin, and a request chain: HTML -> Google CSS
   -> Google font files. Three origins before a single letter could be drawn.
   Google measured it at 1,090ms of render-blocking on Slow 4G.

   Now they are ordinary same-origin files, preloaded from the HTML, so the
   browser starts fetching them without waiting for any CSS to parse.

   LATIN SUBSET ONLY. Every one of the 92 distinct characters the site renders
   is inside the latin unicode-range -- checked, not assumed. latin-ext would
   have added 92KB and covered nothing the page uses. If copy ever gains a
   character outside it (an accented name, say) that glyph falls back to the
   next font in the stack; it does not break, it just looks different.

   NO size-adjust / ascent-override HERE ON PURPOSE. Matched-metric fallbacks
   need the real ascent, descent and unitsPerEm from inside the font files, and
   woff2 is Brotli-compressed -- neither brotli nor fontTools is on this
   machine, so those numbers cannot be read. Invented ones would CAUSE layout
   shift rather than prevent it. CLS is measured at 0.000 today, and
   self-hosting shortens the swap window rather than lengthening it, so the
   fallback shows for less time than before. If CLS ever moves off zero,
   install fontTools, read the metrics, and add the overrides then.

   Both families are SIL Open Font License; self-hosting is expressly allowed.
   --------------------------------------------------------------------------- */
@font-face{
  font-family:"Archivo";
  src:url("../fonts/archivo-latin.woff2") format("woff2");
  font-weight:400 600;      /* the only two the site uses */
  font-style:normal;
  font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:"Fraunces";
  src:url("../fonts/fraunces-latin.woff2") format("woff2");
  font-weight:300 600;
  font-style:normal;
  font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

  :root{
    --ink:#14100E;          /* smoked charcoal — warm near-black */
    --ink-raised:#26201B;   /* raised surface */
    --ink-deep:#0A0706;     /* deepest well */
    --steam:#EDE6DC;        /* warm off-white, birch/steam */
    --steam-dim:#A79C90;    /* warm muted grey, hue-biased to ember */
    --steam-faint:#8D877D;   /* AA on all three grounds. Verified by site-audit.py, not by hand. */
    --ember:#E2913F;        /* firelight amber */
    --ember-deep:#B4602A;   /* coal */
    --glacier:#93B6C4;      /* cold plunge / snow-shadow blue */
    --hair:rgba(237,230,220,.13);
    --hair-soft:rgba(237,230,220,.07);

    --display:"Fraunces","Iowan Old Style",Georgia,serif;
    --body:"Archivo","Helvetica Neue",Helvetica,Arial,sans-serif;

    --measure:58ch;
    --pad:clamp(1.25rem,5vw,5rem);
  }

  *{box-sizing:border-box}

  body{
    margin:0;
    background:var(--ink);
    color:var(--steam);
    font-family:var(--body);
    font-size:clamp(.95rem,.92rem + .15vw,1.02rem);
    line-height:1.7;
    -webkit-font-smoothing:antialiased;
  }

  /* ---------- type ---------- */
  h1,h2,h3{
    font-family:var(--display);
    font-weight:400;
    text-wrap:balance;
    margin:0;
    letter-spacing:-.015em;
  }
  h1{font-size:clamp(2.9rem,1.4rem + 6.2vw,6.6rem);line-height:.98;letter-spacing:-.02em;font-optical-sizing:auto}
  h2{font-size:clamp(2rem,1.25rem + 3vw,3.8rem);line-height:1.06}
  h3{font-size:clamp(1.2rem,1.05rem + .6vw,1.5rem);line-height:1.25}
  p{margin:0;max-width:var(--measure)}
  a{color:inherit}

  .label{
    font-family:var(--body);
    font-size:.68rem;
    font-weight:600;
    letter-spacing:.22em;
    text-transform:uppercase;
    color:var(--steam-faint);
  }
  .label--ember{color:var(--ember)}
  .lede{font-size:clamp(1.02rem,.98rem + .4vw,1.22rem);color:var(--steam-dim);line-height:1.62}

  /* ---------- layout ---------- */
  /* Every nav link is an in-page anchor, and the nav is sticky. Without this
     the target lands underneath it. The bottom value is for the fixed thumb
     bar and is set in the mobile block. */
  html{scroll-padding-top:5rem}
  .wrap{max-width:1200px;margin-inline:auto;padding-inline:var(--pad)}
  section{padding-block:clamp(3rem,5vw,4.5rem)}
  .stack{display:flex;flex-direction:column}
  .rule{border:0;border-top:1px solid var(--hair-soft);margin:0}

  /* ---------- section rhythm ----------
     These were 37 inline style attributes. They are the reason the spacing
     drifted: nine sections used one gap, one used another, and nothing said
     which was correct. Now there is one value per role and one place to
     change it. */
  .section{gap:clamp(1.75rem,3.5vw,2.5rem)}
  /* A section that continues the previous thought rather than starting a new
     one sits closer to it. Where this is used the <hr> above it is removed:
     the rule and a full break were doing the same job twice, which is what
     made the page read as evenly spaced slabs. The rules that remain now mark
     real changes of subject. */
  .section--close{padding-block-start:clamp(1.5rem,3vw,2.5rem)}
  .section--tight{gap:1.2rem}
  .section__head{gap:1.1rem}
  .capture__head{gap:.8rem;margin-top:2.75rem}

  .lede--sm{font-size:.92rem}
  .lede--md{font-size:.95rem}
  .hl{color:var(--steam)}
  .hl--ember{color:var(--ember)}
  .label--glacier{color:var(--glacier)}
  .foot__place{margin-top:.4rem}
  .map{width:100%;height:auto;display:block}

  /* ---------- buttons ---------- */
  .btn{
    display:inline-flex;align-items:center;gap:.6em;
    font-family:var(--body);font-size:.85rem;font-weight:600;
    letter-spacing:.09em;text-transform:uppercase;text-decoration:none;
    padding:1.05em 2em;border-radius:2px;
    transition:background .25s ease,color .25s ease,border-color .25s ease;
  }
  .btn--fire{background:var(--ember);color:var(--ink-deep);border:1px solid var(--ember)}
  .btn--fire:hover{background:#F0A557;border-color:#F0A557}
  .btn--ghost{background:transparent;color:var(--steam);border:1px solid var(--hair)}
  .btn--ghost:hover{border-color:var(--steam-dim);background:rgba(237,230,220,.04)}
  .btn:focus-visible{outline:2px solid var(--glacier);outline-offset:3px}

  /* ---------- focus and skip ----------
     Every focusable thing gets a visible ring, not just .btn. Someone
     navigating by keyboard must always be able to see where they are. */
  a:focus-visible,button:focus-visible,input:focus-visible{outline:2px solid var(--glacier);outline-offset:3px;border-radius:2px}

  /* Visible to screen readers, invisible on screen. For labels that would be
     redundant next to a placeholder but are still needed for accessibility. */
  .visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

  /* Off-screen until focused, then it appears top-left. Lets a keyboard or
     screen-reader user jump past the nav straight into the page. */
  /* Focus targets for fragment links. Safari moves the viewport but not the
     screen reader cursor, so link targets need to be programmatically
     focusable. tabindex="-1" does that without entering the tab order; this
     rule stops the resulting focus ring appearing on a whole section. */
  [tabindex="-1"]:focus{outline:none}

  .skip{position:absolute;left:-9999px;top:0;z-index:60;background:var(--ember);color:var(--ink-deep);padding:.9em 1.4em;border-radius:2px;font-size:.8rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;text-decoration:none}
  .skip:focus{left:1rem;top:1rem}

  /* ---------- nav ---------- */
  .nav{
    position:sticky;top:0;z-index:20;
    display:flex;align-items:center;justify-content:space-between;gap:1rem;
    padding:1.1rem var(--pad);
    background:rgba(20,16,14,.82);
    -webkit-backdrop-filter:blur(14px);
    backdrop-filter:blur(14px);
    border-bottom:1px solid var(--hair-soft);
  }
  .nav__mark{font-family:var(--display);font-size:1.05rem;letter-spacing:.01em;text-decoration:none}
  .nav__mark span{color:var(--ember)}
  .nav__links{display:flex;align-items:center;gap:clamp(1rem,2.4vw,2.1rem)}
  .nav__links a{
    font-size:.78rem;font-weight:600;letter-spacing:.11em;text-transform:uppercase;
    color:var(--steam-dim);text-decoration:none;transition:color .2s ease;
  }
  .nav__links a:hover{color:var(--steam)}
  .nav__book{
    color:var(--ink-deep)!important;background:var(--ember);
    padding:.7em 1.2em;border-radius:2px;
  }
  /* ---------- mobile ----------
     Not a shrunken desktop. The nav links come back as a scrolling row, the
     primary actions move to a fixed bar at thumb height, and the vertical
     rhythm tightens so the page is not an endless scroll on a phone. */
  .thumb{display:none}

  @media(max-width:820px){
    .nav{flex-wrap:wrap;gap:.75rem;padding:.9rem var(--pad) .7rem}
    .nav__mark{font-size:1rem}
    .nav__book{display:none}
    .nav__links{order:3;width:100%;gap:1.35rem;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;padding-bottom:.15rem}
    .nav__links::-webkit-scrollbar{display:none}
    .nav__links a{white-space:nowrap;font-size:.72rem}

    body{font-size:16px;padding-bottom:5.25rem}
    /* Keeps focused links and anchor targets clear of the fixed bar instead of
       scrolling underneath it. The bar is roughly 5.25rem tall; this leaves a
       little more than that. */
    html{scroll-padding-bottom:6.5rem;scroll-padding-top:6.75rem}

    .thumb{display:flex;position:fixed;left:0;right:0;bottom:0;z-index:30;align-items:center;gap:.9rem;padding:.8rem var(--pad) calc(.8rem + env(safe-area-inset-bottom));background:rgba(20,16,14,.94);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);border-top:1px solid var(--hair)}
    .thumb__btn{flex:1;display:flex;align-items:center;justify-content:center;background:var(--ember);color:var(--ink-deep);text-decoration:none;font-size:.78rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;padding:1.1em 1rem;border-radius:2px;min-height:48px}
    .thumb__call{flex:none;display:flex;align-items:center;justify-content:center;min-width:48px;min-height:48px;padding:0 .9rem;border-radius:2px;border:1px solid var(--hair);color:var(--steam);text-decoration:none;font-size:.78rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase}

    section{padding-block:3.5rem}
    /* Not auto: the hero photograph is the best thing on the site and auto
       shrinks it to roughly half a phone screen. */
    .hero{min-height:min(80vh,620px);min-height:min(80svh,620px);padding-block:4.5rem 3rem}
    h1{font-size:clamp(2.3rem,8.5vw,3.1rem)}
    .hero h1{max-width:none}
    .hero__inner{gap:1.35rem}
    .hero .lede{max-width:none}
    .hero__photo{object-position:52% 38%}

    .foot{gap:2rem 2.5rem}
    .foot__col{gap:.9rem}
    .foot a{font-size:.92rem}
  }

  @media(max-width:400px){
    section{padding-block:3rem}
    .hero{padding-block:3.5rem 2.5rem}
  }

  /* No backdrop blur (Safari before 18, and anything older): the translucent
     nav and thumb bar would sit see-through over the hero photograph and the
     links would be unreadable. Fall back to near-opaque instead. */
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .nav{background:rgba(20,16,14,.97)}
    .thumb{background:rgba(20,16,14,.99)}
  }

  /* ---------- hero ----------

     THE HERO SHOT — this is the one that matters most on the whole site.

     Shot after dark, in an ordinary suburban driveway. Snow on the ground.
     The window lit orange from inside. Steam coming off the roof. Low angle,
     from across the street.

     The best version of this is not a photograph. It is a silent 6-second
     video loop, dropped in full-bleed as a background behind .hero__glow,
     with the glow and grain kept over the top of it at reduced opacity.
     Steam moving and firelight flickering does what no still frame can.

     Until that exists, the hero runs on the gradient below, which is why
     this section still looks finished with no image in it at all.
  */
  .hero{
    position:relative;overflow:hidden;
    /* vh first: iOS 15 cannot parse svh and drops the whole declaration,
       which collapses the hero. Newer browsers take the svh line below. */
    min-height:min(92vh,940px);
    min-height:min(92svh,940px);
    display:flex;align-items:flex-end;
    padding-block:clamp(5rem,14vw,9rem);
    border-bottom:1px solid var(--hair-soft);
  }
  /* The photograph itself — a real <img>, not a CSS background, so it can
     carry srcset (three sizes, phones get the small one) and
     fetchpriority="high". A background image can do neither. */
  .hero__photo{
    position:absolute;inset:0;pointer-events:none;
    width:100%;height:100%;
    object-fit:cover;
    object-position:50% 55%;
    aspect-ratio:1800/1012;
    background-color:var(--ink-deep);
    border:0;
  }
  /* Legibility scrim, not a background any more. Dark at the top so the sticky
     nav reads, light through the middle so the lit arch shows, heavy at the
     bottom so the headline and buttons stay readable over the photo. */
  .hero__glow{
    position:absolute;inset:0;pointer-events:none;
    /* Headline contrast measured in-browser inside the real h1 band:
       490px 6.29:1 · 900px 4.98:1 · 1440px 3.78:1 · 1920px 3.86:1.
       Was 1.56-1.81:1 and failed 3:1 at every width. The 100deg layer
       darkens the left where the headline sits - do not remove it.
       Re-measure if any stop changes. */
    background:
      linear-gradient(180deg, rgba(12,9,8,.60) 0%, rgba(12,9,8,.30) 18%,
                      rgba(12,9,8,.68) 44%, rgba(12,9,8,.88) 70%, rgba(12,9,8,.96) 100%),
      linear-gradient(100deg, rgba(12,9,8,.7) 0%, rgba(12,9,8,.15) 46%, transparent 70%),
      radial-gradient(100% 70% at 78% 88%, rgba(226,145,63,.14) 0%, transparent 62%);
  }
  /* .hero__grain REMOVED 2026-08-30 — an experiment, not a design decision.
     It was a full-viewport layer with mix-blend-mode:overlay over a tiled
     160px PNG. A non-normal blend mode creates a stacking context and can
     force the subtree off the GPU, which on a Moto G Power means CPU-
     rasterising the whole hero. LCP element render delay was 2,030ms with
     the image in hand at ~280ms, and this was the top-ranked and cheapest-
     to-falsify explanation.
     If the measurement does NOT move, put it back: it is 22.9KB and it is
     part of how the hero looks. The rule is in git at a416cde. */
  .hero .wrap{position:relative;width:100%}
  /* Transform only. Animating opacity from 0 hides the h1 for .9s, which
     disqualifies it as the Largest Contentful Paint element. */
  .hero__inner{display:flex;flex-direction:column;gap:1.6rem;animation:rise .9s cubic-bezier(.16,1,.3,1) both}
  /* em, not ch: a ch is the width of a zero in whatever font is loaded, so
     these re-measure when Fraunces arrives and the whole hero reflows. em is
     tied to font-size only. --measure stays in ch; body text reflow is
     harmless and ch is the better unit for a reading measure. */
  .hero h1{max-width:7.5em}
  .hero .lede{max-width:23em}
  .hero__cta{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem 1.25rem;margin-top:.6rem}
  /* Quiet phone offer sitting beside the hero buttons. */
  .hero__call{font-size:.88rem;color:var(--steam-dim)}
  .hero__call a{color:var(--steam);text-decoration:none;border-bottom:1px solid var(--hair);padding-bottom:.1em}
  .hero__call a:hover{border-color:var(--steam-dim)}
  .hero__foot{
    display:flex;flex-wrap:wrap;gap:.6rem 1.6rem;margin-top:2.4rem;
    padding-top:1.4rem;border-top:1px solid var(--hair-soft);
  }
  @keyframes rise{from{transform:translateY(22px)}to{transform:none}}

  /* ---------- spec table ---------- */
  .spec{display:grid;gap:0;grid-template-columns:1fr;border-top:1px solid var(--hair)}
  .spec__row{
    display:grid;grid-template-columns:minmax(11rem,22%) 1fr;
    gap:1rem 2rem;padding:1.35rem 0;border-bottom:1px solid var(--hair-soft);
    align-items:baseline;
  }
  .spec__row dt{font-size:.72rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--steam-faint)}
  .spec__row dd{margin:0;font-size:1.02rem;color:var(--steam);font-variant-numeric:tabular-nums}
  @media(max-width:620px){.spec__row{grid-template-columns:1fr;gap:.3rem}}

  /* ---------- faq ----------
     Same restraint as the spec table: hairline rules, no boxes, everything
     visible at once. Question above, answer beneath. */
  .faq{display:grid;grid-template-columns:1fr;border-top:1px solid var(--hair)}
  .faq__row{display:flex;flex-direction:column;gap:.55rem;padding:1.6rem 0;border-bottom:1px solid var(--hair-soft)}
  .faq__row dt{font-family:var(--display);font-size:clamp(1.12rem,1.02rem + .4vw,1.32rem);line-height:1.3;color:var(--steam)}
  .faq__row dd{margin:0;font-size:.95rem;line-height:1.65;color:var(--steam-dim);max-width:var(--measure)}

  /* ---------- photo placeholder ---------- */
  .shot{
    position:relative;display:flex;flex-direction:column;justify-content:flex-end;
    gap:.4rem;padding:1.1rem;
    border:1px dashed rgba(237,230,220,.22);border-radius:2px;
    background:linear-gradient(155deg, rgba(226,145,63,.07), rgba(147,182,196,.05));
    min-height:var(--shot-h,320px);
  }
  .shot::before{
    content:"Photo";position:absolute;top:1rem;left:1.1rem;
    font-size:.62rem;font-weight:600;letter-spacing:.2em;text-transform:uppercase;
    color:var(--steam-faint);
  }
  .shot p{font-size:.82rem;line-height:1.45;color:var(--steam-dim);max-width:34ch}

  /* A real photograph where a .shot placeholder used to be. margin-top:auto
     pins it to the bottom of a flex card so it lines up with the dashed
     placeholders still sitting beside it. */
  .shot-img{width:100%;height:auto;display:block;background:var(--ink-raised)}
  .shot-video{width:100%;height:auto;display:block;background:var(--ink-raised);aspect-ratio:1/1}
  /* Both cards in the Choose-your-sauna row are 1:1, so the still and the
     clip stay identical in shape and size however the row reflows.
     margin-top:auto is SCOPED HERE ON PURPOSE: it pins the media to the bottom
     of a flex card so the two cards line up. Applied globally it also hits
     grid contexts like .own .two, where it overrides align-items:center and
     drops the image to the bottom of the row. Keep it scoped. */
  .model .shot-img,.model .shot-video{aspect-ratio:1/1;object-fit:cover}
  .model .shot-img,.model .shot-figure{margin-top:auto}
  /* The figure wraps the video so a screen reader gets a real description.
     It is now the flex item, so it carries the bottom alignment and must
     shed the browser's default figure margin. */
  .shot-figure{margin:0;display:block}

  /* Portrait photograph beside the spec list.

     max-width is 512px ON PURPOSE and must not be raised. The source file is
     only 1024px wide, so at 2x device pixel ratio 512 CSS px is exactly its
     native resolution. Rendering it any wider upscales the picture and it
     goes soft. If a wider image is ever wanted, reshoot or re-export at a
     higher resolution first — do not widen this rule. */
  .shot-portrait{
    width:100%;
    max-width:512px;
    height:auto;
    display:block;
    justify-self:end;
    background:var(--ink-raised);
  }
  @media(max-width:820px){
    /* Stacks above the specs, and centres rather than clinging to one edge. */
    .shot-portrait{justify-self:center;max-width:512px}
  }

  /* ---------- grids ---------- */
  .two{display:grid;grid-template-columns:1fr 1fr;gap:clamp(1.5rem,4vw,3.5rem);align-items:center}
  /* A heading in a half-width column gets ~19 characters per line at the full
     h2 size, so anything over a short sentence runs to four lines. max-width
     would make that worse, not better — a narrower box wraps more. Size is the
     lever: this gives ~26 characters a line. .own h2 below is smaller still
     and wins on source order. */
  .two h2{font-size:clamp(1.8rem,1.2rem + 1.8vw,2.8rem)}
  .three{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(1.5rem,3vw,2.75rem)}
  .four{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(1.5rem,2.5vw,2.25rem)}
  @media(max-width:860px){.two,.three{grid-template-columns:1fr}}
  @media(max-width:960px){.four{grid-template-columns:repeat(2,1fr)}}
  @media(max-width:620px){.four{grid-template-columns:1fr}}

  /* ---------- heat / cold ---------- */
  .duo{display:grid;grid-template-columns:1fr 1fr;border:1px solid var(--hair);border-radius:2px;overflow:hidden}
  .duo__pane{padding:clamp(1.75rem,4vw,3rem);display:flex;flex-direction:column;gap:1rem;min-height:340px}
  .duo__pane--heat{background:linear-gradient(165deg,rgba(226,145,63,.13),rgba(180,96,42,.04))}
  .duo__pane--cold{background:linear-gradient(165deg,rgba(147,182,196,.13),rgba(147,182,196,.03));border-left:1px solid var(--hair)}
  .duo__temp{font-family:var(--display);font-size:clamp(2.4rem,6vw,3.6rem);line-height:1;font-variant-numeric:tabular-nums}
  .duo__pane--heat .duo__temp{color:var(--ember)}
  .duo__pane--cold .duo__temp{color:var(--glacier)}
  @media(max-width:760px){
    .duo{grid-template-columns:1fr}
    .duo__pane--cold{border-left:0;border-top:1px solid var(--hair)}
  }

  /* ---------- choose your sauna ----------
     Built from the patterns already in this file: the same 1px-gap grid and
     raised-card treatment as .rates / .rate--feature, and .shot for photos. */
  .models{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--hair-soft);border:1px solid var(--hair)}
  .model{background:var(--ink);padding:clamp(1.5rem,3.5vw,2.5rem);display:flex;flex-direction:column;gap:1.15rem}
  .model--raised{background:var(--ink-raised)}
  .model__head{display:flex;align-items:baseline;justify-content:space-between;gap:1rem}
  .model__plus{font-family:var(--display);font-size:1.35rem;line-height:1;color:var(--ember);font-variant-numeric:tabular-nums}
  .model__list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.6rem}
  .model__list li{position:relative;padding-left:1.1rem;font-size:.95rem;color:var(--steam-dim)}
  .model__list li::before{content:"";position:absolute;left:0;top:.68em;width:.4rem;height:1px;background:var(--steam-faint)}
  @media(max-width:760px){.models{grid-template-columns:1fr}}

  /* ---------- steps ---------- */
  .step{display:flex;flex-direction:column;gap:.7rem;padding-top:1.4rem;border-top:1px solid var(--hair)}
  .step__n{font-family:var(--display);font-size:2.1rem;line-height:1;color:var(--ember-deep);font-variant-numeric:tabular-nums}
  .step p{font-size:.95rem;color:var(--steam-dim)}

  /* ---------- pricing ---------- */
  .rates{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--hair-soft);border:1px solid var(--hair)}
  .rate{background:var(--ink);padding:clamp(1.5rem,3vw,2.25rem);display:flex;flex-direction:column;gap:.55rem}
  .rate--feature{background:var(--ink-raised)}
  .rate__price{font-family:var(--display);font-size:clamp(2.1rem,5vw,2.9rem);line-height:1;font-variant-numeric:tabular-nums}
  .rate p{font-size:.9rem;color:var(--steam-dim)}
  /* Rates are a starting point, not a fixed list. This sits tight above the number. */
  .rate__from{margin-bottom:-.4rem}
  .rate__tag{align-self:flex-start;font-size:.62rem;font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:var(--ember);border:1px solid rgba(226,145,63,.4);padding:.3em .7em;border-radius:2px}
  @media(max-width:760px){.rates{grid-template-columns:1fr}}

  /* Inline amber link, for pointing out of body copy. */
  .link-ember{color:var(--ember);text-decoration:underline;text-underline-offset:.22em;text-decoration-thickness:1px}
  .link-ember:hover{color:#F0A557}

  /* The porch upgrade — an ember-outlined box under the four tiers. */
  .upgrade{
    padding:clamp(1.35rem,3vw,2rem);
    border:1px solid rgba(226,145,63,.4);border-radius:2px;
    background:rgba(226,145,63,.045);
    display:flex;flex-direction:column;gap:.7rem;
  }
  .upgrade p{font-size:.94rem;color:var(--steam-dim)}
  .upgrade__price{color:var(--steam)}

  /* ---------- reviews ---------- */
  .reviews{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(1.75rem,3.5vw,3rem)}
  @media(max-width:860px){.reviews{grid-template-columns:1fr}}
  .review{display:flex;flex-direction:column;gap:1.1rem;padding-top:1.5rem;border-top:1px solid var(--hair)}
  .review blockquote{margin:0;font-family:var(--display);font-size:1.18rem;line-height:1.45;color:var(--steam)}
  .review cite{font-style:normal;font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--steam-faint);margin-top:auto}
  .stars{display:flex;align-items:center;gap:.7rem;font-size:.9rem;color:var(--steam-dim)}
  .stars__marks{color:var(--ember);display:inline-flex;align-items:center}
  .stars__marks svg{fill:currentColor;display:block}
  /* White text, amber underline — reads as a link without shouting. */
  .link-underline{color:var(--steam);text-decoration:underline;text-decoration-color:var(--ember);text-decoration-thickness:1px;text-underline-offset:.22em}
  .link-underline:hover{text-decoration-color:#F0A557}

  /* ---------- own one (funnel band) ---------- */
  .own{background:var(--ink-deep);border-top:1px solid var(--hair-soft);border-bottom:1px solid var(--hair-soft)}
  .own .two{align-items:center}
  .own h2{font-size:clamp(1.6rem,1.2rem + 1.5vw,2.3rem)}
  /* Two-figure anchor: the cost of trying one against the cost of owning one. */
  .anchor{display:flex;flex-wrap:wrap;gap:2.5rem;padding-top:1.6rem;margin-top:.4rem;border-top:1px solid var(--hair)}
  .anchor__item{display:flex;flex-direction:column;gap:.2rem}
  .anchor__n{font-family:var(--display);font-size:clamp(1.8rem,4vw,2.4rem);line-height:1;font-variant-numeric:tabular-nums}
  .anchor__item:first-child .anchor__n{color:var(--ember)}
  .anchor__item:last-child .anchor__n{color:var(--steam-faint)}
  /* The rent-to-own terms, stated plainly instead of hidden behind a button. */
  .terms{display:flex;flex-direction:column;gap:.55rem;margin-top:1.15rem;padding-top:1.1rem;border-top:1px solid var(--hair)}
  .terms__row{display:grid;grid-template-columns:minmax(6.5rem,26%) 1fr;gap:.25rem 1rem;align-items:baseline}
  .terms__row dt{font-size:.66rem;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--ember)}
  .terms__row dd{margin:0;font-size:.95rem;color:var(--steam)}
  @media(max-width:620px){.terms__row{grid-template-columns:1fr;gap:.2rem}}

  /* ---------- book ---------- */
  .book{position:relative;overflow:hidden;text-align:left}
  .book__glow{
    position:absolute;inset:0;pointer-events:none;
    background:radial-gradient(80% 120% at 50% 130%, rgba(226,145,63,.22), transparent 62%);
  }
  .book .wrap{position:relative}
  .embed{
    margin-top:2.25rem;padding:clamp(1.5rem,4vw,2.5rem);
    border:1px dashed rgba(226,145,63,.38);border-radius:2px;
    background:rgba(226,145,63,.05);
    display:flex;flex-direction:column;gap:.55rem;
  }
  .embed p{font-size:.88rem;color:var(--steam-dim)}

  /* ---------- email capture (MailerLite) ----------
     MailerLite's own stylesheet is deliberately not loaded — it ships ~500
     lines of !important rules and an Open Sans import that would fight
     everything above and render a white box on this dark page. Their markup
     hooks are kept; every visual rule below is ours, built from the same
     tokens as the rest of the site. */
  .ml-subscribe-form{margin-top:1.5rem}
  .ml-block-form{display:flex;flex-wrap:wrap;gap:.75rem;align-items:stretch}
  .ml-field-email{flex:1 1 20rem;display:flex;min-width:0}
  .ml-block-form input[type="email"]{
    width:100%;
    background:transparent;
    border:1px solid var(--hair);
    border-radius:2px;
    color:var(--steam);
    font-family:var(--body);
    /* 16px minimum: below that iOS zooms the whole page when the field is
       tapped, and the visitor has to pinch back out. */
    font-size:16px;
    line-height:1.4;
    padding:1.05em 1.15em;
    min-height:48px;
    transition:border-color .2s ease;
  }
  .ml-block-form input[type="email"]::placeholder{color:var(--steam-faint);opacity:1}
  .ml-block-form input[type="email"]:hover{border-color:var(--steam-faint)}
  /* Same shape and weight as .btn--fire, so the two read as one system. */
  .ml-block-form button.primary{
    flex:0 0 auto;
    background:var(--ember);
    color:var(--ink-deep);
    border:1px solid var(--ember);
    border-radius:2px;
    font-family:var(--body);
    font-size:.85rem;font-weight:600;
    letter-spacing:.09em;text-transform:uppercase;
    padding:1.05em 2em;
    min-height:48px;
    cursor:pointer;
    transition:background .25s ease,border-color .25s ease;
  }
  .ml-block-form button.primary:hover{background:#F0A557;border-color:#F0A557}
  .row-success p{color:var(--steam-dim);font-size:.95rem;max-width:var(--measure)}
  /* Stack below the point where the field stops being comfortably wide. */
  @media(max-width:560px){
    .ml-block-form{flex-direction:column}
    .ml-field-email{flex:1 1 auto}
    .ml-block-form button.primary{width:100%}
  }

  /* A numbered sequence where the number is a marker, not decoration. The
     .step pattern uses a 2rem display-serif numeral, which is right for three
     big cards and wrong for three lines of instruction. */
  .how{list-style:none;counter-reset:s;margin:0;padding:0;display:flex;flex-direction:column;gap:1rem}
  .how li{counter-increment:s;position:relative;padding-left:2.1rem;font-size:.95rem;line-height:1.65;color:var(--steam-dim);max-width:var(--measure)}
  .how li::before{content:counter(s);position:absolute;left:0;top:.15em;font-size:.72rem;font-weight:600;letter-spacing:.16em;color:var(--steam-faint)}
  .how b{color:var(--steam);font-weight:600}

  /* ---------- footer ---------- */
  footer{padding:3rem 0 4rem;border-top:1px solid var(--hair-soft)}
  .foot{display:flex;flex-wrap:wrap;gap:1.5rem 3rem;justify-content:space-between;align-items:flex-start}
  .foot a{color:var(--steam-dim);text-decoration:none;font-size:.85rem}
  .foot a:hover{color:var(--steam)}
  .foot__col{display:flex;flex-direction:column;gap:.5rem}

  @media(prefers-reduced-motion:reduce){
    *{animation:none!important;transition:none!important}
  }

/* ---------------------------------------------------------------------------
   Booking widget. Append to css/site.css — it uses the site's own tokens,
   so it follows the palette automatically.
   --------------------------------------------------------------------------- */

  .bk{display:flex;flex-direction:column;gap:clamp(1.6rem,3vw,2.25rem)}

  .bk__step{border:0;margin:0;padding:0;min-width:0}
  .bk__legend{padding:0;font-size:.68rem;font-weight:600;letter-spacing:.22em;
              text-transform:uppercase;color:var(--steam-faint);margin-bottom:.85rem}

  .bk__opts{display:grid;grid-template-columns:repeat(2,1fr);gap:.7rem}
  .bk__opts--3{grid-template-columns:repeat(3,1fr)}
  @media(max-width:620px){.bk__opts,.bk__opts--3{grid-template-columns:1fr}}

  .bk__opt{display:flex;flex-direction:column;gap:.3rem;text-align:left;
           padding:.95rem 1.05rem;min-height:48px;cursor:pointer;
           background:var(--ink-raised);color:var(--steam);
           border:1px solid var(--hair);border-radius:2px;
           font-family:var(--body);transition:border-color .15s,background .15s}
  .bk__opt:hover{border-color:var(--steam-faint)}
  .bk__opt.is-on{border-color:var(--ember);background:rgba(226,145,63,.10)}
  .bk__opt--wide{width:100%}

  /* The two exceptions. Secondary by TREATMENT, not by hiding: still one tap
     away, still fully readable, but visibly not the main path. One line
     instead of a stacked card, no filled background, lighter border. On a
     phone this is two short rows under three cards rather than five cards. */
  .bk__opts--alt{grid-template-columns:repeat(2,1fr);gap:.55rem;margin-top:.55rem}
  @media(max-width:620px){.bk__opts--alt{grid-template-columns:1fr}}
  .bk__opt--alt{
    flex-direction:row;align-items:baseline;flex-wrap:wrap;gap:.15rem .5rem;
    /* 44px, not less. Quieter must not mean smaller than a fingertip -- the
       audit caught this at 40px, which is below the touch-target floor. The
       secondary treatment comes from the border, background and single-line
       layout, none of which cost tap area. */
    padding:.6rem .8rem;min-height:44px;
    background:transparent;border-color:var(--hair-soft);
  }
  .bk__opt--alt .bk__opt-t{font-size:.85rem;font-weight:600}
  .bk__opt--alt .bk__opt-s{font-size:.78rem}
  .bk__opt--alt:hover{border-color:var(--steam-faint);background:rgba(237,230,220,.03)}
  /* Chosen state stays as loud as the priced options -- once it IS the
     selection, it is no longer an exception. */
  .bk__opt--alt.is-on{border-color:var(--ember);background:rgba(226,145,63,.10)}
  .bk__opt-t{font-size:.95rem;font-weight:600}
  .bk__opt-s{font-size:.82rem;color:var(--steam-dim)}
  .bk__opt.is-on .bk__opt-s{color:var(--steam)}

  .bk__cal{border:1px solid var(--hair);border-radius:2px;padding:.9rem;background:var(--ink-raised)}
  .bk__calhead{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin-bottom:.7rem}
  .bk__mon{font-family:var(--display);font-size:1.05rem}
  .bk__nav{width:44px;height:44px;display:flex;align-items:center;justify-content:center;
           background:transparent;color:var(--steam);border:1px solid var(--hair);
           border-radius:2px;cursor:pointer;font-size:1rem}
  .bk__nav:hover{border-color:var(--steam-faint)}

  .bk__dow,.bk__grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
  .bk__dow{margin-bottom:.35rem}
  .bk__dow span{text-align:center;font-size:.66rem;font-weight:600;letter-spacing:.1em;
                text-transform:uppercase;color:var(--steam-faint)}

  .bk__pad{min-height:44px}
  .bk__day{min-height:44px;max-height:56px;display:flex;align-items:center;justify-content:center;
           background:transparent;color:var(--steam);border:1px solid transparent;border-radius:2px;
           font-family:var(--body);font-size:.9rem;cursor:pointer;
           /* the peak dot below is positioned against this */
           position:relative}
  .bk__day:hover:not(:disabled){border-color:var(--ember)}
  /* three disabled states, deliberately different:
     is-past  gone
     is-off   already booked — the only one that gets struck through
     (is-na, "free but not a valid start day", was removed 2026-08-30 with the
      Friday-only rule. Every day is a valid start now, so the state was
      unreachable — selectable() is exactly spanFree(), which is tested one
      branch earlier.) */
  .bk__day.is-past{color:var(--steam-faint);opacity:.35;cursor:default}
  .bk__day.is-off{color:var(--steam-faint);opacity:.55;cursor:default;text-decoration:line-through}
  .bk__day.is-on{background:var(--ember);color:var(--ink-deep);font-weight:600}
  .bk__day.is-span{background:rgba(226,145,63,.18)}
  /* peak dates carry a dot, not a colour — the calendar already uses colour
     for chosen and for booked, and a third would be unreadable */
  .bk__day.is-peak::after{content:'';position:absolute;left:50%;bottom:5px;
    width:3px;height:3px;margin-left:-1.5px;border-radius:50%;
    background:var(--ember);opacity:.9}
  .bk__day.is-on.is-peak::after{background:var(--ink-deep);opacity:.7}
  .bk__day.is-past.is-peak::after{opacity:.3}

  .bk__hint{margin-top:.6rem;font-size:.82rem;color:var(--steam-dim)}

  .bk__sum{border:1px solid var(--hair);border-radius:2px;padding:1.1rem 1.2rem;
           background:var(--ink-deep);display:flex;flex-direction:column;gap:.7rem}
  .bk__lines{display:grid;grid-template-columns:1fr auto;gap:.35rem 1.5rem;margin:0}
  .bk__lines dt{font-size:.88rem;color:var(--steam-dim)}
  .bk__lines dd{margin:0;font-size:.88rem;text-align:right;font-variant-numeric:tabular-nums}
  .bk__total{display:flex;justify-content:space-between;align-items:baseline;
             padding-top:.7rem;border-top:1px solid var(--hair-soft);
             font-size:.78rem;letter-spacing:.16em;text-transform:uppercase;color:var(--steam-faint)}
  .bk__total b{font-family:var(--display);font-size:1.7rem;font-weight:400;color:var(--steam);
               letter-spacing:normal;text-transform:none;font-variant-numeric:tabular-nums}
  .bk__small{margin:0;font-size:.8rem;color:var(--steam-dim);line-height:1.5}

  .bk__fields{display:grid;grid-template-columns:repeat(2,1fr);gap:.8rem}
  @media(max-width:620px){.bk__fields{grid-template-columns:1fr}}
  .bk__f{display:flex;flex-direction:column;gap:.3rem;min-width:0}
  .bk__f--wide{grid-column:1/-1}
  /* Field hint. Same register as .bk__small and .bk__hint -- .8rem in
     --steam-dim -- so it reads as the site's existing quiet-note voice
     rather than a new treatment. */
  .bk__f-hint{font-size:.8rem;color:var(--steam-dim);line-height:1.45;margin-top:.1rem}
  /* "Did you mean" — a suggestion, not a warning. Deliberately quieter than
     .bk__status--error: it must not read as a rejection, because nothing has
     been rejected and the form submits either way. */
  .bk__f-sugg{margin:0;font-size:.8rem;color:var(--steam-dim);line-height:1.45}

  /* Success panel. Replaces the form rather than emptying it -- an empty form
     reads as "did that just wipe?". */
  .bk__done{
    display:flex;flex-direction:column;gap:.9rem;
    border:1px solid var(--hair);border-left:3px solid var(--ember);
    border-radius:2px;padding:clamp(1.3rem,3vw,1.9rem);
    background:var(--ink-deep);
  }
  .bk__done-h{font-family:var(--display);font-size:clamp(1.4rem,1.1rem + .9vw,1.9rem);
              margin:0;color:var(--steam);font-weight:400}
  .bk__done-h:focus-visible{outline:2px solid var(--glacier);outline-offset:4px}
  .bk__done-lede{margin:0;font-size:1rem;color:var(--steam);line-height:1.55}
  .bk__done-line{margin:0;font-size:.92rem;color:var(--ember);font-weight:600;line-height:1.5}
  /* The honest line. Quieter than the rest, but present. */
  .bk__done-small{margin:0;font-size:.85rem;color:var(--steam-dim);line-height:1.5}
  .bk__done-again{align-self:flex-start;margin-top:.3rem}
  .bk__sugg-btn{
    font-family:var(--body);font-size:.8rem;font-weight:600;
    background:none;border:0;padding:0;cursor:pointer;
    color:var(--ember);text-decoration:underline;text-underline-offset:2px;
  }
  .bk__sugg-btn:hover{color:#F0A557}
  .bk__sugg-btn:focus-visible{outline:2px solid var(--glacier);outline-offset:2px;border-radius:2px}
  .bk__f span{font-size:.7rem;font-weight:600;letter-spacing:.14em;
              text-transform:uppercase;color:var(--steam-faint)}
  .bk__f input,.bk__f textarea{
    font-family:var(--body);font-size:16px;padding:.7rem .8rem;color:var(--steam);
    background:var(--ink);border:1px solid var(--hair);border-radius:2px;min-height:48px}
  .bk__f textarea{min-height:64px;resize:vertical}
  .bk__f input:focus,.bk__f textarea:focus{outline:2px solid var(--ember);outline-offset:1px;border-color:transparent}

  .bk__send{align-self:flex-start;background:var(--ember);color:var(--ink-deep);
            border:0;border-radius:2px;padding:1.05em 2.1em;min-height:48px;cursor:pointer;
            font-family:var(--body);font-size:.8rem;font-weight:600;
            letter-spacing:.14em;text-transform:uppercase}
  .bk__send:hover{background:var(--ember-deep);color:var(--steam)}
  @media(max-width:620px){.bk__send{align-self:stretch;text-align:center}}

  .bk__status{margin:0;font-size:.88rem;color:var(--steam-dim);min-height:1.2em}

  /* A blocked submit is not a footnote.
     The old style was .88rem in --steam-dim, the same weight and colour as
     every other hint on the page, sitting below the button. Dan pressed send
     with no date chosen, read "Pick a date first." as nothing happening, and
     called the widget broken. If the person who specified it cannot see the
     message, a customer has no chance.
     Measured on the tinted panel itself, not on the bare page colour: ember
     text over a 10%-ember wash reads 7.13:1 on --ink-deep, 6.54:1 on --ink
     and 5.42:1 on --ink-raised. Worst case clears 4.5:1 for body text. */
  .bk__status--error{
    color:var(--ember);
    font-size:1rem;
    font-weight:600;
    background:rgba(226,145,63,.10);
    border-left:3px solid var(--ember);
    border-radius:2px;
    padding:.7em .9em;
    margin-top:.2rem;
  }
  /* The empty field itself, so the eye lands on the cause and not only on the
     sentence describing it. */
  .bk__f input.is-invalid,
  .bk__f textarea.is-invalid{
    border-color:var(--ember);
    background:rgba(226,145,63,.07);
  }
  .bk__f input.is-invalid:focus,
  .bk__f textarea.is-invalid:focus{outline-color:var(--ember)}

  @media(prefers-reduced-motion:reduce){.bk__opt{transition:none}}

  /* ---- the waiver page ------------------------------------------------
     A long legal document. The job is readability on a phone: a real
     measure, generous line height, and clear separation between clauses so
     someone can find the one they are looking for. No boxes and no scroll
     container -- the agreement is the page. */
  .wv{max-width:62ch;display:flex;flex-direction:column;gap:1.15rem}
  .wv__h2{font-family:var(--display);font-weight:600;
          font-size:clamp(1.15rem,1rem + .5vw,1.45rem);line-height:1.3;
          margin:1.6rem 0 .2rem;color:var(--steam)}
  .wv .wv__h2:first-child{margin-top:0}
  .wv__h3{font-family:var(--body);font-size:.72rem;font-weight:700;
          letter-spacing:.14em;text-transform:uppercase;
          color:var(--ember);margin:1.2rem 0 -.35rem}
  .wv p{margin:0;font-size:.95rem;line-height:1.68;color:var(--steam-dim)}
  .wv__strong{color:var(--steam)}
  .wv__list{margin:.1rem 0 0;padding-left:1.3rem;display:flex;
            flex-direction:column;gap:.6rem}
  .wv__list li{font-size:.95rem;line-height:1.68;color:var(--steam-dim)}

  /* The booking reference: shown, never editable. It is not an input because
     it is not the signer's to change -- an editable field invites someone to
     "fix" it and detach their signature from their booking. */
  .bk__ref{display:flex;flex-direction:column;gap:.3rem;
           padding:.9rem 1.1rem;border:1px solid var(--hair);
           background:var(--ink-raised);border-radius:4px}
  .bk__ref-val{margin:0;font-family:var(--body);font-size:.95rem;
               font-weight:600;color:var(--ember);word-break:break-all}
  .bk__ref-val--none{color:var(--steam-dim);font-weight:400}

  .wv__form{display:flex;flex-direction:column;gap:1.1rem;max-width:46rem}
  .wv__check{display:flex;gap:.7rem;align-items:flex-start;cursor:pointer;
             font-size:.95rem;line-height:1.55;color:var(--steam)}
  /* 20px plus the gap clears the 44px touch floor via the label's own height. */
  .wv__check input{width:20px;height:20px;margin:.15rem 0 0;flex:0 0 auto;
                   accent-color:var(--ember);cursor:pointer}
  .wv__check input.is-invalid{outline:2px solid var(--ember);outline-offset:3px}

  @media (max-width:820px){
    .wv{max-width:none}
    .wv p,.wv__list li{font-size:1rem}
  }
