/* ==========================================================================
   Francis Jordan Hot Tub Co. — design system

   Direction: the sleek, image-led structure of the client's reference site, but
   WARM rather than cold. Both Paul and Antonio asked explicitly for warmth, so
   the ground is bone (#FAF9F6) rather than pure white, with sand and sage as
   the accents. Product images sit on bone, never on clinical white.

   Collection grids are 2-across on desktop. That is deliberate: it divides both
   8 hot tubs and 4 swim spas with no orphan card, and it is the direct answer to
   the client's stated hatred of hot tub sites where tubs are "lined up like used
   cars".
   ========================================================================== */

/* ---------- tokens ---------- */
:root {
  --ink:        #1F2124;
  --near-black: #0E0E0E;
  --bone:       #FAF9F6;
  --bone-deep:  #F2EFEA;
  --line:       #E3E1DC;
  --action:     #1B4D8F;
  --action-dk:  #14396B;
  --sand:       #D4B896;
  --sage:       #8B9A7E;
  --terracotta: #B4553C;   /* deadlines only */
  --save:       #2E7D52;   /* savings figures only */

  --muted:      #5C5F63;

  /* 8pt spacing scale */
  --s1: 0.5rem;  --s2: 1rem;    --s3: 1.5rem;  --s4: 2rem;
  --s5: 3rem;    --s6: 4rem;    --s7: 6rem;    --s8: 8rem;

  --wrap: 1240px;
  --wrap-narrow: 720px;

  --font-display: 'Chivo', 'Helvetica Neue', Arial, sans-serif;
  --font-body:    'Poppins', 'Helvetica Neue', Arial, sans-serif;
  --font-accent:  'Newsreader', Georgia, serif;

  --radius: 4px;
  --radius-lg: 20px;

  /* Sticky header height. Hard-coded as 76px in three separate places before,
     which is how the nav drawer ended up anchored 1px above the header's actual
     bottom edge (min-height 76 + a 1px border = 77). One number, one source. */
  --header-h: 77px;

  --shadow-card: 0 1px 2px rgba(31,33,36,.04), 0 8px 24px rgba(31,33,36,.06);
  --shadow-lift: 0 2px 4px rgba(31,33,36,.06), 0 16px 40px rgba(31,33,36,.10);

  --ease: cubic-bezier(.22,.61,.36,1);
}

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

/* The UA stylesheet gives [hidden] display:none at the weakest possible weight, so
   ANY class rule that sets display wins and the element stays visible. The spa
   finder hides cards with the hidden attribute and .product-card sets display:flex,
   which silently defeated the entire filter. Restate it with force. */
[hidden] { display: none !important; }

/* scroll-padding-top is what stops the sticky header from covering the target of
   every in-page jump. Without it the header (77px) sits directly on top of
   whatever the anchor pointed at: the skip link landed on #main with the first
   heading hidden underneath, and #book on the consultation page put the form's
   own heading behind the bar. It applies to the smooth scroll AND to a hard load
   of a /page/#anchor URL, which is the case a manual test usually misses. */
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + var(--s2));
}

body {
  margin: 0;
  background: var(--bone);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.65;
  font-weight: 400;
  overflow-x: hidden;          /* page body must never scroll horizontally */
}

img, svg, video { max-width: 100%; height: auto; display: block; }

a { color: var(--action); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 3px solid var(--action);
  outline-offset: 2px;
}

.skip-link {
  position: absolute; left: -9999px; top: 0;
  background: var(--near-black); color: var(--bone);
  padding: var(--s2) var(--s3); z-index: 200;
}
.skip-link:focus { left: 0; }

/* ---------- type ---------- */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.12;
  margin: 0 0 var(--s2);
  letter-spacing: -0.01em;
}

h1 { font-size: clamp(2rem, 5vw, 3.25rem); }
h2 { font-size: clamp(1.6rem, 3.4vw, 2.4rem); }
h3 { font-size: clamp(1.15rem, 2vw, 1.4rem); }

p { margin: 0 0 var(--s2); }
p:last-child { margin-bottom: 0; }

/* Wide-tracked uppercase display. The reference site gets its width largely from
   the typeface itself, so a substitute needs the tracking added deliberately. */
.display-caps {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.10em;
  font-weight: 600;
}
.eyebrow {
  font-family: var(--font-accent);
  font-style: italic;
  font-size: 0.95rem;
  color: var(--muted);
  letter-spacing: 0.01em;
  margin: 0 0 var(--s1);
}
.eyebrow-caps {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--muted);
  margin: 0 0 var(--s2);
}
.lede { font-size: 1.15rem; color: var(--muted); }

/* ---------- layout ---------- */
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--s3); }
.wrap-narrow { width: 100%; max-width: var(--wrap-narrow); margin-inline: auto; padding-inline: var(--s3); }

.section { padding-block: var(--s7); }
.section--tight { padding-block: var(--s5); }
.section--bone-deep { background: var(--bone-deep); }

/* Dark sections. Colour rules are scoped to a copy column rather than applied to
   the section, because a section-level `h2 { color:#fff }` out-specifies a nested
   card's own colour and form inputs inherit it, which makes card text and typed
   input invisible. */
.section--dark { background: var(--near-black); }
.section--dark .copy,
.section--dark .copy h2,
.section--dark .copy h3,
.section--dark .copy p,
.section--dark .copy .eyebrow { color: var(--bone); }
.section--dark .copy .eyebrow,
.section--dark .copy .lede { color: #B9BABC; }

.section-head { max-width: 46rem; margin-bottom: var(--s5); }
.section-head--center { margin-inline: auto; text-align: center; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5em;
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  padding: 1.05rem 2rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  text-align: center;
  transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease), transform .2s var(--ease);
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }

.btn--primary { background: var(--near-black); color: var(--bone); }
.btn--primary:hover { background: var(--ink); }

.btn--action { background: var(--action); color: #fff; }
.btn--action:hover { background: var(--action-dk); }

.btn--ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--bone); }

.btn--ghost-light { background: transparent; color: var(--bone); border-color: rgba(250,249,246,.55); }
.btn--ghost-light:hover { background: var(--bone); color: var(--ink); }

.btn--block { width: 100%; }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--s2); }

/* ---------- header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: rgba(250,249,246,.94);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.site-header__bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3);
  min-height: 76px;
}
.brand { display: flex; align-items: center; gap: .6rem; flex-shrink: 0; }
.brand__mark {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.02rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  line-height: 1.1;
}
.brand__mark span { display: block; font-size: 0.62rem; letter-spacing: 0.22em; color: var(--muted); font-weight: 500; }
.brand:hover { text-decoration: none; }

/* Logo. Height-driven so the intrinsic width/height attributes still describe the
   aspect ratio and nothing shifts while it loads. Sizes live here, never inline:
   an inline size out-specifies every responsive rule below. */
.brand__logo { height: 44px; width: auto; display: block; }
.brand__logo--footer { height: 52px; margin-bottom: .85rem; }
.popup__logo { height: 108px; width: auto; display: block; margin: 0 auto 1rem; }

/* "See all questions" under each FAQ block. Sized in a class, not inline. */
.faq__all { margin-top: var(--s3); font-family: var(--font-display); font-size: 0.82rem;
            text-transform: uppercase; letter-spacing: 0.1em; }
.faq__all a { display: inline-block; padding: .75rem 0; min-height: 44px; }
/* .75rem takes this from 39px to 44px, clearing the Apple/Material tap-target
   recommendation rather than only the 24px WCAG floor. It appears on 37 pages,
   so it was the single biggest new entry in the mobile audit warn list. */

/* "Do not see your city?" under the areas chips. */
.areas-note { margin-top: var(--s3); color: var(--muted); font-size: 0.95rem; max-width: 34rem; }
.section--bone-deep .areas-note { color: var(--muted); }
@media (max-width: 560px) {
  /* The wordmark stays; the emblem shrinks. Dropping the emblem instead would
     leave the header with no mark at exactly the width where it is most needed. */
  .brand__logo { height: 36px; }
}


.nav { display: flex; align-items: center; gap: var(--s4); }
.nav a {
  font-family: var(--font-display);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 500;
  color: var(--ink);
  padding-block: .4rem;
  /* Two-word labels ("Hot Tubs", "How It Works") were breaking across two lines
     inside their own link in the narrow-desktop band, which made those links 49px
     tall against 31px for the single-word ones and left the row visibly ragged.
     The nav itself never wrapped; only the text inside each link did. */
  white-space: nowrap;
}
.nav a:hover, .nav a[aria-current="page"] { text-decoration: none; box-shadow: inset 0 -2px 0 var(--sand); }

.header__actions { display: flex; align-items: center; gap: var(--s2); }
.header__phone {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  color: var(--ink);
  white-space: nowrap;
}

.nav-toggle {
  display: none;
  background: none; border: 1px solid var(--line); border-radius: var(--radius);
  padding: .6rem .8rem; cursor: pointer;
}
.nav-toggle span { display: block; width: 20px; height: 2px; background: var(--ink); margin: 4px 0; }

@media (max-width: 900px) {
  .nav-toggle { display: block; }
  .nav {
    position: fixed; inset: var(--header-h) 0 auto 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--bone);
    border-bottom: 1px solid var(--line);
    padding: var(--s2) var(--s3) var(--s4);
    transform: translateY(-120%);
    transition: transform .3s var(--ease);
    /* vh first as the fallback, dvh second so it wins where supported. 100vh on
       a phone measures the viewport WITHOUT the browser chrome subtracted, so a
       vh-only cap makes the drawer taller than the visible screen and the last
       links sit under the URL bar with nothing to scroll. Same pairing the quiz
       panel already uses. */
    max-height: calc(100vh - var(--header-h));
    max-height: calc(100dvh - var(--header-h));
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* The drawer is a fixed overlay: without this a touch drag that starts on it
       chains through to the page underneath once the drawer hits its end. */
    overscroll-behavior: contain;
  }
  .nav[data-open="true"] { transform: translateY(0); }
  .nav a { padding: var(--s2) 0; border-bottom: 1px solid var(--line); font-size: 0.9rem; }
  .header__phone { font-size: 0.85rem; }
}

/* ---------- hero ---------- */
/* Cinematic single-subject presentation, modelled on the reference site's HOME
   page treatment, not its collection grid. */
.hero { position: relative; background: var(--near-black); color: var(--bone); overflow: hidden; }
.hero__media { position: absolute; inset: 0; }
.hero__media img { width: 100%; height: 100%; object-fit: cover; }
.hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(100deg, rgba(14,14,14,.86) 0%, rgba(14,14,14,.62) 45%, rgba(14,14,14,.22) 100%);
}
.hero__inner { position: relative; padding-block: clamp(var(--s7), 14vh, var(--s8)); }
.hero__copy { max-width: 40rem; }
.hero h1 { color: var(--bone); }
.hero__eyebrow {
  font-family: var(--font-display);
  text-transform: uppercase; letter-spacing: 0.2em;
  font-size: 0.72rem; font-weight: 500;
  color: var(--sand);
  margin-bottom: var(--s2);
}
.hero__lede { color: #D8D6D2; font-size: 1.15rem; margin-bottom: var(--s4); max-width: 34rem; }

/* ---------- trust strip ---------- */
.trust-strip { background: var(--bone-deep); border-block: 1px solid var(--line); }
.trust-strip__grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--s3);
  padding-block: var(--s4);
}
.trust-item { text-align: center; }
.trust-item__figure {
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 2.6vw, 1.9rem);
  font-weight: 700;
  color: var(--ink);
  line-height: 1;
  margin-bottom: .35rem;
}
.trust-item__label {
  font-size: 0.82rem; color: var(--muted); line-height: 1.4;
}
@media (max-width: 780px) {
  .trust-strip__grid { grid-template-columns: repeat(2, 1fr); gap: var(--s3) var(--s2); }
}

/* ---------- product cards ---------- */
/* 2-across on desktop. Divides 8 and 4 exactly, so no row is ever left with an
   orphan card, and the cards stay large enough to read as a showcase. */
.product-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s5) var(--s4);
}
@media (max-width: 820px) { .product-grid { grid-template-columns: 1fr; gap: var(--s4); } }

.product-card {
  display: flex; flex-direction: column;
  background: var(--bone);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  color: var(--ink);              /* explicit, so a dark section cannot bleed in */
  transition: box-shadow .25s var(--ease), transform .25s var(--ease);
}
.product-card:hover { box-shadow: var(--shadow-lift); transform: translateY(-3px); text-decoration: none; }
.product-card__media {
  background: var(--bone-deep);
  aspect-ratio: 4 / 3;
  display: flex; align-items: center; justify-content: center;
  padding: var(--s3);
  /* A flex item defaults to min-height:auto, which lets the image's intrinsic
     height override aspect-ratio. Two cards in the same row then got different
     image box heights, which also opened a dead gap above the shorter card's CTA
     because .product-card__foot uses margin-top:auto. */
  flex: 0 0 auto;
  min-height: 0;
}
.product-card__media img {
  width: 100%; height: 100%;
  object-fit: contain;            /* never crop a product shot */
  max-height: 100%;
}
.product-card__body { padding: var(--s3); display: flex; flex-direction: column; flex: 1; }
.product-card__sku {
  font-family: var(--font-display);
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--muted); margin-bottom: .5rem;
}
.product-card__name { font-size: 1.3rem; margin-bottom: var(--s1); color: var(--ink); }
.product-card__desc { font-size: 0.95rem; color: var(--muted); margin-bottom: var(--s2); }
.product-card__foot { margin-top: auto; }
.product-card__cta {
  font-family: var(--font-display);
  font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.13em;
  font-weight: 600; color: var(--action);
}

/* Spec chips carry the weight an extra photograph would. */
.chips { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: var(--s2); list-style: none; padding: 0; }
.chip {
  font-family: var(--font-display);
  font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase;
  background: var(--bone-deep); border: 1px solid var(--line);
  color: var(--muted);
  padding: .32rem .6rem; border-radius: 100px;
  white-space: nowrap;
}
.chip--accent { background: rgba(212,184,150,.28); border-color: rgba(212,184,150,.6); color: #6B5333; }

/* ---------- product detail ---------- */
.pdp-hero { display: grid; grid-template-columns: 1.15fr 1fr; gap: var(--s6); align-items: start; }
@media (max-width: 980px) { .pdp-hero { grid-template-columns: 1fr; gap: var(--s4); } }

.gallery__main {
  background: var(--bone-deep);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  aspect-ratio: 4 / 3;
  display: flex; align-items: center; justify-content: center;
  padding: var(--s4);
  margin-bottom: var(--s2);
}
.gallery__main img { max-height: 100%; object-fit: contain; }
.gallery__thumbs { display: flex; gap: var(--s1); flex-wrap: wrap; }
.gallery__thumb {
  width: 84px; height: 68px;
  background: var(--bone-deep);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .3rem; cursor: pointer;
}
.gallery__thumb img { width: 100%; height: 100%; object-fit: contain; }
.gallery__thumb[aria-selected="true"] { border-color: var(--ink); box-shadow: 0 0 0 1px var(--ink); }

.pdp__sku {
  font-family: var(--font-display);
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--muted); margin-bottom: var(--s1);
}
.pdp__summary { font-size: 1.05rem; color: var(--muted); margin-bottom: var(--s3); }

.offer-card {
  background: var(--bone-deep);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s3);
  margin-bottom: var(--s3);
}
.offer-card__label {
  font-family: var(--font-display);
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--muted); margin-bottom: .5rem;
}
.offer-card__figure { font-family: var(--font-display); font-size: 1.5rem; font-weight: 700; color: var(--save); line-height: 1.1; }
.offer-card__note { font-size: 0.85rem; color: var(--muted); margin-top: .5rem; }

/* spec table: renders only populated rows, since the two product lines populate
   different fields (hot tubs bury pump/electrical data in feature prose) */
.spec-table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
.spec-table th, .spec-table td { text-align: left; padding: .8rem 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.spec-table th {
  font-family: var(--font-display);
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--muted); font-weight: 600; width: 42%;
}
.spec-table td { white-space: pre-line; }

/* ---------- comparison table (home, about) ----------
   A mark and a few words per cell, never a sentence. The first build of this
   table put a full sentence in every cell and ran taller than the screen twice
   over. Scannable beats complete here: the sentences that were cut all live in
   body copy elsewhere on the page.

   The mark sits ABOVE the words rather than beside them, so a reader can run
   down a column and read the answer without reading anything at all.

   The wrapper scrolls on its own rather than letting the table push the page
   wide: body has an overflow-x guard, so an over-wide table would otherwise be
   clipped instead of scrollable. It is focusable so a keyboard user can reach
   that scroll, which is why it also carries a group role and a label. */
.compare-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.compare-scroll:focus-visible { outline: 2px solid var(--action); outline-offset: 4px; }
.compare {
  width: 100%; min-width: 680px;
  border-collapse: separate; border-spacing: 0;
  font-size: 0.95rem; line-height: 1.4;
}
.compare__corner { width: 14rem; background: transparent; border: 0; }
/* The three answer columns are centred, headers included, so the marks stack
   into three clean vertical lines a reader can run down. Only the row label on
   the left stays flush, because it is read as a list rather than scanned. */
.compare__head {
  font-family: var(--font-display);
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.12em;
  font-weight: 600; color: var(--muted); text-align: center;
  padding: .8rem var(--s2); vertical-align: bottom;
  border-bottom: 1px solid var(--line);
}
.compare__label {
  font-family: var(--font-display);
  font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.1em;
  font-weight: 600; color: var(--ink); text-align: left;
  width: 14rem; padding: var(--s2) var(--s2) var(--s2) 0;
  border-bottom: 1px solid var(--line); vertical-align: middle;
}
.compare__cell {
  padding: var(--s2); vertical-align: top; color: var(--muted);
  border-bottom: 1px solid var(--line); text-align: center;
}
.compare__mark {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  margin: 0 auto .35rem;
}
.compare__mark--yes  { background: rgba(46, 125, 82, .12);  color: var(--save); }
.compare__mark--no   { background: rgba(180, 85, 60, .12);  color: var(--terracotta); }
.compare__mark--part { background: rgba(92, 95, 99, .10);   color: var(--muted); }
.compare__mark .icon { display: block; }
.compare__text { display: block; }
/* Our column is the one the reader is being asked to judge, so it is set in the
   ink colour on a sand wash while the other two stay muted. The wash runs the
   full height of the column, header included, which is what makes it read as one
   column rather than a run of highlighted cells. */
.compare__head.compare--us { color: var(--ink); background: rgba(212, 184, 150, .34); border-bottom-color: var(--sand); }
.compare__cell.compare--us { color: var(--ink); background: rgba(212, 184, 150, .16); font-weight: 500; }
.compare tbody tr:last-child .compare--us { border-bottom-color: var(--sand); }
.compare__note {
  font-size: 0.85rem; color: var(--muted);
  margin-top: var(--s3); max-width: 46rem;
}

/* Under 820px the table restacks: one block per row, three marks in a row under
   the row label. The marks are what make this work on a phone, because three
   short answers side by side still fit where three sentences never could. */
@media (max-width: 820px) {
  .compare, .compare tbody, .compare tr { display: block; }
  .compare { min-width: 0; }
  .compare thead { display: none; }
  .compare tbody tr {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s2);
    padding-bottom: var(--s3); margin-bottom: var(--s3);
    border-bottom: 1px solid var(--line);
  }
  .compare tbody tr:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
  .compare__label {
    display: block; grid-column: 1 / -1;
    width: auto; padding: 0; border-bottom: 0;
  }
  .compare__cell {
    display: block; padding: 0; border-bottom: 0;
    font-size: 0.86rem; text-align: center;
  }
  .compare__cell.compare--us { background: transparent; }
  .compare__cell::before {
    content: attr(data-col);
    display: block;
    font-family: var(--font-display);
    font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.1em;
    font-weight: 600; color: var(--muted); margin-bottom: .45rem;
    /* Three column labels of very different lengths would stagger the marks
       under them, so the label block is a fixed two lines tall. 2.8em is two
       lines at the inherited 1.4 line-height; anything less lets a wrapped
       label push its mark down a few pixels out of the row. */
    min-height: 2.8em;
  }
  .compare__cell.compare--us::before { color: var(--ink); }
}

/* Text that exists only for a screen reader. The comparison marks are meaning,
   not decoration, so each one carries its word here. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.feature-cols { columns: 2; column-gap: var(--s4); }
@media (max-width: 700px) { .feature-cols { columns: 1; } }
.feature-list { list-style: none; padding: 0; margin: 0; }
.feature-list li {
  position: relative; padding-left: 1.4rem; margin-bottom: .55rem;
  font-size: 0.95rem; break-inside: avoid;
}
.feature-list li::before {
  content: ""; position: absolute; left: 0; top: .55em;
  width: 7px; height: 7px; border-radius: 50%; background: var(--sage);
}

/* ---------- collection cards (home) ---------- */
.collection-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s4); }
@media (max-width: 820px) { .collection-cards { grid-template-columns: 1fr; } }
.collection-card {
  position: relative; overflow: hidden;
  border-radius: var(--radius);
  min-height: 380px;
  display: flex; align-items: flex-end;
  color: var(--bone);
  background: var(--near-black);
}
.collection-card img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform .6s var(--ease); }
.collection-card:hover img { transform: scale(1.04); }
.collection-card:hover { text-decoration: none; }
.collection-card__scrim { position: absolute; inset: 0; background: linear-gradient(to top, rgba(14,14,14,.88) 0%, rgba(14,14,14,.34) 55%, rgba(14,14,14,.10) 100%); }
.collection-card__body { position: relative; padding: var(--s4); width: 100%; }
.collection-card__body h3 { color: var(--bone); margin-bottom: .35rem; }
.collection-card__count { font-size: .88rem; color: #CFCDC9; }

/* ---------- steps ---------- */
.steps { counter-reset: step; display: grid; gap: var(--s4); grid-template-columns: repeat(3, 1fr); }
@media (max-width: 860px) { .steps { grid-template-columns: 1fr; } }
.step { counter-increment: step; }
.step__num {
  font-family: var(--font-display);
  font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--sand); margin-bottom: var(--s1);
}
.step__num::before { content: "Step " counter(step); }
.step h3 { font-size: 1.15rem; }

/* ---------- forms ---------- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2) var(--s3); }
@media (max-width: 620px) { .form-grid { grid-template-columns: 1fr; } }
.field { display: flex; flex-direction: column; }
.field--full { grid-column: 1 / -1; }
.field label {
  font-family: var(--font-display);
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.13em;
  font-weight: 600; color: var(--muted); margin-bottom: .4rem;
}
.field .req { color: var(--terracotta); }

/* 16px minimum. Anything smaller makes iOS zoom on focus and never zoom back. */
.field input, .field select, .field textarea {
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--ink);
  background: transparent;
  border: 0; border-bottom: 1px solid var(--line);
  border-radius: 0;
  padding: .65rem 0;
  width: 100%;
  transition: border-color .2s var(--ease);
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-bottom-color: var(--action); border-bottom-width: 2px;
}
.field select { appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%); background-position: calc(100% - 14px) 1.25rem, calc(100% - 9px) 1.25rem; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
.field textarea { border: 1px solid var(--line); padding: .7rem; resize: vertical; min-height: 110px; }

.consent { display: flex; gap: .7rem; align-items: flex-start; grid-column: 1 / -1; }
/* 24px: an 18px checkbox is under the comfortable touch target on mobile, and this
   one gates SMS consent so a mis-tap has real consequences. */
.consent input { width: 24px; height: 24px; flex-shrink: 0; margin-top: .15rem; }
.consent label { font-size: 0.78rem; line-height: 1.5; color: var(--muted); text-transform: none; letter-spacing: 0; font-family: var(--font-body); font-weight: 400; }

.form-note { font-size: 0.78rem; color: var(--muted); }
.form-status { font-size: 0.9rem; padding: var(--s2); border-radius: var(--radius); margin-top: var(--s2); display: none; }
.form-status[data-state="ok"]   { display: block; background: rgba(46,125,82,.10); border: 1px solid rgba(46,125,82,.35); color: #1F5C3B; }
.form-status[data-state="err"]  { display: block; background: rgba(180,85,60,.10); border: 1px solid rgba(180,85,60,.35); color: #8A3D28; }

/* dark form panel: give inputs an explicit colour so they cannot inherit a
   section-level light colour and render invisible text */
.form-panel--dark { background: var(--near-black); padding: var(--s4); border-radius: var(--radius); }
.form-panel--dark .field label { color: #B9BABC; }
.form-panel--dark .field input,
.form-panel--dark .field select,
.form-panel--dark .field textarea { color: var(--bone); border-bottom-color: rgba(250,249,246,.32); }
.form-panel--dark .field textarea { border-color: rgba(250,249,246,.32); }
.form-panel--dark .consent label,
.form-panel--dark .form-note { color: #A9AAAC; }
.form-panel--dark .field select { background-image: linear-gradient(45deg, transparent 50%, #A9AAAC 50%), linear-gradient(135deg, #A9AAAC 50%, transparent 50%); }

/* ---------- exit offer popup ---------- */
/* 8s OR exit intent, whichever first, once per visitor ever. */
.popup-overlay {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(0,0,0,.75);
  display: flex; align-items: center; justify-content: center;
  padding: var(--s3);
  opacity: 0; visibility: hidden;
  transition: opacity .24s var(--ease), visibility .24s var(--ease);
  overflow-y: auto;
}
.popup-overlay[data-open="true"] { opacity: 1; visibility: visible; }
.popup {
  position: relative;
  width: 100%; max-width: 925px;
  background: var(--bone);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: grid; grid-template-columns: 0.4fr 0.8fr;
  transform: translateY(10px) scale(.99);
  transition: transform .24s var(--ease);
  margin: auto;
}
.popup-overlay[data-open="true"] .popup { transform: none; }
@media (max-width: 780px) { .popup { grid-template-columns: 1fr; } }

.popup__aside { background: var(--near-black); color: var(--bone); padding: var(--s4); display: flex; flex-direction: column; justify-content: center; gap: var(--s2); }
.popup__aside .eyebrow-caps { color: var(--sand); }
.popup__aside h2 { color: var(--bone); font-size: 1.45rem; }
.popup__aside p { font-size: 0.88rem; color: #B9BABC; }
@media (max-width: 780px) { .popup__aside { padding: var(--s3); } .popup__aside p { display: none; } }

.popup__main { padding: var(--s4); }
@media (max-width: 780px) { .popup__main { padding: var(--s3); } }
.popup__main h3 { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em; font-size: 1.35rem; }
.popup__sub { font-family: var(--font-accent); font-style: italic; font-size: 1rem; color: var(--muted); max-width: 30rem; }

.popup__close {
  position: absolute; top: .7rem; right: .7rem; z-index: 2;
  width: 38px; height: 38px;
  background: var(--bone); border: 1px solid var(--line); border-radius: var(--radius);
  cursor: pointer; font-size: 1.1rem; line-height: 1; color: var(--ink);
  display: flex; align-items: center; justify-content: center;
}
.popup__close:hover { background: var(--bone-deep); }

/* ---------- footer ---------- */
.site-footer { background: var(--near-black); color: #B9BABC; padding-block: var(--s6) var(--s4); font-size: 0.92rem; }
.site-footer a { color: var(--bone); }
.footer__grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1.2fr; gap: var(--s4); margin-bottom: var(--s5); }
@media (max-width: 900px) { .footer__grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer__grid { grid-template-columns: 1fr; } }
.footer__head {
  font-family: var(--font-display);
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--bone); margin-bottom: var(--s2); font-weight: 600;
}
.footer__list { list-style: none; margin: 0; padding: 0; }
.footer__list li { margin-bottom: .5rem; }
.footer__brand .brand__mark { color: var(--bone); }
.footer__brand .brand__mark span { color: #8A8B8D; }
.footer__legal {
  border-top: 1px solid rgba(250,249,246,.14);
  padding-top: var(--s3);
  display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3);
  justify-content: space-between;
  font-size: 0.78rem; color: #8A8B8D;
}
.footer__areas { font-size: 0.82rem; line-height: 1.7; color: #8A8B8D; }

/* ---------- misc ---------- */
.breadcrumb { font-size: 0.8rem; color: var(--muted); padding-block: var(--s3) 0; }
.breadcrumb a { color: var(--muted); }
.breadcrumb span { margin-inline: .45rem; opacity: .6; }

.prose { max-width: 42rem; }
.prose h2 { margin-top: var(--s5); }
.prose h3 { margin-top: var(--s4); }
.prose ul { padding-left: 1.2rem; }
.prose li { margin-bottom: .5rem; }

.faq { border-top: 1px solid var(--line); }
.faq__item { border-bottom: 1px solid var(--line); }
.faq__q {
  width: 100%; text-align: left; background: none; border: 0; cursor: pointer;
  font-family: var(--font-display); font-size: 1.02rem; font-weight: 600; color: var(--ink);
  padding: var(--s3) 2.5rem var(--s3) 0; position: relative;
}
.faq__q::after {
  content: "+"; position: absolute; right: .4rem; top: 50%; transform: translateY(-50%);
  font-size: 1.4rem; font-weight: 400; color: var(--muted);
}
.faq__item[data-open="true"] .faq__q::after { content: "\2013"; }
.faq__a { display: none; padding-bottom: var(--s3); color: var(--muted); max-width: 46rem; }
.faq__item[data-open="true"] .faq__a { display: block; }

.callout {
  border-left: 3px solid var(--sand);
  background: var(--bone-deep);
  padding: var(--s3);
  border-radius: 0 var(--radius) var(--radius) 0;
}

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s6); align-items: center; }
@media (max-width: 900px) { .two-col { grid-template-columns: 1fr; gap: var(--s4); } }

.media-frame { border-radius: var(--radius); overflow: hidden; background: var(--bone-deep); }
.media-frame img { width: 100%; }

/* wide content must scroll inside its own container, never the page body */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.placeholder-flag {
  background: rgba(180,85,60,.10);
  border: 1px dashed rgba(180,85,60,.5);
  color: #8A3D28;
  font-size: .78rem;
  padding: .2rem .5rem;
  border-radius: var(--radius);
  display: inline-block;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  html { scroll-behavior: auto; }
  .collection-card:hover img { transform: none; }
}

/* ==========================================================================
   Post-audit fixes
   ========================================================================== */

/* Spa finder facet row. Previously an inline repeat(4,1fr) on the element, which
   out-specified the responsive rule and produced four 68px-wide selects on a phone. */
.finder-facets { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s2) var(--s3); margin-bottom: var(--s3); }
@media (max-width: 1000px) { .finder-facets { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  { .finder-facets { grid-template-columns: 1fr; } }

/* Header CTA was wrapping to three clipped lines in the narrow desktop band. */
.header__actions .btn { white-space: nowrap; }
/* The narrow-desktop band, where the nav is still a row rather than the drawer.
   Adding FAQ as a 7th item on 2026-08-28 made the row wrap onto two lines from
   940px all the way up to about 1250px, while .site-header stayed 77px tall, so
   the second line escaped the header. The band used to stop at 1080px and only
   tightened enough for six items. Two steps rather than one, because a single
   tight setting applied up to 1259px shrinks the type more than it needs to on
   the wider end. If an eighth nav item is ever added, re-measure: these numbers
   are fitted to seven. */
@media (max-width: 1259px) and (min-width: 901px) {
  .nav { gap: var(--s3); }
  .nav a { font-size: 0.72rem; letter-spacing: 0.08em; }
}
@media (max-width: 1140px) and (min-width: 901px) {
  .nav { gap: var(--s2); }
  .nav a { font-size: 0.68rem; letter-spacing: 0.06em; }
}
@media (max-width: 900px) {
  /* On mobile the header keeps the phone and the menu; the booking CTA is already
     repeated in-page, so hiding it here buys back the width that clipped it. */
  .header__actions .btn { display: none; }
}

/* On mobile, put the form above the supporting copy on the consultation page: the
   first field was landing at ~1218px in an 844px viewport, below three screens of
   preamble. The form IS the offer, so it leads. */
@media (max-width: 900px) {
  #book .two-col { display: flex; flex-direction: column; }
  #book .form-panel--dark { order: -1; }
}

/* A 3px horizontal overflow was measurable at 390px. Chip rows and the spec table
   are the wide offenders, so let them scroll inside themselves. */
.chips { max-width: 100%; }
@media (max-width: 560px) {
  .spec-table th { width: 46%; }
  .trust-strip__grid { gap: var(--s2); }
}

/* Per-model document links */
.doc-links { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s3); }
.doc-link {
  display: inline-flex; align-items: center; gap: .5rem;
  font-family: var(--font-display); font-size: 0.74rem;
  text-transform: uppercase; letter-spacing: 0.1em; font-weight: 600;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .8rem 1.1rem; color: var(--ink); background: var(--bone);
}
.doc-link:hover { background: var(--bone-deep); text-decoration: none; }

/* ==========================================================================
   Visual QA fixes
   ========================================================================== */

/* .callout sat uncapped inside .wrap and ran ~134 characters per line at 1440px,
   directly above an FAQ capped near 85ch. Longest measure on the site. */
.callout { max-width: 46rem; }

/* Dark-panel input underlines composited to 2.76:1, under the 3:1 floor for
   non-text UI. These hairlines are the ONLY thing locating a field on black, so
   this is a findability problem, not a polish one. */
.form-panel--dark .field input,
.form-panel--dark .field select,
.form-panel--dark .field textarea { border-bottom-color: rgba(250,249,246,.62); }
.form-panel--dark .field textarea { border-color: rgba(250,249,246,.62); }

/* Required asterisk was terracotta on near-black at 3.96:1, under 4.5:1. Sand
   clears it comfortably and is already in the palette. */
.form-panel--dark .field .req,
.popup__main .field .req { color: var(--sand); }

/* The card CTA repeated the corporate navy twelve times per collection page,
   which is the single biggest reason the catalogue read cold. Ink keeps the
   hierarchy without painting the grid blue; the arrow still signals the affordance. */
.product-card__cta { color: var(--ink); }
.product-card:hover .product-card__cta { color: var(--action); }

/* Every page ended in roughly 900px of one flat #0E0E0E, reading as a single
   undifferentiated slab. Separate the footer from the CTA band above it. */
.site-footer { background: #141416; border-top: 1px solid rgba(250,249,246,.10); }

/* PDF links read as faint grey rectangles: var(--line) on bone is a 1.24:1
   boundary. Warm the border and add a fill so they read as buttons. */
.doc-link { background: var(--bone-deep); border-color: rgba(212,184,150,.75); }
.doc-link:hover { background: #EDE8E0; border-color: var(--sand); }

/* Mobile had no conversion affordance at all: the header CTA is hidden below
   900px and no phone number exists yet, so the drawer was pure navigation. */
.nav__cta { display: none; }
@media (max-width: 900px) {
  .nav__cta {
    display: block;
    margin-top: var(--s2);
    background: var(--action); color: #fff;
    text-align: center; border-radius: var(--radius);
    padding: 1rem; border-bottom: 0 !important;
    font-family: var(--font-display); font-size: 0.82rem;
    text-transform: uppercase; letter-spacing: 0.12em; font-weight: 600;
  }
  .nav__cta:hover { background: var(--action-dk); text-decoration: none; }
}

/* The popup ran 1134px tall in an 844px viewport, scrolling its own close button
   out of reach. Cap it and let the panel scroll instead of the dialog.

   The cap is a vh/dvh pair, matching the quiz panel. With vh alone the cap is
   computed against the viewport as if the browser chrome were not there, so on a
   real phone the panel is still ~100px taller than the screen and the bottom of
   the form is unreachable. This is the same class of bug the quiz was already
   fixed for; the popup was simply missed at the time. */
@media (max-width: 780px) {
  .popup {
    max-height: calc(100vh - 2rem);
    max-height: calc(100dvh - 2rem);
  }
  .popup__main { overflow-y: auto; overscroll-behavior: contain; }
  /* Anchored to the panel, not the viewport. position:fixed put the button at a
     fixed screen coordinate that does not track the panel's actual top edge once
     the panel is centred and capped, and on a short viewport it landed over the
     panel's content instead of its corner. */
  .popup__close { position: absolute; top: .6rem; right: .6rem; }
}

/* ==========================================================================
   Client feedback round
   ========================================================================== */

/* Trust strip figures are now full phrases ("15-Year Shell Warranty") rather than
   single words ("Decades"), because a big highlighted fragment does not tell you
   what it is. Type has to breathe across two lines without looking broken. */
.trust-item__figure {
  font-size: clamp(1.05rem, 1.55vw, 1.28rem);
  line-height: 1.22;
  letter-spacing: -0.005em;
  margin-bottom: .5rem;
  text-wrap: balance;
}
.trust-item__label { font-size: 0.8rem; line-height: 1.5; max-width: 20rem; margin-inline: auto; }
.trust-strip__grid { padding-block: var(--s5); align-items: start; }

/* The service-area chips sat on the tinted section at muted grey on bone-deep and
   effectively dissolved into the background. Give them their own ground, a real
   border and ink-weight text so the city list reads as content, not decoration. */
.areas-chips .chip {
  background: var(--bone);
  border-color: #CFCAC2;
  color: var(--ink);
  font-size: 0.76rem;
  letter-spacing: 0.04em;
  padding: .5rem .85rem;
  box-shadow: 0 1px 1px rgba(31,33,36,.03);
}
.areas-chips { gap: .55rem; }
.areas-more { margin-top: 1.25rem; font-weight: 600; }

/* Chips that lead to a city page. They have to read as clickable without turning
   the block into a wall of blue underlines, so the affordance is a colour shift
   and a lift on hover rather than link styling. The cities with no page of their
   own keep the plain .chip treatment, which is the honest signal: there is
   nothing behind them to click. */
.areas-chips .chip--link {
  display: inline-block;
  text-decoration: none;
  color: var(--ink);
  border-color: #BDB6AB;
  transition: background .15s ease, border-color .15s ease, transform .15s ease;
}
.areas-chips .chip--link:hover,
.areas-chips .chip--link:focus-visible {
  background: var(--sand, #D4B896);
  border-color: #A9906C;
  transform: translateY(-1px);
}

/* Trust strip at 390px: three of four figures wrapped and the second row sat 21px
   out of alignment, because the grid rows sized independently. Give every cell the
   same footprint and stop the phrases wrapping mid-idea. */
@media (max-width: 780px) {
  .trust-strip__grid { grid-template-columns: 1fr 1fr; gap: var(--s3) var(--s2); }
  .trust-item { display: flex; flex-direction: column; }
  .trust-item__figure { font-size: 0.95rem; min-height: 2.6em; display: flex; align-items: center; justify-content: center; }
  .trust-item__label { font-size: 0.74rem; }
}
@media (max-width: 400px) {
  .trust-item__figure { font-size: 0.9rem; }
}

/* Colour swatches. Real manufacturer samples, so they are worth showing properly. */
.swatch-group { margin-bottom: var(--s4); }
.swatches { display: flex; flex-wrap: wrap; gap: var(--s2); list-style: none; padding: 0; margin: 0; }
.swatch { width: 132px; }
.swatch img {
  width: 132px; height: 92px; object-fit: cover;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bone);
}
.swatch span {
  display: block; margin-top: .45rem;
  font-family: var(--font-display); font-size: 0.72rem;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
}
@media (max-width: 560px) {
  .swatch, .swatch img { width: 104px; }
  .swatch img { height: 74px; }
}

/* ==========================================================================
   Iconography
   Stroke marks, sized in ems of the surrounding type, inheriting currentColor.
   Deliberately NOT badges: an award-shaped badge on a company with no awards
   would be the wrong signal, so these read as quiet marks rather than seals.
   ========================================================================== */
.icon { display: block; flex-shrink: 0; }

/* Trust strip emblems. Sand ring on bone-deep so the mark reads as a considered
   element rather than a stray glyph, and so it survives the dark-section rules. */
.trust-item__mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 58px; height: 58px;
  margin: 0 auto var(--s2);
  border-radius: 50%;
  background: var(--bone);
  border: 1px solid rgba(212,184,150,.85);
  color: #6B5333;
}
.trust-strip { padding-top: var(--s1); }
@media (max-width: 780px) {
  .trust-item__mark { width: 46px; height: 46px; margin-bottom: .6rem; }
  .trust-item__mark .icon { width: 24px; height: 24px; }
}

/* Callout mark, e.g. the charity commitment on the about page */
.callout--icon { display: grid; grid-template-columns: auto 1fr; gap: var(--s3); align-items: start; }
.callout__mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: 50%;
  background: rgba(212,184,150,.22);
  border: 1px solid rgba(212,184,150,.7);
  color: #6B5333;
}
@media (max-width: 560px) {
  .callout--icon { grid-template-columns: 1fr; gap: var(--s2); }
}

/* Step marks. Sand on dark, ink-warm on light, so a steps block works either way. */
.step__mark { display: block; margin-bottom: .75rem; color: var(--sand); }
.section--dark .step__mark { color: var(--sand); }
.section:not(.section--dark) .step__mark { color: #6B5333; }

/* Check lists. A tick carries "included" better than a dot does. */
.check-list { list-style: none; padding: 0; margin: 0; }
.check-list li {
  display: grid; grid-template-columns: auto 1fr; gap: .7rem;
  align-items: start; margin-bottom: .7rem; font-size: 0.97rem;
}
.check-list .icon { color: var(--save); margin-top: .18em; }
.copy .check-list li { color: inherit; }

/* Document links: the glyph is what separates the PDF from the internal page. */
.doc-link { gap: .55rem; }
.doc-link .icon { color: #6B5333; }

/* Callout body is a single grid child so the copy is not split across columns. */
.callout__body > :first-child { margin-top: 0; }
.callout--icon .callout__body { min-width: 0; }

/* ==========================================================================
   Section background photography
   A flat near-black band is cheap but inert. A real photograph behind the copy
   does more work, provided the scrim is heavy enough that bone text stays
   comfortably readable. 0.78 measures well above the 4.5:1 floor against the
   brightest part of the sunset frame currently used.
   ========================================================================== */
.section--bg { position: relative; isolation: isolate; overflow: hidden; background: var(--near-black); }
.section--bg > .wrap { position: relative; z-index: 2; }
.section__media { position: absolute; inset: 0; z-index: 0; }
.section__media img { width: 100%; height: 100%; object-fit: cover; object-position: center 60%; }
.section__scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(180deg, rgba(14,14,14,.88) 0%, rgba(14,14,14,.72) 45%, rgba(14,14,14,.86) 100%),
    rgba(14,14,14,.34);
}
/* Inherit the dark-section colour rules so headings, ledes and step copy all read. */
.section--bg .copy,
.section--bg .copy h2,
.section--bg .copy h3,
.section--bg .copy p { color: var(--bone); }
.section--bg .copy .lede,
.section--bg .copy .eyebrow { color: #CFCDC9; }
.section--bg .step__mark { color: var(--sand); }
@media (prefers-reduced-motion: no-preference) {
  .section--bg .section__media img { transform: scale(1.02); }
}

/* Reversed two-column: media left, copy right.
   Scoped to the two-column breakpoint on purpose. Below it the grid collapses to a
   single column and the DOM order takes over, which puts the copy first, so a
   reader on a phone still gets the point before the picture. */
@media (min-width: 901px) {
  .two-col--reverse .copy { order: 2; }
  .two-col--reverse .media-frame,
  .two-col--reverse .feature-list { order: 1; }
}

/* ---------- Spa Match quiz ----------
   Reuses .popup-overlay for the backdrop and .popup__close for the dismiss
   control, but takes its own panel: one column, one question at a time, and a
   scrolling body so a tall result set never pushes the close button off screen.

   Deliberately bone all the way through. A dark head would out-specify the form
   controls nested underneath it and inherit a light colour onto inputs sitting
   on a light ground, which is the invisible-text trap the dark form panel had to
   be fixed for. Contrast comes from bone-deep bands, not from a colour flip. */
.quiz-panel {
  position: relative;
  width: 100%; max-width: 760px;
  margin: auto;
  background: var(--bone);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex; flex-direction: column;
  max-height: calc(100vh - var(--s6));
  /* dvh second, so it wins where supported. On a phone, 100vh counts the space
     behind the browser chrome, so the panel is taller than the screen and the
     close button sits under the URL bar. Kept as a pair rather than replaced:
     the vh line is the fallback. */
  max-height: calc(100dvh - var(--s6));
  transform: translateY(10px) scale(.99);
  transition: transform .24s var(--ease);
}
.popup-overlay[data-open="true"] .quiz-panel { transform: none; }

.quiz__head {
  background: var(--bone-deep);
  border-bottom: 1px solid var(--line);
  padding: var(--s4) var(--s4) var(--s3);
  flex-shrink: 0;
}
.quiz__head h2 { font-size: 1.5rem; margin-bottom: .35rem; }
.quiz__sub {
  font-family: var(--font-accent); font-style: italic;
  font-size: 0.98rem; color: var(--muted); max-width: 34rem;
}
@media (max-width: 620px) {
  .quiz__head { padding: var(--s3) var(--s3) var(--s2); }
  .quiz__head h2 { font-size: 1.2rem; }
  .quiz__sub { display: none; }
}

.quiz__progress { margin-top: var(--s3); }
.quiz__bar {
  height: 4px; border-radius: 999px;
  background: rgba(31,33,36,.12);
  overflow: hidden;
}
.quiz__bar span {
  display: block; height: 100%;
  width: var(--pct, 0%);
  background: var(--action);
  transition: width .3s var(--ease);
}
.quiz__count {
  font-family: var(--font-display);
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.13em;
  font-weight: 600; color: var(--muted); margin-top: .55rem;
}

.quiz__body { padding: var(--s4); overflow-y: auto; flex: 1 1 auto; }
@media (max-width: 620px) { .quiz__body { padding: var(--s3); } }

/* fieldset and legend, reset to behave like normal blocks */
.quiz__step { border: 0; margin: 0; padding: 0; min-width: 0; }
.quiz__q {
  display: block; float: none; width: 100%; padding: 0;
  font-family: var(--font-display); font-weight: 600;
  font-size: 1.35rem; line-height: 1.3; color: var(--ink);
  margin-bottom: .5rem;
}
@media (max-width: 620px) { .quiz__q { font-size: 1.12rem; } }
.quiz__help { font-size: 0.88rem; color: var(--muted); max-width: 38rem; line-height: 1.6; }

.quiz__options { display: flex; flex-direction: column; gap: .6rem; margin-top: var(--s3); }

.quiz__option {
  position: relative;
  display: flex; align-items: center; gap: .9rem;
  padding: 1rem 1.1rem;
  background: var(--bone);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color .16s var(--ease), background .16s var(--ease);
  /* explicit, so nothing above can bleed a light colour onto option text */
  color: var(--ink);
  font-size: 1rem; line-height: 1.4;
}
.quiz__option:hover { border-color: var(--ink); background: var(--bone-deep); }
.quiz__option:focus-within { outline: 2px solid var(--action); outline-offset: 2px; }

/* The radio stays in the accessibility tree and stays focusable. It is only
   moved out of sight, never display:none, which would take it off the tab order
   and kill native arrow-key navigation between options. */
.quiz__option input {
  position: absolute; opacity: 0;
  width: 1px; height: 1px; margin: 0; pointer-events: none;
}
.quiz__option-tick {
  order: -1; flex-shrink: 0;
  width: 22px; height: 22px; border-radius: 50%;
  border: 1.5px solid var(--line);
  background: var(--bone);
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.quiz__option input:checked ~ .quiz__option-tick {
  border-color: var(--action);
  box-shadow: inset 0 0 0 5px var(--action);
}
.quiz__option input:checked ~ .quiz__option-text { font-weight: 600; }
.quiz__option:has(input:checked) { border-color: var(--action); background: rgba(27,77,143,.05); }

.quiz__foot {
  border-top: 1px solid var(--line);
  background: var(--bone-deep);
  padding: var(--s2) var(--s4);
  display: flex; gap: var(--s2); flex-shrink: 0;
}
@media (max-width: 620px) { .quiz__foot { padding: var(--s2) var(--s3); } }
.quiz__back { padding: .7rem 1.2rem; font-size: .72rem; }
.quiz__foot:not(:has(.btn:not([hidden]))) { display: none; }

/* ---------- results ---------- */
.quiz__results h3 {
  font-family: var(--font-display); font-size: 1.35rem; margin-bottom: .4rem;
}
.quiz__results h3:focus { outline: none; }
.quiz__results > div[data-quiz-cards] { margin-top: var(--s3); }

.quiz-card {
  display: grid; grid-template-columns: 104px 1fr;
  gap: var(--s2);
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bone);
  padding: .75rem;
  margin-bottom: .7rem;
  color: var(--ink);
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.quiz-card:hover { text-decoration: none; border-color: var(--ink); box-shadow: var(--shadow-card); }

.quiz-card--top {
  grid-template-columns: 1fr;
  border-color: var(--ink);
  box-shadow: var(--shadow-card);
  padding: 0; overflow: hidden;
  margin-bottom: var(--s3);
}
.quiz-card--top .quiz-card__body { padding: var(--s3); }

.quiz-card__media {
  background: var(--bone-deep);
  border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  aspect-ratio: 4 / 3;
}
.quiz-card--top .quiz-card__media { border-radius: 0; aspect-ratio: 16 / 9; }
.quiz-card__media img { width: 100%; height: 100%; object-fit: contain; }

.quiz-card__rank {
  font-family: var(--font-display);
  font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.16em;
  font-weight: 600; color: var(--muted); margin-bottom: .3rem;
}
.quiz-card--top .quiz-card__rank { color: #6B5333; }
.quiz-card__body h4 { font-family: var(--font-display); font-size: 1.02rem; line-height: 1.3; }
.quiz-card--top .quiz-card__body h4 { font-size: 1.4rem; }
.quiz-card__sku { font-size: 0.72rem; color: var(--muted); margin-top: .15rem; }

.quiz-card__why { list-style: none; margin: .6rem 0 0; padding: 0; }
.quiz-card__why li {
  position: relative; padding-left: 1rem;
  font-size: 0.86rem; line-height: 1.55; color: var(--muted); margin-bottom: .2rem;
}
.quiz-card__why li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--sage);
}
.quiz-card__note {
  font-size: 0.78rem; color: #8A3D28; margin-top: .45rem; font-weight: 500;
}
.quiz-card__cta {
  display: inline-block; margin-top: .6rem;
  font-family: var(--font-display); font-size: 0.72rem;
  text-transform: uppercase; letter-spacing: 0.12em; font-weight: 600;
  color: var(--action);
}
/* Mobile: stack the result cards so the product photo sits ABOVE the copy at
   full card width.

   The runners up were laid out as a 104px side thumbnail, dropping to 76px on a
   phone. At that size the one thing a buyer actually wants to look at, the spa,
   is a postage stamp next to three lines of text.

   4/3, not the 16/9 the top card uses on desktop, because every product hero is
   exactly 1200x900. A 4/3 box therefore fits them edge to edge with no
   letterboxing at all, while 16/9 wastes height on bone.

   Measured at a 390px viewport (panel 342px, card content 294px): the image goes
   from 76x57 to 294x220. That is 3.9x on the long axis and 15x by area, which is
   the figure that matches what the eye reads as size. */
@media (max-width: 560px) {
  .quiz-card,
  .quiz-card--top {
    grid-template-columns: 1fr;
    gap: 0;
    padding: 0;
    overflow: hidden;
  }
  .quiz-card__media,
  .quiz-card--top .quiz-card__media {
    border-radius: 0;
    aspect-ratio: 4 / 3;
  }
  .quiz-card__body,
  .quiz-card--top .quiz-card__body { padding: var(--s3); }
  .quiz-card__why li { font-size: 0.84rem; }
}

.quiz__recap {
  font-size: 0.78rem; color: var(--muted); line-height: 1.6;
  padding: .7rem .9rem; background: var(--bone-deep);
  border-radius: var(--radius); margin-top: var(--s2);
}

.quiz__capture {
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
}
.quiz__capture h3 { font-size: 1.15rem; }
.quiz__form { margin-top: var(--s3); }
.quiz__book {
  margin-top: var(--s3); padding-top: var(--s3);
  border-top: 1px dashed var(--line);
  text-align: center;
}
.quiz__book p { font-size: 0.88rem; color: var(--muted); margin-bottom: .7rem; }

/* ==========================================================================
   Mobile audit round (2026-08-14)

   Everything below was found by scripts/mobile_audit.py, which drives a real
   Chromium with touch emulation across every built page at 320 / 360 / 390 /
   414px. None of it is visible at desktop width and none of it is visible in the
   markup, which is why it survived three passing gates.
   ========================================================================== */

/* ---------- touch targets ----------
   WCAG 2.2 AA (2.5.8) puts the floor at 24x24 CSS px; Apple HIG says 44 and
   Material says 48. Every inline text link on the site sat at 17-20px tall,
   because an <a> in a paragraph is only as tall as its line box.

   These are raised on mobile ONLY. At desktop width a 44px-tall footer link
   would put visible gaps through a tight link list for no benefit, since a
   mouse pointer is precise and a finger is not. */
@media (max-width: 900px) {
  /* Padding, not a fixed line-height. A 44px line-height would give any link
     that wraps to two lines an 88px box with the text floating in the middle of
     it, and "Delivery & Service" does wrap at 320px. Padding grows the tap box
     on one line and still behaves on two. */
  .footer__list a,
  .footer__areas a,
  .areas-more a {
    display: inline-block;
    padding-block: .7rem;   /* 14.7px text * 1.65 + 22.4px = ~46px */
  }
  /* The legal links sit two-up on one row and never wrap, so these can take a
     hard minimum instead. */
  .footer__legal a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  /* The new padding supplies the rhythm the margin used to. */
  .footer__list li { margin-bottom: 0; }

  /* Breadcrumb links were 38x17. The trail sits on one line, so the tap box is
     grown with padding rather than min-height, and the separator is nudged to
     keep the visual spacing it had. */
  .breadcrumb a {
    display: inline-block;
    padding-block: .55rem;
    min-height: 28px;
  }
  .breadcrumb { padding-block: var(--s2) 0; }

  /* The wordmark is the single most-tapped route back to the home page and its
     hit box was 29px tall inside a 77px header. */
  .brand { padding-block: .5rem; }

  /* 48x43 -> a square 46. One pixel under the floor is still under the floor. */
  .nav-toggle { min-height: 46px; min-width: 46px; }
}

/* City chips are links on every service-area page and the home page. 66x38 is
   under the 44px floor on the axis a thumb is least accurate. */
@media (max-width: 900px) {
  .areas-chips .chip--link {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
}

/* Modal dismiss controls. 38x38 is the one control a visitor MUST be able to hit
   to get their screen back, so it is the worst place on the site to be under the
   floor. Sized up on every viewport, not just mobile. */
.popup__close { width: 44px; height: 44px; }

/* Quiz Back / Start over were 38px tall from a tighter padding override. */
.quiz__back { min-height: 44px; padding-block: .55rem; }

/* Form controls on a phone. The underline field style gave inputs a 40px box and
   selects a 39px one, both under the 44px floor -- on the consultation form,
   the exit offer and the quiz capture, which are the only things on this site
   that produce a lead. Widened on mobile only; the desktop density is unchanged. */
@media (max-width: 900px) {
  .field input,
  .field select,
  .field textarea { padding-block: .85rem; }
  .field select { background-position: calc(100% - 14px) 1.45rem, calc(100% - 9px) 1.45rem; }
}

/* The small button size, previously an inline style on the spa-finder reset.
   Compact on desktop, but never below the touch and text floors on a phone. */
.btn--sm { padding: .7rem 1.2rem; font-size: 0.72rem; }
@media (max-width: 900px) {
  .btn--sm { min-height: 44px; font-size: 0.75rem; padding-block: .8rem; }
}

/* ---------- legible text on a phone ----------
   The micro-label idiom (uppercase, letterspaced, 0.68-0.74rem) reads as
   considered typography on a desktop monitor and as unreadable on a phone held
   at arm's length. Every class here carries information a visitor needs: spec
   values, model numbers, column headers, the trust-strip explanations.

   Raised to a 12px floor on mobile only, so the desktop composition the client
   signed off on is untouched. */
@media (max-width: 900px) {
  .trust-item__label { font-size: 0.8rem; }     /* 12.8px, was 11.84 */
  .chip             { font-size: 0.75rem; }     /* 12px,   was 10.88 */
  .product-card__sku,
  .pdp__sku,
  .offer-card__label,
  .footer__head,
  .eyebrow-caps,
  .hero__eyebrow,
  .compare__head,
  .spec-table th,
  .doc-link,
  .field label      { font-size: 0.75rem; }     /* 12px,   was 11.2-11.84 */
  .compare__cell::before { font-size: 0.7rem; } /* 11.2px in a 3-up label row */
  /* "Question 3 of 5" is the only thing telling someone mid-quiz how much is
     left, which is exactly the information that stops them abandoning. */
  .quiz__count { font-size: 0.75rem; }
  /* Colour names under the swatches. Real manufacturer finish names -- "Sterling
     Marble", "Tuscan Sun" -- and the only label telling a buyer which finish
     they are looking at, so they are content, not decoration. */
  .swatch span { font-size: 0.75rem; }
}

/* The wordmark's second line is brand chrome, not copy: three words in a fixed
   lockup that nobody reads for information. It is raised for comfort but is
   deliberately allowed to stay under the 12px prose floor, and mobile_audit.py
   names it as the one documented exclusion rather than the gate being loosened
   to accommodate it. */
.brand__mark span { font-size: 0.68rem; }
@media (max-width: 900px) {
  .brand__mark span { font-size: 0.7rem; letter-spacing: 0.18em; }
}

/* ---------- sticky hover on touch ----------
   A touch device with no pointer still matches :hover on tap, and the state
   STAYS until something else is tapped. Every lift, scale and shadow below was
   therefore stuck on the last card a visitor touched, which reads as a selected
   state that means nothing.

   The transform-based effects are the ones that matter: a colour change that
   lingers is invisible, a card that stays lifted 3px with a 40px shadow is not. */
@media (hover: none) {
  .btn:hover,
  .product-card:hover,
  .areas-chips .chip--link:hover { transform: none; }
  .product-card:hover { box-shadow: none; }
  .collection-card:hover img { transform: none; }
  .quiz-card:hover { box-shadow: none; border-color: var(--line); }
  .quiz__option:hover { background: var(--bone); border-color: var(--line); }
  /* :hover on a link still underlines on tap, which flashes on every navigation. */
  a:hover { text-decoration: none; }
}

/* ---------- scroll lock ----------
   Applied by main.js to BODY ONLY while the nav drawer, the exit popup or the
   quiz is open.

   Body only, and this is measured rather than assumed. All three candidates lock
   the page; they differ in what else they break, tested at 390x844 scrolled to
   y=1500:

     html + body overflow:hidden -> scroll kept, header top = -1500px  (BROKEN)
     html overflow:hidden        -> scroll kept, header top = -1500px  (BROKEN)
     body overflow:hidden        -> scroll kept, header top = 0        (CORRECT)
     body position:fixed         -> scroll reset to 0, header ok       (loses place)

   overflow:hidden on the ROOT element removes the scrollport that
   position:sticky sticks to, so .site-header silently reverts to static and is
   left behind at the top of the document -- 1500px above the screen. Under the
   popup or the quiz nobody would notice, because those cover the viewport. Under
   the nav drawer it is fatal: the drawer is anchored to the header and the
   button that closes it lives there.

   position:fixed on body is the lock most often recommended, and it does hold on
   older iOS where body overflow can leak, but it drops the visitor back to the
   top of the page unless the offset is saved and restored by hand. The overlays
   carry overscroll-behavior:contain so a drag that runs past the end of a
   scrollable panel does not chain into the page behind it, which is the leak
   that matters here. */
body.is-locked { overflow: hidden; }

/* Stops a drag on the backdrop itself from being handed to the page behind it. */
.popup-overlay[data-open="true"] { overscroll-behavior: contain; }
