/* ==========================================================================
   Mobile Floor Coverings — shared stylesheet
   Every page uses this file and only this file. No page-level stylesheet,
   no new custom properties outside :root. If a page needs something this
   file lacks, it goes in this file.
   ========================================================================== */

/* --- 1. Fonts. Self-hosted. No Google Fonts request, no CDN. -------------- */
@font-face{
  font-family:'Archivo';
  src:url('../fonts/archivo-wdth-wght.woff2') format('woff2-variations'),
      url('../fonts/archivo-wdth-wght.woff2') format('woff2');
  font-weight:100 900; font-stretch:62% 125%; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Inter';
  src:url('../fonts/inter-latin-var.woff2') format('woff2-variations'),
      url('../fonts/inter-latin-var.woff2') format('woff2');
  font-weight:100 900; font-style:normal; font-display:swap;
}

/* --- 2. Tokens. Every contrast ratio below was computed, not estimated. --- */
:root{
  /* Ink — #184888 is 74.9% of every non-white pixel in the client's logo */
  --blue:#184888;          /*  8.47:1 on paper  */
  --blue-deep:#0F3163;     /* 11.98:1           */
  --blue-mid:#2F6199;      /*  5.97:1           */
  --blue-wash:#E8EDF5;

  /* Ground. This was a warm cream lifted off the sunlit side of the van;
     across whole pages it read as a yellow cast, so the site chrome is now
     white with cool neutral greys and the warmth stays in the photograph.
     Changed 2026-08-26. */
  --paper:#FFFFFF;
  --sand:#F1F4F8;
  --hair:#DDE3EB;

  /* Accent — black. Buttons, rules, eyebrows, the seam motif.
     The build first used an amber lifted from the sunlit van; it read as brown
     and was replaced 2026-08-26. Blue stays the brand, black does the work. */
  --accent:#111317;           /* 18.59:1 on white, and white on it is 18.59:1 */
  --accent-hover:#184888;     /* a black button turns brand blue on hover */
  --on-dark-accent:#FFFFFF;   /* the same role on --blue-deep grounds     */

  --ink:#15181D;           /* 17.79:1 on white */
  --muted:#58616F;         /*  6.26:1 */
  --on-dark:#C9D5E6;       /*  8.63:1 on --blue-deep */

  --ease:cubic-bezier(.22,1,.36,1);
  --dur-micro:200ms;
  --dur-reveal:400ms;
  --dur-hero:800ms;

  --hdr-h:66px;      /* fixed. the hero subtracts exactly this */
  --gap:1.25rem;
  --pad:clamp(1.25rem,5vw,2.5rem);
  --max:1180px;
  --max-prose:66ch;
  --section:clamp(3.5rem,8vw,6.5rem);

  --fs-h1:clamp(2.35rem,6.6vw,4.3rem);
  --fs-h2:clamp(1.85rem,4.1vw,3.05rem);
  --fs-h3:clamp(1.05rem,1.6vw,1.22rem);

  /* Vault R8 — a phone invents a dark version of any page that has not
     declared one, at paint time, after computed style. `only` is the keyword
     that disables it; `light` alone does not. Also drives scrollbars, the
     list a <select> opens, checkboxes and the autofill panel — none of which
     any background we set can reach. */
  color-scheme:only light;
  accent-color:var(--blue);
}

/* --- 3. Reset ------------------------------------------------------------ */
*,*::before,*::after{ box-sizing:border-box }
html{ -webkit-text-size-adjust:100% }
body,h1,h2,h3,h4,p,ul,ol,li,figure,blockquote,dl,dd{ margin:0 }
ul[class],ol[class]{ list-style:none; padding:0 }
img,svg,video{ max-width:100%; height:auto; display:block }
button,input,select,textarea{ font:inherit; color:inherit }

html{ scroll-behavior:smooth }              /* anchor jumps only — never a scroll library */
@media (prefers-reduced-motion:reduce){ html{ scroll-behavior:auto } }

body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:'Inter',system-ui,-apple-system,'Segoe UI',sans-serif;
  font-size:1.0625rem;
  line-height:1.62;
  font-synthesis-weight:none;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}

/* --- 4. Type ------------------------------------------------------------- */
h1,h2,h3,.h-display{
  font-family:'Archivo',Impact,system-ui,sans-serif;
  text-transform:uppercase;
  color:var(--blue);
  text-wrap:balance;
  overflow-wrap:break-word;      /* nothing may overflow its box at 360px */
  hyphens:none;                  /* and nothing may be hyphen-broken either */
}
h1{ font-size:var(--fs-h1); font-weight:800; font-stretch:88%;
    letter-spacing:-.005em; line-height:.92 }
h2{ font-size:var(--fs-h2); font-weight:800; font-stretch:92%; line-height:.95 }
h3{ font-size:var(--fs-h3); font-weight:700; font-stretch:100%;
    letter-spacing:.02em; line-height:1.2 }

p{ max-width:var(--max-prose) }
p + p{ margin-top:.9em }

.lede{ font-size:clamp(1.08rem,2vw,1.28rem); line-height:1.5; color:var(--ink) }
.muted{ color:var(--muted) }
.small{ font-size:.875rem; line-height:1.5 }

/* The eyebrow. Always --accent. */
.eyebrow{
  font-family:'Inter',sans-serif;
  font-size:.78rem; font-weight:600; letter-spacing:.13em;
  text-transform:uppercase; color:var(--accent);
  margin:0 0 .85rem;
}

a{ color:var(--blue); text-underline-offset:.18em; text-decoration-thickness:1px }
a:hover{ color:var(--blue-deep) }
:focus-visible{ outline:3px solid var(--blue); outline-offset:3px; border-radius:2px }

/* Tabular figures wherever a price or a phone number is set */
.num{ font-family:'Archivo',sans-serif; font-weight:700;
      font-variant-numeric:tabular-nums; font-stretch:100% }

/* --- 5. Layout ----------------------------------------------------------- */
.wrap{
  width:100%; max-width:var(--max); margin-inline:auto;
  /* max(), not a sum: the inset can raise the gutter, never stack onto it */
  padding-left:max(var(--pad), env(safe-area-inset-left, 0px));
  padding-right:max(var(--pad), env(safe-area-inset-right, 0px));
}
.section{ padding-block:var(--section) }
.section--sand{ background:var(--sand) }
.section--wash{ background:var(--blue-wash) }
.section--deep{ background:var(--blue-deep); color:var(--on-dark) }
.section--deep h2,.section--deep h3{ color:#fff }
.section--deep .eyebrow{ color:var(--on-dark-accent) }
/* :not(.btn) matters — a bare `.section--deep a` is (0,1,1) and outranks
   .btn--onDark (0,1,0), which paints white text on a white button. */
.section--deep a:not(.btn){ color:var(--on-dark-accent) }

.skip{
  position:absolute; left:-9999px; top:0; z-index:200;
  background:var(--blue); color:#fff; padding:.85rem 1.25rem; font-weight:600;
}
.skip:focus{ left:.5rem; top:.5rem }

/* The seam rule — the motif that ties every page together. A 3px black rule
   under each section heading that draws in from the left. scaleX only, so it
   never touches layout. */
.seam{
  display:block; width:88px; height:3px; background:var(--accent);
  margin:1.05rem 0 1.6rem;
  transform:scaleX(0); transform-origin:left center;
  transition:transform var(--dur-reveal) var(--ease);
}
.is-in .seam,.seam.is-in{ transform:scaleX(1) }
.section--deep .seam{ background:var(--on-dark-accent) }

/* --- 6. Buttons ---------------------------------------------------------- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:.55rem;
  min-height:48px; padding:.8rem 1.5rem;
  font-family:'Archivo',sans-serif; font-weight:700; font-stretch:100%;
  font-size:.95rem; letter-spacing:.045em; text-transform:uppercase;
  text-decoration:none; border:2px solid transparent; border-radius:2px;
  cursor:pointer;
  transition:background-color var(--dur-micro) var(--ease),
             border-color var(--dur-micro) var(--ease),
             color var(--dur-micro) var(--ease),
             transform var(--dur-micro) var(--ease);
}
.btn:active{ transform:translateY(1px) }
.btn--primary{ background:var(--accent); color:#fff }        /* white on black = 18.6:1 */
.btn--primary:hover{ background:var(--accent-hover); color:#fff }
.btn--blue{ background:var(--blue); color:#fff }             /* 9.05:1 */
.btn--blue:hover{ background:var(--blue-deep); color:#fff }
.btn--ghost{ background:transparent; color:var(--blue); border-color:var(--blue) }
.btn--ghost:hover{ background:var(--blue); color:#fff }
.section--deep .btn--ghost{ color:#fff; border-color:var(--on-dark) }
.section--deep .btn--ghost:hover{ background:#fff; color:var(--blue-deep) }
/* On the dark hero and the blue header, white is the primary. */
.btn--onDark{ background:#fff; color:var(--blue-deep) }
.btn--onDark:hover{ background:var(--sand); color:var(--blue-deep) }
.btn--onDarkGhost{ background:transparent; color:#fff; border-color:rgba(255,255,255,.5) }
.btn--onDarkGhost:hover{ background:#fff; color:var(--blue-deep); border-color:#fff }
.btn-row{ display:flex; flex-wrap:wrap; gap:.75rem; margin-top:1.75rem }

/* --- 7. Header ----------------------------------------------------------- */
/* Fixed, and transparent while it sits over the hero photograph; it turns to
   translucent brand blue with a blur once the page scrolls, so it stays
   readable over the light sections below. Inner pages have no dark hero, so
   they carry .hdr--solid and start in the scrolled state.

   The height is FIXED and never animates. It used to shrink on scroll, and
   because a sticky header is in flow, every padding / height / font-size
   change altered the document height as the page scrolled (measured: 7554px
   down to 7544px), which shoved everything below it up and down and read as
   the whole site shaking. Only colour changes now. */
.hdr{
  position:fixed; top:0; left:0; right:0; z-index:100;
  background:transparent;
  border-bottom:1px solid transparent;
  transition:background-color var(--dur-micro) var(--ease),
             border-color var(--dur-micro) var(--ease);
}
.hdr.is-stuck,
.hdr--solid{
  background:rgba(15,49,99,.93);
  -webkit-backdrop-filter:blur(18px);
          backdrop-filter:blur(18px);
  border-bottom-color:rgba(255,255,255,.12);
}
.hdr__in{
  max-width:var(--max); margin-inline:auto;
  min-height:calc(var(--hdr-h) - 1px);
  padding:0 max(var(--pad), env(safe-area-inset-right, 0px))
         0 max(var(--pad), env(safe-area-inset-left, 0px));
  display:grid; grid-template-columns:1fr auto 1fr;
  align-items:center; gap:1rem;
}

/* The supplied logo is a 990x178 PNG on an opaque white background, so dropped
   straight in it reads as a white box with an illegible second line. The
   lockup is rebuilt from the logo's own two parts: his actual badge, and his
   name set in the site's display face. The original PNG still ships as the
   favicon and the share card. */
.hdr__logo{
  justify-self:center; display:flex; align-items:center; gap:.6rem;
  text-decoration:none; flex:0 0 auto;
}
.hdr__mark{ height:38px; width:auto; flex:0 0 auto; display:block }
.hdr__name{
  font-family:'Archivo',sans-serif; color:#fff;
  text-transform:uppercase; line-height:.98;
  display:flex; flex-direction:column;
}
.hdr__name b{ font-weight:800; font-stretch:88%; font-size:1.12rem; letter-spacing:-.005em }
.hdr__name span{
  font-weight:600; font-stretch:100%; font-size:.55rem; letter-spacing:.19em;
  color:rgba(255,255,255,.72);
}

.nav{ display:flex; align-items:center; gap:.1rem }
.nav--left{ justify-self:start }
.nav--right{ justify-self:end }
.nav > a{
  display:inline-block; padding:.5rem .62rem; border-radius:2px;
  font-family:'Archivo',sans-serif; font-weight:600; font-stretch:86%;
  font-size:.82rem; letter-spacing:.09em; text-transform:uppercase;
  color:rgba(255,255,255,.82); text-decoration:none; position:relative;
  white-space:nowrap;
  transition:color var(--dur-micro) var(--ease);
}
.nav > a::after{
  content:''; position:absolute; left:.62rem; right:.62rem; bottom:.22rem;
  height:2px; background:#fff;
  transform:scaleX(0); transform-origin:left center;
  transition:transform var(--dur-micro) var(--ease);
}
.nav > a:hover,.nav > a[aria-current="page"]{ color:#fff }
.nav > a:hover::after,.nav > a[aria-current="page"]::after{ transform:scaleX(1) }

/* Facebook / Instagram, the way the reference site carries them */
.hdr__socials{ display:flex; align-items:center; gap:.15rem; margin-left:.3rem }
.social{
  width:34px; height:34px; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center;
  border-radius:50%; color:rgba(255,255,255,.72);
  transition:color var(--dur-micro) var(--ease),
             background-color var(--dur-micro) var(--ease);
}
.social svg{ width:17px; height:17px; fill:currentColor; display:block }
.social:hover{ color:#fff; background:rgba(255,255,255,.15) }

.hdr__call{ margin-left:.5rem; flex:0 0 auto }
.hdr__burger{
  display:none; justify-self:end;
  width:46px; height:46px; align-items:center; justify-content:center;
  background:transparent; border:1px solid rgba(255,255,255,.35);
  border-radius:2px; cursor:pointer;
}
.hdr__burger span{ display:block; width:20px; height:2px; background:#fff; position:relative }
.hdr__burger span::before,.hdr__burger span::after{
  content:''; position:absolute; left:0; width:20px; height:2px; background:#fff;
  transition:transform var(--dur-micro) var(--ease);
}
.hdr__burger span::before{ top:-6px } .hdr__burger span::after{ top:6px }
.hdr__burger[aria-expanded="true"] span{ background:transparent }
.hdr__burger[aria-expanded="true"] span::before{ transform:translateY(6px) rotate(45deg) }
.hdr__burger[aria-expanded="true"] span::after{ transform:translateY(-6px) rotate(-45deg) }

/* --- The phone menu ------------------------------------------------------ */
.mobmenu{ display:none }
@media (max-width:1060px){
  .hdr__in{ grid-template-columns:1fr auto }
  .hdr__logo{ justify-self:start }
  .hdr__burger{ display:flex }
  .nav--left,.nav--right{ display:none }

  .mobmenu{
    position:absolute; left:0; right:0; top:100%;
    flex-direction:column;
    background:rgba(15,49,99,.98);
    -webkit-backdrop-filter:blur(18px); backdrop-filter:blur(18px);
    border-bottom:1px solid rgba(255,255,255,.14);
    box-shadow:0 18px 30px -18px rgba(0,0,0,.55);
    padding:.35rem max(var(--pad), env(safe-area-inset-right, 0px)) 1.15rem
            max(var(--pad), env(safe-area-inset-left, 0px));
    max-height:calc(100svh - var(--hdr-h)); overflow-y:auto;
    /* In landscape the six links exceed the cap, so the menu really scrolls;
       without this a flick that hits either end carries into the page behind
       and drags the header through its is-stuck flip mid-gesture. */
    overscroll-behavior:contain;
  }
  .mobmenu.is-open{ display:flex }
  .mobmenu nav{ display:flex; flex-direction:column }
  /* Scoped to the nav, not to every <a> in the menu — the unscoped version
     also repainted the Call button's own text white on white. */
  .mobmenu nav a{
    padding:.9rem .15rem; border-bottom:1px solid rgba(255,255,255,.13);
    font-family:'Archivo',sans-serif; font-weight:600; font-stretch:88%;
    font-size:1.02rem; letter-spacing:.07em; text-transform:uppercase;
    color:rgba(255,255,255,.9); text-decoration:none;
  }
  .mobmenu nav a:hover{ color:#fff }
  .mobmenu__foot{
    display:flex; align-items:center; justify-content:space-between;
    gap:1rem; margin-top:1.15rem;
  }
  .mobmenu__foot .btn{ flex:1 1 auto }
  .mobmenu__foot .btn{ padding:.8rem 1rem }
  .mobmenu .hdr__socials{ margin:0; gap:.4rem }
  .mobmenu .social{ border:1px solid rgba(255,255,255,.25) }
  .mobmenu .social:hover{ border-color:transparent }
}
@media (max-width:560px){
  .hdr__mark{ height:32px }
  .hdr__name b{ font-size:1rem }
  .hdr__name span{ font-size:.5rem; letter-spacing:.14em }
}

/* Below 700px the phone moves to a fixed bar at the bottom of the screen,
   where it is bigger, always visible, and the largest tappable thing on the
   page — which is what it should have been all along. */
.callbar{ display:none }
@media (max-width:1060px){
  .callbar{
    position:fixed; left:0; right:0; bottom:0; z-index:99;
    display:flex; align-items:center; justify-content:center; gap:.6rem;
    min-height:58px; padding:.6rem 1rem;
    background:var(--accent); color:#fff; text-decoration:none;
    font-family:'Archivo',sans-serif; font-weight:700; font-size:1.05rem;
    letter-spacing:.03em; text-transform:uppercase;
    font-variant-numeric:tabular-nums;
    box-shadow:0 -4px 18px rgba(17,19,23,.22);
    padding-bottom:calc(.6rem + env(safe-area-inset-bottom, 0px));
  }
  .callbar:hover,.callbar:active{ background:var(--accent-hover); color:#fff }
  .callbar svg{ width:19px; height:19px; fill:currentColor; flex:0 0 auto }
  /* the bar grows by the home-indicator inset now that cover is set */
  body{ padding-bottom:calc(64px + env(safe-area-inset-bottom, 0px)) }
}
@media print{ .callbar,.hdr{ display:none } }

/* --- 8. Hero ------------------------------------------------------------- */
/* Structure taken from the reference site kodyn picked: full-bleed media, a
   dark gradient over it, centred content, the CTAs, and a scroll cue. The
   header is fixed and sits on top, so this is a full 100svh.

   This is a deliberate departure from vault R5 ("light, welcoming heros,
   dark text") — he asked for this specific treatment by name, twice. It also
   removes the problem that treatment was fighting: with the photograph full
   bleed there is no panel edge to blend, so there is no seam to get wrong. */
.hero{
  position:relative; overflow:hidden; isolation:isolate;
  min-height:100vh;      /* fallback: a browser that does not know svh drops */
  min-height:100svh;     /* the next line and would otherwise have NO hero height */
  display:flex; align-items:center; justify-content:center;
  background:var(--blue-deep); text-align:center;
}
.hero__media{ position:absolute; inset:0; z-index:0; overflow:hidden }
.hero__img{
  /* Deliberately oversized and offset: the entrance slides it and the scrub
     translates it, and at exactly 100% either one would pull a bare edge into
     frame. */
  position:absolute; top:-7%; left:-7%; width:114%; height:114%;
  max-width:none;   /* the global img{max-width:100%} reset was capping this at
                       100% while left:-7% still applied, leaving a bare strip
                       of hero background down the right-hand edge */
  object-fit:cover; object-position:55% 88%;
  display:block;
}
/* Dark enough at the top for white header links, dark enough at the bottom
   for the scroll cue, lighter through the middle so the van still reads.
   Every text-over-photo contrast ratio here was sampled from the composited
   pixels, not estimated. */
.hero__overlay{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  /* Carries the whole scrim now that the radial is gone. The middle band is
     where the headline and lede sit, so that is where it is strongest. */
  background:linear-gradient(180deg,
    rgba(8,24,48,.66) 0%, rgba(8,24,48,.52) 20%,
    rgba(8,24,48,.60) 44%, rgba(8,24,48,.60) 66%,
    rgba(8,24,48,.78) 100%);
}
.hero__canvas{
  position:absolute; inset:0; z-index:2; width:100%; height:100%;
  display:block; pointer-events:none;
  opacity:0; transition:opacity var(--dur-hero) var(--ease);
}
.hero__canvas.is-live{ opacity:1 }

/* The van carries its own large painted lettering, and with the photograph
   full bleed that lettering sits directly behind the headline whatever crop
   is used — the two compete. A soft pool of shade behind the copy only,
   rather than more darkness over the whole picture, gives the type a clean
   field and leaves the van bright at the edges. */
.hero__content{
  position:relative; z-index:3;
  width:100%; max-width:54rem; margin-inline:auto;
  padding:calc(var(--hdr-h) + clamp(1.5rem,5vw,3rem)) var(--pad) clamp(5rem,11vw,7rem);
}
/* The copy used to sit on a second scrim here: a ::before at z-index:-1, 150%
   wide and 135% tall, holding a dark radial gradient. It was removed
   2026-08-27 after it rendered on one machine as a large light-grey blob with
   soft edges, covering the lower two thirds of the hero — its exact geometry.
   A negative-z-index pseudo-element that overhangs its parent on all four
   sides is the fragile part; it did not reproduce here, and rather than chase
   one GPU, the darkening it provided has been folded into .hero__overlay,
   which is a plain full-bleed layer at z-index 1 and cannot misplace itself.
   Contrast over the photograph is unchanged. */
.hero .eyebrow{ color:rgba(255,255,255,.78) }
.hero h1{ color:#fff }
.hero h1 .l2{ display:block; color:#fff }
.hero__sub{
  margin:1.35rem auto 0; max-width:48ch;
  color:rgba(255,255,255,.9);
}
.hero__facts{
  display:flex; flex-wrap:wrap; justify-content:center;
  gap:.5rem .95rem; margin-top:1.6rem;
  font-size:.78rem; font-weight:600; letter-spacing:.055em; text-transform:uppercase;
  color:rgba(255,255,255,.9);
}
.hero__facts li{ display:flex; align-items:center; gap:.55rem }
.hero__facts li + li::before{
  content:''; width:5px; height:5px; background:rgba(255,255,255,.55); border-radius:50%;
}
.hero .btn-row{ justify-content:center; margin-top:2rem }

/* Scroll cue */
.hero__scroll{
  position:absolute; left:50%; bottom:1.6rem; transform:translateX(-50%);
  z-index:3; display:flex; flex-direction:column; align-items:center; gap:.45rem;
  color:rgba(255,255,255,.6); text-decoration:none;
  font-family:'Inter',sans-serif; font-size:.66rem; font-weight:500;
  letter-spacing:.24em; text-transform:uppercase;
  transition:color var(--dur-micro) var(--ease);
}
.hero__scroll:hover{ color:#fff }
.hero__scroll svg{
  width:20px; height:20px; stroke:currentColor; fill:none;
  stroke-width:2; stroke-linecap:round; stroke-linejoin:round;
}
@media (max-width:1060px){ .hero__scroll{ bottom:4.6rem } }

/* On a phone the hero box is portrait and the photograph landscape, so cover
   crops horizontally: object-position X is the only lever, and this one moves
   the van's painted lettering out from directly behind the copy. */
@media (max-width:859px){
  /* Portrait box, landscape photo: cover crops horizontally, so X is the only
     lever. At 74% the window sat on the cab and cropped the logo badge clean
     out of frame on a phone. 44% centres the window on the badge and the
     painted lettering, which is the whole point of the picture. */
  .hero__img{ object-position:44% 56% }
}

/* Y is tuned so the van's painted logo sits clear ABOVE the headline rather
   than behind it. The wider the screen the shorter the hero is relative to its
   width, so cover crops more vertically and the logo climbs — these two walk Y
   back to hold it in the same place. */
@media (min-width:1500px){ .hero__img{ object-position:55% 80% } }
@media (min-width:1800px){ .hero__img{ object-position:55% 74% } }

/* An anchor jump must clear the fixed header rather than hide under it. */
[id]{ scroll-margin-top:calc(var(--hdr-h) + 14px) }

/* --- 8b. Trust bar ------------------------------------------------------- */
/* A strip of at-a-glance facts directly under the hero. Same idea as the
   reference site's: light band, centred row, hairline dividers, small bold
   uppercase labels with a brand-coloured icon. */
.trustbar{
  /* White, not the band grey: the section immediately below it is --sand, and
     matching that just merged the two into one flat block. */
  background:var(--paper);
  border-bottom:1px solid var(--hair);
  padding:1.1rem 0;
}
.trustbar__in{
  max-width:var(--max); margin-inline:auto; padding-inline:var(--pad);
  display:flex; align-items:center; justify-content:center;
  gap:clamp(.85rem,2.2vw,1.6rem); flex-wrap:nowrap;
}
.trustbar__item{
  display:flex; align-items:center; gap:.55rem;
  font-family:'Inter',sans-serif; font-size:.78rem; font-weight:700;
  letter-spacing:.07em; text-transform:uppercase; color:var(--muted);
  white-space:nowrap;
}
.trustbar__icon{ width:20px; height:20px; flex:0 0 auto; color:var(--blue) }
.trustbar__div{ width:1px; height:24px; background:var(--hair); flex:0 0 auto }

/* The reference row is nowrap, which overflows. Ours becomes a two-up grid
   and drops the dividers rather than scrolling sideways. It has to switch at
   1180, not 1040: four labels plus three dividers measured 1145px wide and
   pushed the whole page into a horizontal scroll at 1100. */
@media (max-width:1180px){
  .trustbar__in{
    display:grid; grid-template-columns:repeat(2,auto);
    justify-content:center; gap:.85rem 1.75rem;
  }
  .trustbar__div{ display:none }
  .trustbar__item{ font-size:.76rem; letter-spacing:.07em; white-space:normal }
}
@media (max-width:520px){
  .trustbar__in{ grid-template-columns:1fr; justify-items:start; gap:.7rem }
  /* one column, so there is room to read it */
  .trustbar__item{ font-size:.82rem }
}

/* --- 10. Cards, steps, quotes ------------------------------------------- */
.grid{ display:grid; gap:1.5rem; margin-top:2.25rem }
.grid--3{ grid-template-columns:repeat(auto-fit,minmax(250px,1fr)) }
.grid--2{ grid-template-columns:repeat(auto-fit,minmax(300px,1fr)) }

.card{
  background:#fff; border:1px solid var(--hair); border-radius:2px;
  padding:1.6rem 1.5rem;
}
.section--sand .card{ background:var(--paper) }
.card h3{ margin-bottom:.55rem }
.card p{ max-width:none }

/* The three steps. His own words, numbered. */
.step{ position:relative; padding-left:3.4rem }
.step__n{
  position:absolute; left:0; top:-.15rem;
  font-family:'Archivo',sans-serif; font-weight:800; font-stretch:88%;
  font-size:2.1rem; line-height:1; color:var(--accent);
  font-variant-numeric:tabular-nums;
}
.step h3{ color:var(--blue) }

blockquote{ margin:0 }
.quote{
  background:#fff; border:1px solid var(--hair); border-left:4px solid var(--accent);
  border-radius:2px; padding:1.5rem 1.4rem;
}
.section--sand .quote{ background:var(--paper) }
.quote p{ max-width:none; font-size:1.02rem }
.quote footer{
  margin-top:.9rem; font-size:.82rem; color:var(--muted);
  font-weight:600; letter-spacing:.03em;
}
.quote cite{ font-style:normal; color:var(--blue); text-transform:uppercase;
             font-family:'Archivo',sans-serif; font-weight:700; letter-spacing:.03em }

/* Figures */
figure{ margin:0 }
figure img{ width:100%; border-radius:2px; border:1px solid var(--hair) }
figcaption{ margin-top:.6rem; font-size:.8rem; color:var(--muted) }

/* Plain lists inside prose */
.list{ list-style:none; padding:0; margin:1.1rem 0 0; max-width:var(--max-prose) }
.list li{ position:relative; padding-left:1.5rem; margin-bottom:.55rem }
.list li::before{
  content:''; position:absolute; left:0; top:.62em;
  width:8px; height:2px; background:var(--accent);
}

/* Price table */
.rates{ width:100%; border-collapse:collapse; margin-top:2rem; font-size:.98rem }
.rates caption{ text-align:left; color:var(--muted); font-size:.85rem; padding-bottom:.75rem }
.rates th,.rates td{ text-align:left; padding:.85rem .75rem; border-bottom:1px solid var(--hair) }
.rates thead th{
  font-family:'Archivo',sans-serif; font-weight:700; text-transform:uppercase;
  font-size:.78rem; letter-spacing:.06em; color:var(--blue);
  border-bottom:2px solid var(--blue);
}
.rates td:last-child,.rates th:last-child{ text-align:right }
.rates .num{ color:var(--accent); white-space:nowrap }
.table-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch }

/* Town list */
.towns{ display:flex; flex-wrap:wrap; gap:.6rem; margin-top:1.75rem; padding:0; list-style:none }
.towns li{
  border:1px solid var(--hair); background:#fff; border-radius:999px;
  padding:.45rem 1rem; font-size:.9rem; font-weight:500;
}
.section--sand .towns li{ background:var(--paper) }

/* --- 11. Forms ----------------------------------------------------------- */
.form{ display:grid; gap:1.1rem; max-width:640px }
.field{ display:flex; flex-direction:column; gap:.4rem }
.field label{ font-size:.86rem; font-weight:600; color:var(--ink) }
.field .req{ color:var(--accent) }
.field input,.field select,.field textarea{
  width:100%; padding:.75rem .85rem;
  background:#fff; color:var(--ink);
  border:1px solid var(--hair); border-radius:2px;
  transition:border-color var(--dur-micro) var(--ease),
             box-shadow var(--dur-micro) var(--ease);
}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:none; border-color:var(--blue);
  box-shadow:0 0 0 3px rgba(24,72,136,.16);
}
.field textarea{ min-height:120px; resize:vertical }
.field-row{ display:grid; gap:1.1rem; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)) }

.checks{ display:grid; gap:.5rem; grid-template-columns:repeat(auto-fit,minmax(160px,1fr));
         padding:0; margin:0; list-style:none }
.checks li{ display:flex; align-items:center; gap:.55rem }
.checks input{ width:20px; height:20px; flex:0 0 auto }
fieldset{ border:0; padding:0; margin:0 }
legend{ font-size:.86rem; font-weight:600; padding:0 0 .55rem }

/* Honeypot. Never display:none — some bots skip hidden fields. */
.hp{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden }

.form-status{ font-size:.95rem; padding:.85rem 1rem; border-radius:2px; margin-top:.25rem }
.form-status:empty{ display:none }
.form-status--ok{ background:var(--blue-wash); color:var(--blue-deep);
                  border-left:4px solid var(--blue) }
.form-status--err{ background:var(--sand); color:var(--ink); border-left:4px solid var(--accent) }
.form-status a{ color:inherit; font-weight:700 }

/* Vault R8, the part CSS backgrounds cannot reach: the browser paints the
   autofill panel itself and offers no property for it. */
input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus,
textarea:-webkit-autofill,select:-webkit-autofill{
  -webkit-box-shadow:0 0 0 100px #fff inset !important;
  box-shadow:0 0 0 100px #fff inset !important;
  -webkit-text-fill-color:var(--ink) !important;
  caret-color:var(--ink);
}
::placeholder{ color:#767E8C; opacity:1 }

/* --- 12. Footer ---------------------------------------------------------- */
.ftr{ background:var(--blue-deep); color:var(--on-dark); padding-block:clamp(2.75rem,6vw,4rem) }
.ftr a{ color:var(--on-dark); text-decoration:none }
.ftr a:hover{ color:#fff; text-decoration:underline }
.ftr__grid{
  display:grid; gap:2.25rem;
  grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
}
.ftr__logo img{ height:44px; width:auto; background:#fff; padding:.4rem .6rem; border-radius:2px }
.ftr h2,.ftr h3{
  color:#fff; font-size:.78rem; letter-spacing:.13em; font-stretch:100%;
  font-weight:700; margin-bottom:.9rem;
}
.ftr ul{ list-style:none; padding:0; margin:0 }
.ftr li{ margin-bottom:.5rem; font-size:.92rem }
.ftr__nap{ font-size:.92rem; font-style:normal; line-height:1.7 }
.ftr__nap strong{ color:#fff }
.ftr__phone{
  display:inline-block; margin-top:.35rem;
  font-family:'Archivo',sans-serif; font-weight:700; font-size:1.35rem;
  color:var(--on-dark-accent) !important; font-variant-numeric:tabular-nums;
}
.ftr__base{
  margin-top:2.5rem; padding-top:1.4rem; border-top:1px solid rgba(217,205,182,.26);
  display:flex; flex-wrap:wrap; gap:.65rem 1.4rem;
  align-items:center; font-size:.82rem;
}
.ftr__base ul{ display:flex; flex-wrap:wrap; gap:.65rem 1.25rem }
.ftr__base li{ margin:0 }

/* --- 13. Motion ---------------------------------------------------------- */
/* opacity and transform only, everywhere, so the compositor never has to
   touch layout or paint. The only @keyframes in this file is the review
   marquee, which translates and is disabled under reduced motion. */
.reveal{
  opacity:0; transform:translateY(14px);
  transition:opacity var(--dur-reveal) var(--ease),
             transform var(--dur-reveal) var(--ease);
}
.reveal.is-in{ opacity:1; transform:none }
.reveal[data-delay="1"]{ transition-delay:.08s }
.reveal[data-delay="2"]{ transition-delay:.16s }
.reveal[data-delay="3"]{ transition-delay:.24s }

/* Backstop: if the JS never runs at all, nothing stays invisible. */
@media (prefers-reduced-motion:reduce){
  .reveal{ opacity:1; transform:none; transition:none }
  .seam{ transform:scaleX(1); transition:none }
  .hero__canvas{ display:none }
  .btn,.nav a::after,.hdr{ transition:none }
}
/* .reveal starts at opacity:0, so a page printed before its JS ran would
   otherwise come out as blank sheets. */
@media print{
  .reveal{ opacity:1 !important; transform:none !important }
  .hdr,.ftr__base,.hero__canvas,.btn{ display:none }
  body{ background:#fff }
}

/* --- 14. Utilities ------------------------------------------------------- */
.vh{
  position:absolute !important; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
.center{ text-align:center }
.center p,.center .seam{ margin-inline:auto }
.center p{ max-width:60ch }
.stack > * + *{ margin-top:1.1rem }
.pt0{ padding-top:0 }

/* --- 15. 404 ------------------------------------------------------------- */
.oops{ padding-block:clamp(4rem,12vw,8rem); text-align:center }
.oops h1{ font-size:clamp(3rem,12vw,7rem) }

/* --- 16. Defensive dark-mode restatement -------------------------------- */
/* For extensions and reader modes that key off the media query rather than
   honouring color-scheme. Vault R8. */
@media (prefers-color-scheme:dark){
  :root{ color-scheme:only light }
  body{ background:var(--paper); color:var(--ink) }
  .card,.rev,.quote,.towns li,.field input,.field select,.field textarea{ background:#fff }
}

/* --- 17. Centred sections ------------------------------------------------
   The reference site centres every section header — small eyebrow, big
   heading, rule, intro line — and it reads better on a phone, where a centred
   block needs no left gutter to feel deliberate.

   Note `p { max-width: 66ch }` from the type rules applies to the eyebrow too,
   so text-align alone was not enough: without margin-inline the paragraph box
   stays hard left inside a centred parent and only its text centres, which is
   why the hero eyebrow sat off to the left.
   ------------------------------------------------------------------------ */
.hero__content .eyebrow{ margin-inline:auto }

.section > .wrap{ text-align:center }
.section > .wrap p{ margin-inline:auto }
.section > .wrap .seam{ margin-inline:auto }
.section > .wrap .btn-row{ justify-content:center }
.section > .wrap .towns{ justify-content:center }

/* The three steps read better with the numeral centred above the heading
   than hanging off the left edge. */
.section > .wrap .step{ padding-left:0 }
.section > .wrap .step__n{
  position:static; display:block; margin:0 auto .35rem; line-height:1;
}

/* Blocks where centring genuinely hurts readability keep their own alignment:
   form fields need their labels on the left of the control, a rate table has
   columns, and a bulleted list has a hanging marker. */
.section > .wrap .form,
.section > .wrap .field,
.section > .wrap .checks,
.section > .wrap .rates,
.section > .wrap .list,
.section > .wrap .quote,
.section > .wrap .card{ text-align:left }
.section > .wrap .form p,
.section > .wrap .card p,
.section > .wrap .quote p,
.section > .wrap .list{ margin-inline:0 }
.section > .wrap .form{ margin-inline:auto }
.section > .wrap .quote footer{ text-align:left }
/* A centred rule should draw from the middle outwards; drawing from its left
   edge was right when the whole block was left-aligned. */
.section > .wrap .seam{ transform-origin:center }

/* When a section header has no intro line under it, the rule is the last thing
   before the content and 36px reads cramped. Only targets that case — headers
   that do have a paragraph keep the tighter spacing. */
.section > .wrap .reveal > .seam:last-child{ margin-bottom:3rem }

/* --- 18. Review marquee --------------------------------------------------
   Same construction as the reference site: an overflow-hidden viewport, a
   flex track, and one linear keyframe that translates the track -50%. The
   card set is duplicated once in JS, so at -50% the second copy sits exactly
   where the first started and the loop is seamless.

   translate only, so it stays on the compositor. It pauses on hover, on
   focus-within, when the tab is hidden and when it scrolls out of view —
   nothing on this site runs on a timer nobody is watching.
   ------------------------------------------------------------------------ */
.marquee{
  overflow:hidden; margin-top:2.5rem;
  -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 6%, #000 94%, transparent 100%);
          mask-image:linear-gradient(90deg, transparent 0, #000 6%, #000 94%, transparent 100%);
}
.marquee__track{
  display:flex; gap:1.5rem; width:max-content;
  animation:marquee-scroll 66s linear infinite;
}
/* NOT -50%. The track is 14 cards and therefore 13 gaps, so half its width is
   7 cards + 6.5 gaps — but a seamless wrap needs 7 cards + 7 gaps. -50% was
   half a gap (12px) short and the row snapped backwards once a cycle. The JS
   measures the real distance and sets --marquee-shift; this is the fallback. */
@keyframes marquee-scroll{
  from{ transform:translateX(0) }
  to{ transform:translateX(calc(-50% - var(--marquee-half-gap, 0px))) }
}
.marquee__track:hover,
.marquee__track:focus-within,
.marquee.is-paused .marquee__track{ animation-play-state:paused }

.rev{
  flex:0 0 340px; display:flex; flex-direction:column; gap:.9rem;
  background:var(--paper); border:1px solid var(--hair); border-radius:2px;
  padding:1.6rem 1.5rem; text-align:left;
  transition:box-shadow var(--dur-micro) var(--ease);
}
.rev:hover{ box-shadow:0 10px 28px -16px rgba(15,49,99,.4) }
.rev p{ max-width:none; margin:0; font-size:.97rem; line-height:1.55 }
.rev__top{ display:flex; align-items:center; justify-content:space-between; gap:.75rem }
.rev__src{
  font-family:'Inter',sans-serif; font-size:.66rem; font-weight:700;
  letter-spacing:.13em; text-transform:uppercase; color:var(--muted);
}
.stars{ display:inline-flex; gap:2px }
.stars svg{ width:16px; height:16px; fill:#B4780C; display:block }
.rev footer{
  margin-top:auto; padding-top:.35rem;
  font-size:.82rem; color:var(--muted); font-weight:600; letter-spacing:.02em;
}
.rev cite{
  font-style:normal; color:var(--blue); text-transform:uppercase;
  font-family:'Archivo',sans-serif; font-weight:700; letter-spacing:.03em;
}
@media (max-width:560px){ .rev{ flex:0 0 78vw; padding:1.35rem 1.25rem } }

/* Reduced motion: no auto-scroll. It becomes an ordinary horizontal scroller
   the visitor drives themselves, which is what the animation was standing in
   for anyway. */
@media (prefers-reduced-motion:reduce){
  .marquee{ overflow-x:auto; -webkit-overflow-scrolling:touch }
  .marquee__track{ animation:none }
}

/* --- 19. Platform buttons ------------------------------------------------
   White button carrying the platform's own logo. Google's and Yelp's brand
   guidelines both permit their mark on a link that points at your own
   listing, which is exactly what these do — so the logos are drawn in their
   official colours rather than tinted to our palette.
   ------------------------------------------------------------------------ */
.btn--brand{
  background:var(--paper); color:var(--ink);
  border:1px solid var(--hair);
  gap:.7rem; padding-inline:1.35rem;
}
.btn--brand:hover{
  background:var(--paper); color:var(--ink);
  border-color:var(--blue-mid);
  box-shadow:0 6px 18px -10px rgba(15,49,99,.45);
}
.brandmark{ width:20px; height:20px; flex:0 0 auto; display:block }

/* --- 20. Material tiles --------------------------------------------------
   Replaces the "Sample Board" card. That component existed because every
   material photograph we had was 300px — a chip on a card is honest at that
   size and a full-bleed image is not. Now that most of these are 1536px
   photographs of real installed floors, the constraint is gone, and the card
   chrome it justified (border, drop shadow, punched hole, label in a strip
   underneath) was what made the grid read as a 2010 product catalogue.

   These are full-bleed instead: the photograph IS the tile, the label sits on
   it, and nothing is framed. Transform and opacity only.
   ------------------------------------------------------------------------ */
.tiles{
  display:grid; gap:1rem; margin-top:2.5rem;
  grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  padding:0; list-style:none;
}
@media (min-width:900px){ .tiles{ grid-template-columns:repeat(3,1fr) } }

.tile{ margin:0 }
.tile a{
  position:relative; display:block; overflow:hidden;
  aspect-ratio:5/6; border-radius:2px; text-decoration:none;
  background:var(--sand);
}
.tile img{
  width:100%; height:100%; object-fit:cover; display:block;
  transition:transform 700ms var(--ease);
}
/* The scrim only covers the lower half, so the material still reads at the
   top of the tile. Contrast of the label against the darkest part of each
   photograph was measured, not assumed. */
.tile a::after{
  content:''; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg,
    rgba(8,20,38,0) 38%, rgba(8,20,38,.30) 62%, rgba(8,20,38,.82) 100%);
  transition:opacity var(--dur-micro) var(--ease);
}
.tile__label{
  position:absolute; left:0; right:0; bottom:0; z-index:2;
  display:flex; align-items:center; justify-content:space-between; gap:.75rem;
  padding:1.1rem 1.15rem;
  font-family:'Archivo',sans-serif; font-weight:800; font-stretch:92%;
  font-size:1.02rem; letter-spacing:.02em; text-transform:uppercase;
  color:#fff;
}
.tile__label svg{
  width:20px; height:20px; flex:0 0 auto;
  stroke:currentColor; fill:none; stroke-width:2.2;
  stroke-linecap:round; stroke-linejoin:round;
  transition:transform var(--dur-micro) var(--ease);
}
.tile a:hover img,
.tile a:focus-visible img{ transform:scale(1.055) }
.tile a:hover .tile__label svg,
.tile a:focus-visible .tile__label svg{ transform:translateX(5px) }
.tile a:focus-visible{ outline:3px solid var(--blue); outline-offset:3px }

@media (prefers-reduced-motion:reduce){
  .tile img,.tile__label svg{ transition:none }
  .tile a:hover img{ transform:none }
}
@media (max-width:560px){
  .tile a{ aspect-ratio:4/3 }
  .tile__label{ font-size:.95rem; padding:.95rem 1rem }
}

/* The header row is a 1fr auto 1fr grid, so BOTH side columns take the width
   of the wider one. With "Call (360) 671-4693" spelled out, that pushed the
   header's minimum to 1180px — it overflowed at 1041, the first width where
   the desktop nav returns. Dropping the word and adding a handset glyph
   brings the requirement under 1040 so the breakpoint holds. */
.hdr__tel{
  font-size:.86rem; letter-spacing:.01em; padding:.62rem .9rem;
  gap:.45rem; font-variant-numeric:tabular-nums; min-height:42px;
}
.hdr__tel svg{ width:15px; height:15px; fill:currentColor; flex:0 0 auto }

/* --- 21. Audit fixes -----------------------------------------------------
   Found by the pre-deploy critique panel and each confirmed by measurement.
   ------------------------------------------------------------------------ */

/* .field label sets --ink explicitly, which beat the inherited --on-dark on
   the dark Book section: labels rendered at 1.39:1 and the required asterisks
   at 1.45:1. Five effectively unlabelled boxes. */
.section--deep .field label{ color:var(--on-dark) }
.section--deep .field .req{ color:var(--sun-glow, #F0C46A) }
.section--deep legend{ color:var(--on-dark) }

/* The focus ring was --blue everywhere, which is 1.42:1 on --blue-deep — so
   every link in the header, the Book section, the phone menu and the whole
   footer had an invisible focus indicator. WCAG 2.4.7 / 1.4.11. */
.hdr :focus-visible,
.mobmenu :focus-visible,
.section--deep :focus-visible,
.ftr :focus-visible,
.hero :focus-visible,
.pagehead :focus-visible{ outline-color:#fff }

/* 4.09:1 on white, and the textarea hint is normal-size text. */
::placeholder{ color:#5F6773; opacity:1 }      /* 5.72:1 */

/* 34px was the only tap target on the page under 44px; everything else is
   46-58px. Desktop keeps the compact size, where a pointer is precise. */
@media (max-width:1060px){
  .mobmenu .social{ width:44px; height:44px }
}

/* The marquee auto-scrolls for longer than five seconds, and its only pause
   hooks were :hover and :focus-within — neither of which a touch user has,
   and nothing inside a review card is focusable. WCAG 2.2.2 (Level A) needs a
   real control, so there is a button. */
.marquee__ctrl{
  display:inline-flex; align-items:center; gap:.5rem;
  margin:1.1rem auto 0; padding:.5rem .95rem;
  background:var(--paper); color:var(--ink);
  border:1px solid var(--hair); border-radius:999px;
  font-family:'Inter',sans-serif; font-size:.72rem; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; cursor:pointer;
  transition:border-color var(--dur-micro) var(--ease);
}
.marquee__ctrl:hover{ border-color:var(--blue-mid) }
.marquee__ctrl svg{ width:13px; height:13px; fill:currentColor; display:block }
.marquee__ctrl .ico-play{ display:none }
.marquee.is-stopped .marquee__ctrl .ico-play{ display:block }
.marquee.is-stopped .marquee__ctrl .ico-pause{ display:none }
/* A user-requested stop must beat the visibility/intersection pauses. */
.marquee.is-stopped .marquee__track{ animation-play-state:paused !important }
@media (prefers-reduced-motion:reduce){ .marquee__ctrl{ display:none } }

/* --- 22. Phone pass ------------------------------------------------------
   Found by testing real handset viewports — width AND height. The width-only
   sweep could never see any of this.
   ------------------------------------------------------------------------ */

/* The fixed call bar covers the bottom 58px of the SCREEN, not of the hero.
   On a short viewport the hero is taller than the screen, so its own bottom
   padding does not help and both hero CTAs landed underneath the bar —
   measured on iPhone SE landscape (667x375) and a 640x360 Android. */
@media (max-width:1060px){
  .hero__content{
    padding-bottom:calc(58px + env(safe-area-inset-bottom, 0px) + 1.5rem);
  }
}

/* The scroll cue sat on top of the CTAs on every short viewport — both
   landscape phones and a 568px-tall portrait. It is decoration; the CTAs are
   the page's job. */
@media (max-height:700px){
  .hero__scroll{ display:none }
}

/* Landscape phones. A 100svh hero with a 4.3rem headline does not fit in
   375px of height, so the whole block is compacted rather than letting it
   push the CTAs off the screen. */
@media (max-height:520px) and (orientation:landscape){
  .hero{ min-height:auto }
  .hero__content{
    padding-top:calc(var(--hdr-h) + .85rem);
    padding-bottom:calc(58px + env(safe-area-inset-bottom, 0px) + 1rem);
  }
  .hero h1{ font-size:clamp(1.9rem,5.2vw,2.6rem) }
  .hero__sub{ font-size:1rem; margin-top:.85rem }
  .hero .btn-row{ margin-top:1.15rem }
}

/* Tap targets. 32-38px on the logo and 37px on the marquee control were the
   only two controls left under 44px once footer text links are excluded. */
@media (max-width:1060px){
  .hdr__logo{ min-height:44px }
  .marquee__ctrl{ min-height:44px; padding-inline:1.15rem }
}



/* Very short viewports — a 640x360 budget Android in landscape, or a 320x568
   first-generation SE. At these sizes the hero content is taller than the
   screen no matter how the padding is arranged, so the CTAs fall into the
   bottom 58px that the fixed call bar owns. The lede is the one part that can
   go: the headline says the same thing and the buttons are the point. */
@media (max-height:430px) and (orientation:landscape){
  .hero__sub{ display:none }
  .hero h1{ font-size:clamp(1.7rem,4.6vw,2.2rem) }
  .hero__content{ padding-top:calc(var(--hdr-h) + .5rem) }
  .hero .btn-row{ margin-top:1rem }
}
@media (max-height:600px) and (orientation:portrait){
  .hero__content{ padding-top:calc(var(--hdr-h) + .75rem) }
  .hero h1{ font-size:clamp(2rem,8vw,2.6rem) }
  .hero__sub{ margin-top:.85rem; font-size:1rem }
  .hero .btn-row{ margin-top:1.15rem }
}

/* --- 23. Touch and mid-range phone pass ----------------------------------
   From a phone-only critique panel. Every rule here is an OVERRIDE placed at
   the end of the file on purpose: reordering the originals would have moved
   them out from under the reduced-motion blocks that depend on source order.
   ------------------------------------------------------------------------ */

/* backdrop-filter:blur(18px) on a full-width fixed header makes the compositor
   re-capture and blur the backdrop on every scrolled frame, for the whole
   page — the single most expensive thing on the page during a scroll on a
   mid-range Android GPU, competing with the hero's WebGL loop. It was painted
   under a 93% fill, so at most 7% of it was ever visible. Above 1060px the
   desktop nav is showing and the machine can afford it; below, it cannot. */
@media (max-width:1060px){
  .hdr.is-stuck,
  .hdr--solid{
    -webkit-backdrop-filter:none; backdrop-filter:none;
    background:rgba(15,49,99,.97);
  }
  .mobmenu{
    -webkit-backdrop-filter:none; backdrop-filter:none;
    background:#0F3163;
  }
}

/* will-change:transform was removed from .hero__img 2026-08-27. It pinned a
   full-screen composited layer, and on two of the client's own devices the
   hero's layer stack stopped compositing partway down the page — everything
   below the cut painted white, slicing the headline mid-glyph. Nothing on this
   site is worth a hero that does not render. */

/* Sticky hover on touch. iOS applies :hover on tap and holds it until you tap
   elsewhere, so a hover rule becomes a latched state. The marquee one is the
   real problem — touching the reviews row stopped the auto-scroll for good.
   Specificity is deliberate in each case: .marquee:not(.is-paused) outranks
   the pause button's own rule so the button still works, and :not(:active)
   leaves the press state on the call bar intact. */
@media (hover:none){
  .marquee:not(.is-paused) .marquee__track:hover{ animation-play-state:running }
  .callbar:hover:not(:active){ background:var(--accent); color:#fff }
  .tile a:hover img{ transform:none }
  .social:hover{ background:rgba(255,255,255,.08) }
  .rev:hover{ box-shadow:none }
}

/* Section 17 centres running copy along with the heading. That reads well
   while the 66ch cap sets the measure, but below ~560px the viewport sets it
   and the John paragraph lands as eight double-ragged lines with no fixed left
   edge to return to. margin-inline:0 is load-bearing — without it the 66ch box
   stays auto-centred and the flush-left text sits indented from the wrap.
   `:not(.step) >` matters: a step's numeral and heading are centred by design,
   so its paragraph has to stay centred with them. */
@media (max-width:560px){
  .section > .wrap :not(.step) > p:not(.eyebrow):not(.lede){
    text-align:left;
    margin-inline:0;
  }
}

/* --- 24. The van on a phone ----------------------------------------------
   Full-bleed background, the same idea as the desktop hero: photograph edge to
   edge, copy over the top of it.

   The photograph is 1415x1200 — wider per unit of height than any phone. With
   object-fit:cover in a 390x844 box it scales to the box's HEIGHT, so only
   about 39% of its width survives and object-position Y does nothing at all.
   Everything below is about making that narrow vertical slice a good picture:
   pick the right slice, then take just enough light out of it for white type
   without turning a white truck navy.
   ------------------------------------------------------------------------ */
@media (max-width:859px) and (orientation:portrait){
  /* X frames the slice on the logo badge and the painted wordmark, which is
     the part of this photograph worth showing. */
  .hero__img{ object-position:46% 50% }

  /* The desktop scrim is three stacked layers — a linear overlay, a radial
     behind the copy, and the WebGL floor. On desktop they fall on a van that
     fills a wide frame; on a phone all three land on the same flat white
     flank at once and it read as dark navy rather than as a truck. Measured
     over the composited pixels: the van's white came out at #2A3E5E.

     So on a phone the linear scrim does the work alone: strong at the very top
     where the header sits, strong again at the foot behind the call bar, and
     backed right off through the middle where the logo is. */
  .hero__overlay{
    background:linear-gradient(180deg,
      rgba(8,24,48,.80) 0%,
      rgba(8,24,48,.66) 14%,
      rgba(8,24,48,.72) 40%,
      rgba(8,24,48,.72) 70%,
      rgba(8,24,48,.86) 100%);
  }

  /* The plank floor is the third layer on the same flat white. Halving it
     keeps the motion without adding to the haze. */
  .hero__canvas{ opacity:.5 }
}

/* Keep the hero to roughly one screen on a short phone. At 375x667 it measured
   746px tall — 79px past the viewport — which pushed the Call button down
   behind the fixed call bar. These are the smallest reductions that fit it. */
@media (max-width:859px) and (orientation:portrait) and (max-height:745px){
  .hero h1{ font-size:clamp(1.95rem,7.4vw,2.45rem) }
  .hero__sub{ font-size:.98rem; margin-top:.8rem }
  .hero .btn-row{ margin-top:1.05rem; gap:.6rem }
  .hero__content{ padding-top:calc(var(--hdr-h) + .8rem) }
  .hero .eyebrow{ margin-bottom:.5rem }
}

/* --- 25. Inner-page header ------------------------------------------------
   The home page reserves the fixed header's height inside .hero__content.
   Nothing else did, so an inner page's first section slid underneath it.

   This is that reservation, and it doubles as the page's title block. It sits
   on --blue-deep so the solid blue header merges into it rather than ruling a
   line across the top of every page, which is the same relationship the header
   has with the dark hero photograph on the home page.
   ------------------------------------------------------------------------ */
.pagehead{
  background:var(--blue-deep); color:var(--on-dark);
  padding-top:calc(var(--hdr-h) + clamp(2.25rem,6vw,3.75rem));
  padding-bottom:clamp(2.25rem,6vw,3.75rem);
  text-align:center;
}
.pagehead h1{ color:#fff; margin:0 }
.pagehead .eyebrow{ color:var(--on-dark-accent) }
.pagehead .seam{ background:var(--on-dark-accent) }
.pagehead .lede{ color:var(--on-dark); margin-inline:auto }
/* .pagehead is not a .section, so the section-17 centring cascade never reaches
   it — the seam and the button row have to be centred here explicitly. */
.pagehead .seam{ margin-inline:auto }
.pagehead .btn-row{ justify-content:center }
.pagehead a:not(.btn){ color:var(--on-dark-accent) }
.pagehead .btn--ghost{ color:#fff; border-color:var(--on-dark) }
.pagehead .btn--ghost:hover{ background:#fff; color:var(--blue-deep) }

/* A material block on the flooring page: photograph one side, copy the other,
   alternating down the page. Reuses .grid--2's track sizing rather than
   inventing a second grid. */
.matrow{ align-items:center }
/* Section 17 centres everything inside a .section > .wrap. That is right for a
   single centred column and wrong beside a left-aligned photograph, where the
   ragged-both-edges column reads as a mistake. */
/* These have to out-specify section 17's own `.section > .wrap .seam` and
   `.section > .wrap p` (0,3,0), so they are written from .section down rather
   than from .matrow. A (0,2,1) selector loses to those and the column silently
   stays centred. */
.section > .wrap .matrow > div{ text-align:left }
.section > .wrap .matrow > div .seam{ margin-inline:0; transform-origin:left center }
.section > .wrap .matrow > div p{ margin-inline:0 }
.section > .wrap .matrow > div .btn-row{ justify-content:flex-start }
.matrow img{
  width:100%; height:auto; display:block; border-radius:2px;
  aspect-ratio:5/4; object-fit:cover;
}
.matrow figure{ margin:0 }
@media (min-width:720px){
  /* Even rows put the photograph on the right. order, not direction, so the
     DOM stays heading-then-image for a screen reader on every row. */
  .matrow--flip figure{ order:2 }
}
@media (prefers-reduced-motion:reduce){
  .matrow img{ transition:none }
}

/* --- 26. Long-form prose (privacy, terms, accessibility) -----------------
   Section 17 centres everything inside a .section > .wrap, which is right for
   a short marketing block and wrong for two thousand words of policy. These
   have to be written from .section down to out-specify it (0,3,0), and the
   display headings drop out of all-caps because a legal page set in Archivo
   800 uppercase reads as shouting rather than as a document.
   ------------------------------------------------------------------------ */
.section > .wrap .prose{ max-width:var(--max-prose); margin-inline:auto; text-align:left }
.section > .wrap .prose p,
.section > .wrap .prose ul,
.section > .wrap .prose ol{ margin-inline:0; text-align:left; max-width:none }
.section > .wrap .prose .seam{ margin-inline:0; transform-origin:left center }
.section > .wrap .prose h2,
.section > .wrap .prose h3{ text-align:left }
.prose h2{
  text-transform:none; font-size:1.4rem; font-weight:700; font-stretch:100%;
  line-height:1.25; letter-spacing:0; margin-top:2.6rem;
}
.prose h3{ text-transform:none; font-size:1.1rem; margin-top:1.8rem }
.prose h2 + p,.prose h3 + p{ margin-top:.7rem }
.prose ul{ margin:.9rem 0 0; padding-left:1.25rem }
.prose li{ margin-bottom:.45rem }
.prose .updated{ color:var(--muted); font-size:.9rem; margin-top:.35rem }

/* --- 27. Long single-word page titles at 320px ---------------------------
   --fs-h1 bottoms out at 2.35rem, and "Accessibility" is one 13-character
   word: at 320px it measured wider than the 280px text column and h1's
   overflow-wrap:break-word snapped it to "Accessibilit / y". A word cannot be
   re-broken, so the type has to give. This is in the shell because any future
   page with a long one-word title hits the same wall.
   ------------------------------------------------------------------------ */
@media (max-width:360px){
  .pagehead h1{ font-size:clamp(1.95rem,9vw,2.35rem) }
}

/* --- 28. A photograph shown at a controlled aspect ------------------------
   The recent-work photographs are square crops of taller originals. Dropped
   into a 1180px wrap at their own aspect they render as a 1180px square slab
   that fills the whole screen and reads as a mistake. .shot caps the width and
   crops to a landscape frame, which is what a photograph of a floor wants to
   be anyway.
   ------------------------------------------------------------------------ */
.shot{ max-width:860px; margin-inline:auto }
.shot img{ aspect-ratio:16/10; object-fit:cover; object-position:50% 55% }
@media (max-width:560px){
  .shot img{ aspect-ratio:4/3 }
}

/* --- 29. Inner-page header on short viewports -----------------------------
   Same defect the home hero had, in a different component: on a landscape
   phone (measured 844x390 and 667x375) and on a 320x568, the .pagehead is
   taller than the screen, so its CTAs land in the bottom 58px that the fixed
   call bar owns and are invisible on first paint. Compact the block rather
   than pad it — padding would only push the buttons further down.
   ------------------------------------------------------------------------ */
@media (max-height:540px) and (orientation:landscape){
  .pagehead{
    padding-top:calc(var(--hdr-h) + .9rem);
    padding-bottom:calc(58px + env(safe-area-inset-bottom, 0px) + .5rem);
  }
  .pagehead h1{ font-size:clamp(1.85rem,5vw,2.5rem) }
  .pagehead .lede{ font-size:1rem; margin-top:.75rem }
  .pagehead .seam{ margin-block:.75rem 1rem }
  .pagehead .btn-row{ margin-top:1rem }
}
@media (max-height:600px) and (orientation:portrait){
  .pagehead{
    padding-top:calc(var(--hdr-h) + 1rem);
    padding-bottom:calc(58px + env(safe-area-inset-bottom, 0px) + .5rem);
  }
  .pagehead h1{ font-size:clamp(1.95rem,7.6vw,2.5rem) }
  .pagehead .lede{ font-size:1rem; margin-top:.75rem }
  .pagehead .btn-row{ margin-top:1rem }
}

/* --- 30. A card that is entirely a link -----------------------------------
   A heading link inside a card is a 19px-tall tap target, which fails WCAG
   2.5.8's 24x24 minimum outright. Stretching the link over the whole card
   makes the target the card, and keeps one link per card for a screen reader
   rather than wrapping the whole thing in an anchor.
   ------------------------------------------------------------------------ */
.card--link{ position:relative; transition:border-color var(--dur-micro) var(--ease) }
.card--link h3 a::after{ content:''; position:absolute; inset:0 }
.card--link:hover{ border-color:var(--blue) }
.card--link h3 a{ text-decoration:none }
.card--link h3 a:focus-visible{ outline:none }
.card--link:focus-within{ outline:3px solid var(--blue); outline-offset:3px }

/* Measured on mobile-showroom at 844x390: the .pagehead came to 403px in a
   390px viewport and the CTAs overlapped the call bar by 5px (8px at 667x375).
   The lede is the only part that can go — the headline says the same thing and
   the buttons are the point. Same trade the hero makes at this height. */
@media (max-height:440px) and (orientation:landscape){
  .pagehead .lede{ display:none }
  .pagehead .seam{ margin-block:.6rem .85rem }
}

/* --- 31. Contrast corrections --------------------------------------------
   --hair (#DDE3EB) on white is 1.29:1. WCAG 1.4.11 wants 3:1 for the boundary
   of a user-interface component, and a form control whose edge you cannot see
   is a real problem on the one page whose whole job is the form. --hair stays
   as the hairline for decorative rules and card edges; controls get an edge
   you can actually see.
   ------------------------------------------------------------------------ */
.field input,.field select,.field textarea{ border-color:#8A93A3 }   /* 3.10:1 on #fff */
.field input::placeholder,.field textarea::placeholder{ color:#5C6675 }
.checks input{ accent-color:var(--blue) }

/* The desktop nav shows the current page with an underline (site.css:271). The
   phone menu is the ONLY nav below 1060px and had no equivalent, so
   aria-current was announced but never shown. */
@media (max-width:1060px){
  .mobmenu nav a[aria-current="page"]{
    color:#fff; font-weight:700;
    box-shadow:inset 3px 0 0 var(--on-dark-accent); padding-left:.75rem;
  }
}
